6
\$\begingroup\$

I have about 11 singleton-esque classes divided over a dozen files:

the first file is one that gathers all singleton definitions and makes them callable on a request scope:

public sealed partial class ExpertiseverslagEngine
{
    readonly IOrganizationService _oService;
    readonly XrmServiceContext _oServiceContext;

    static readonly object Padlock = new object();

    ExpertiseverslagEngine()
    {
        _oService = CrmConnector.GetOrganization();
        _oServiceContext = new XrmServiceContext(_oService);
    }

    public static ExpertiseverslagEngine Instance
    {
        get
        {
            lock (Padlock)
            {
                if (UnitOfWorkHelper.CurrentDataStore["ExpertiseverslagEngine"] == null)
                {
                    UnitOfWorkHelper.CurrentDataStore["ExpertiseverslagEngine"] = new ExpertiseverslagEngine();
                }
                return (ExpertiseverslagEngine)UnitOfWorkHelper.CurrentDataStore["ExpertiseverslagEngine"];
            }
        }
    }
}

public sealed partial class OnderhoudEngine
{
    readonly IOrganizationService _oService;
    readonly XrmServiceContext _oServiceContext;

    static readonly object Padlock = new object();

    OnderhoudEngine()
    {
        _oService = CrmConnector.GetOrganization();
        _oServiceContext = new XrmServiceContext(_oService);
    }

    public static OnderhoudEngine Instance
    {
        get
        {
            lock (Padlock)
            {
                if (UnitOfWorkHelper.CurrentDataStore["OnderhoudEngine"] == null)
                {
                    UnitOfWorkHelper.CurrentDataStore["OnderhoudEngine"] = new OnderhoudEngine();
                }
                return (OnderhoudEngine)UnitOfWorkHelper.CurrentDataStore["OnderhoudEngine"];
            }
        }
    }
}

The other 11 files are all but 1 built in this manner:

public partial class ExpertiseverslagEngine
{

    public List<slfn_expertiseverslag> Retrieve_Active()
    {
        return (from exp in _oServiceContext.slfn_expertiseverslagSet
            select exp).ToList();
    }

    public slfn_expertiseverslag Retrieve(Guid expertiseverslagId)
    {
        slfn_expertiseverslag expertiseverslag = (from exp in _oServiceContext.slfn_expertiseverslagSet
            where exp.Id == expertiseverslagId
            select exp).First();

        return expertiseverslag;
    }

    public List<slfn_schadebeschrijvingwagen> Retrieve_Schade(Guid expertiseverslagGuid)
    {
        List<slfn_schadebeschrijvingwagen> beschrijvingen = (from sbw in _oServiceContext.slfn_schadebeschrijvingwagenSet
                                                             where sbw.slfn_Expertiseverslag.Id == expertiseverslagGuid
                                                             select sbw).ToList();
        return beschrijvingen;
    }

    public String Update(slfn_expertiseverslag oexpertiseverslag)
    {
        string status;
        try
        {


            _oServiceContext.UpdateObject(oexpertiseverslag);
            _oServiceContext.SaveChanges();
            status = "Update gelukt";
        }
        catch (Exception e)
        {
            status = String.Format("Update mislukt, foutboodschap: {0}", e.Message);
        }
        return status;
    }

    public string Create(slfn_expertiseverslag expertiseverslag)
    {
        string status;
        try
        {
            _oServiceContext.AddObject(expertiseverslag);
            _oServiceContext.SaveChanges();
            status = "Aanmaken gelukt";
        }
        catch (Exception e)
        {
            status = String.Format("Update mislukt, foutboodschap: {0}", e.Message);
        }
        return status;
    }
}

Another example of one of these classes:

public partial class AssetEngine
{

    public List<slfn_asset> Retrieve_Active()
    {
        return (from asset in _oServiceContext.slfn_assetSet
                select asset).ToList();
    }

    public slfn_asset Retrieve(Guid assetId)
    {
        slfn_asset oAsset = (from asset in _oServiceContext.slfn_assetSet
                             where asset.Id == assetId
                             select asset).First();

        return oAsset;
    }



    internal List<slfn_gebruikershistoriekasset> Retrieve_Geschiedenis(Guid assetId)
    {
        return (from gha in _oServiceContext.slfn_gebruikershistoriekassetSet
                where gha.slfn_Asset.Id == assetId
                select gha).ToList();
    }

    public string Update(slfn_asset oAsset)
    {
        string status;
        try
        {
            _oServiceContext.UpdateObject(oAsset);
            _oServiceContext.SaveChanges();
            status = "Update gelukt.";
        }
        catch (Exception e)
        {
            status = String.Format("Update mislukt, Foutmelding: {0}", e.Message);
        }
        return status;
    }

    public string Create(slfn_asset asset)
    {
        string status;
        try
        {

            _oServiceContext.AddObject(asset);
            _oServiceContext.SaveChanges();
            status = "Aanmaken gelukt.";
        }
        catch (Exception e)
        {
            status = String.Format("Aanmaken mislukt, Foutmelding: {0}.", e.Message);
        }
        return status;
    }
}

The final one is for a different purpose and looks like this:

public sealed partial class OptionsetEngine
{
    public List<OptionMetadata> GetGlobalOptionSet(string optionsetName)
    {
        // Use the RetrieveOptionSetRequest message to retrieve  
        // a global option set by it's name.
        RetrieveOptionSetRequest retrieveOptionSetRequest =
            new RetrieveOptionSetRequest
            {
                Name = optionsetName
            };

        // Execute the request.
        RetrieveOptionSetResponse retrieveOptionSetResponse =
            (RetrieveOptionSetResponse)_oService.Execute(
                retrieveOptionSetRequest);

        // Access the retrieved OptionSetMetadata.
        OptionSetMetadata retrievedOptionSetMetadata =
            (OptionSetMetadata)retrieveOptionSetResponse.OptionSetMetadata;

        // Get the current options list for the retrieved attribute.
        return retrievedOptionSetMetadata.Options.ToList();
    }


    public List<OptionMetadata> GetLocalOptionset(string entityName, string attributeName)
    {

        RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest
        {

            EntityLogicalName = entityName,

            LogicalName = attributeName,

            RetrieveAsIfPublished = true

        };
        // Execute the request.
        RetrieveAttributeResponse retrieveAttributeResponse = (RetrieveAttributeResponse)_oService.Execute(retrieveAttributeRequest);
        // Access the retrieved attribute.
        // Get the current options list for the retrieved attribute.
        List<OptionMetadata> optionlist = new List<OptionMetadata>();
        if (retrieveAttributeResponse.AttributeMetadata.GetType().FullName.Contains("PicklistAttributeMetadata"))
        {
            PicklistAttributeMetadata retrievedPicklistAttributeMetadata = (PicklistAttributeMetadata)retrieveAttributeResponse.AttributeMetadata;
            optionlist =  retrievedPicklistAttributeMetadata.OptionSet.Options.ToList();
        }else if (retrieveAttributeResponse.AttributeMetadata.GetType().FullName.Contains("StatusAttributeMetadata"))
        {
            StatusAttributeMetadata statusMetadata = (StatusAttributeMetadata)retrieveAttributeResponse.AttributeMetadata;
            // Get the current options list for the retrieved attribute.
            optionlist = statusMetadata.OptionSet.Options.ToList();
        }else if (retrieveAttributeResponse.AttributeMetadata.GetType().FullName.Contains("StateAttributeMetadata"))
        {
            StateAttributeMetadata stateMetaData = (StateAttributeMetadata)retrieveAttributeResponse.AttributeMetadata;
            optionlist = stateMetaData.OptionSet.Options.ToList();
        }
        return optionlist;
    }

    public IEnumerable<Entity> Retrieve_Assist_Entities(string entityName)
    {
        return _oServiceContext.CreateQuery(entityName).ToList();
    }
}

As you can tell, this is for development of a Dynamics CRM 2011 extension outside the website platform. I have 11 of these engines, and apart from the final one mentioned above, they all have the same methods and variables in them.

i'm wondering if it's worth it to extract an interface (something like an IEntityEngine) so additional engines can be added more easily, and how to go ahead with that. I see 2 difficulties:

  1. The clases are split over 2 files, so that might cause some difficulties extracting an interface;
  2. each engine is intended for a different entity type, and has to return a different early bound entity;
\$\endgroup\$
10
\$\begingroup\$

Extracting an interface essentially boils down to taking the signatures for all public members, making them part of the interface you're extracting, and then making the type implement the interface you've extracted.

Tools like ReSharper work amazingly well for this (the partial class is handled, no sweat - in fact, the interface only needs to be specified in one place, if it's in both files one of them is redundant):

R# Extract Interface

But before you start extracting interfaces, you should ask yourself why you have 11 classes that are essentially identical. Wouldn't a generic class make your life easier? It could implement a generic interface, which could look like this:

public interface IEngine<T, U>
{
    IEnumerable<T> RetrieveActive();
    T Retrieve(Guid id);
    IEnumerable<U> RetrieveSchade(Guid id);
    string Update(T value);
    string Create(T value);
}

Where T would be slfn_expertiseverslag and U would be slfn_schadebeschrijvingwagen, but then the type parameters would need perhaps better names.

Note that I'm exposing IEnumerable<T>, not List<T> - if the goal of the method is to expose data that's meant to be consumed you should expose IEnumerable<T>. Expose a List<T> when the client code needs to be able to Add() and Remove() items.

There's a problem with your naming. slfn_expertiseverslag and slfn_schadebeschrijvingwagen don't follow naming conventions for types. Should be PascalCase, no underscores (save snake_case for ;).

As to address whether it's worth it to extract an interface, I can't really tell (it could very well be just additional complexity). I think making your class generic would definitely be helpful though.

\$\endgroup\$
  • \$\begingroup\$ The reason the naming is all lowercase is because this is a Dynamics CRM 2011 custom entity. that slfn_ is a prefix which is automatically added when you make the entity, I cannot change that. I'm also going to give an example of another class to show that 1 generic class won't work without a redesign of the code. \$\endgroup\$ – Nzall Feb 27 '14 at 7:34
  • \$\begingroup\$ If you mean entity as in database table, and you're using Entity Framework, mapping POCO class ABC to database table XYZ is fairly easy; an entity doesn't have to be named exactly as the db table it's mapped to. I'll take a look at your updated code later today ;) \$\endgroup\$ – Mathieu Guindon Feb 27 '14 at 18:57
  • \$\begingroup\$ I mean Entity as in a Dynamics CRM% 2011 Entity, from Microsoft.Xrm.Sdk.Entity. I don't think that's related to the Entity Framework. \$\endgroup\$ – Nzall Feb 28 '14 at 7:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.