Grigore Dolghin Grigore Dolghin - 3 months ago 19
C# Question

Instantiate a concrete class based on generic parameter c#

Well, I tried to wrap my mind around this with no luck; can't find a better solution so I would gladly use some help/ideas.

Basically, I have a generic method which receives a businessobject as generic parameter and it should instantiate a EntityFramework entity and return it. There is a naming convention between the businessobject name and the entity framework name (i.e. if the BO is named 'UsersBo', then the EF entity is named 'Users') and that convention is enforced everywhere.

My code looks like this:

public dynamic CreateEntity<T>()
{
switch (typeof(T).Name)
{
case "UsersBo":
return new Users();
case "RolesBo":
return new Roles();
case "AppObjectsBo":
return new AppObjects();
default:
break;
}
return null;
}


I have two questions:


  1. Given the fact the BO and EF entity are located in different DLLs (BLL.dll vs DAL.dll), how the heck do I instantiate it without that ugly switch command? Tried with Activator.CreateInstance and it gave me errors regarding "fully qualified name"

  2. Is there any way to NOT use Dynamic objects? Right now I'm using dynamic becase I can't know which concrete class will be instantiated.



Thank you

================================
Later edit:

Here's the actual code:

public T GetById(Guid id)
{
T entityBo = new T();
var entity = new EntityFactory().CreateEntity<T>();
var repository = new EntityFactory().CreateRepository<T>();
try
{
entity = repository.GetById(id);
if (entity != null)
{
Mapper.Map(entity, entityBo);
}
}
catch (Exception ex)
{
entityBo = default(T);
throw ex.GetBaseException();
}
return entityBo;
}


=====================
I have a generic service class which works with business objects. Of course, these must be mapped to the corresponding EF objects.

If I'm calling the above method using UsersBo as T, the code will become:

public UsersBo GetById(Guid id)
{
UsersBo entityBo = new UsersBo(); // no problem here
var entity = new EntityFactory().CreateEntity<T>(); // That's the problem line: T is UsersBo, but I need a new Users (EF entity) in order to do the mapping later
var repository = new EntityFactory().CreateRepository<T>();
try
{
entity = repository.GetById(id); // this one returns the EF entity
if (entity != null)
{
Mapper.Map(entity, entityBo);
}
}
catch (Exception ex)
{
entityBo = default(T);
throw ex.GetBaseException();
}
return entityBo;
}


Hopefully now makes more sense. I am open to any suggestions, maybe I'm completely off and there's a better way.

Thank you guys.

=============

Slept over it. Top-notch answers, already changed the code and works like a charm. Thank you, guys, you were extremely helpful.

Answer

You could do something like this but I still do not like the overall design because you are forgoing type safety with the keyword dynamic. It would be better to use the passed in type constraint to get back the exact type you want to use in the calling code.

// personally i would change dynamic to object
// calling code could always cast to dynamic if it needed to
public dynamic CreateEntity<T>()
{
    var bllName = typeof(T).Name;
    var efName = bllName.Substring(0, bllName.Length - 2); // removes "Bo"

    var className = string.Concat("Namespace.", efName, ", EfAssembly.dll");
    var efType = Type.GetType(className);
    return efType != null ? Activator.CreateInstance(efType) : null;
}

This (see code below) would be my preference, here T would be the actual EF entity that you want to work with in the calling code and is not related at all to your BLL type. Alternatively you could enrich your BLL types to return the correct EF types but the negative on that is SoC as you would be breaking the loose coupling rule.

public T CreateEntity<T>() where T : class, new()
{
    return new T();
}

Based on your latest edit I refined your code with how you could approach this.

public T GetById<T>(Guid id) where T : new()
{
    T entityBo = new T();

    // this should not be needed, you do not use the results anywhere unless your EF type provides for some defaults at the BLL layer but it would be better to include those defaults in the BLL types constructor instead
    // var entity = new EntityFactory().CreateEntity<T>(); // That's the problem line: T is UsersBo, but I need a new Users (EF entity) in order to do the mapping later

    // this is the line where you need to implement the translation to get the correct repository type
    var repository = new EntityFactory().CreateRepository<T>();
    try
    {
        // get your object from your repository or if it returns null then you will end up just returning the default bll instance which is OK
        var entity = repository.GetById(id);
        if (entity != null)
        {
            // map what was returned
            Mapper.Map(entity, entityBo);
        }
    }
    catch (Exception ex)
    {
        // no need for this, it adds nothing
        // entityBo = default(T);

        // do not do this
        // throw ex.GetBaseException();

        // this is better, it preserves the stack trace and all exception details
        throw;
    }
    return entityBo;
}