16

I have the following code (example):

public dynamic GetData(string name) 
{
    using(var ctx = GetObjectContext()) 
    {
        switch (name) 
        {
        case "entity1":
            return ctx.entity1.ToList();
        case "entity2":
            return ctx.entity2.ToList();
        ......
        default:
            return null;
        }
    }
}

I want to avoid switch in this sample. How can I find needed entity class by name, call the ToList() method and return data? Can I do this using reflection?

halfer
  • 19,824
  • 17
  • 99
  • 186
user1209216
  • 7,404
  • 12
  • 60
  • 123
  • could you pls tell me what GetObjectContext() is? what namespace is it in? PS using core v3.1 – toy Feb 26 '20 at 05:24
  • Closed as duplicate, although the question is about EF6 (not core) but by now most people landing here will use EF core and this question already started to attract EF core answers. – Gert Arnold Aug 26 '23 at 19:10
  • How older question is a duplicate? Couldn't it be better to edit this one to indicate it's not related to core? – user1209216 Aug 26 '23 at 19:20
  • Sure, it's not rare at all for newer questions to serve as duplicate targets as technology evolves. The question could be edited, but that won't stop people from posting new EF core answers. (In fact, people that seriously try to answer this exact question should recognize right-away that it's not EF core). – Gert Arnold Aug 27 '23 at 10:22
  • That's actually not even related to Entity Framework, it's related to reflection. – user1209216 Aug 27 '23 at 19:20

3 Answers3

24

You can do it using reflection, however you will also need to use generics because the type of list returned by the ToList() method is different for each entity type.

You can access a property getter through reflection like so:

var enumerable = typeof([ClassNameOfContext]).GetProperty(name).GetValue(ctx, null);

Whereas [ClassNameOfContext] is the name of the class that ctx is an instance of. This is not obvious from your code, but you know it :-)

The problem is that enumerable will be an object and has to be casted to IEnumerable<EntityType> where EntityType is the type of entity you are accessing. In other words, it depends on the name you are passing. If you use generics to determine the type, you will be able to properly cast the object and don't have to return a dynamic even.

public TEntity Get<TEntity>(string name)
{
    ...

and transform the line from above:

var enumerable = (IEnumerable<TEntity>)(typeof([ClassNameOfContext]).GetProperty(name).GetValue(ctx, null));
return enumerable.ToList();

here you go!

Addendum: You could, conceivably, get rid of the string parameter, too - having names of types or properties in strings should be avoided where possible because it is not type safe. The compiler does not recognize it, and IDE features such as refactorings don't account for it. The problem here is that the property names are usually the pluralized form of the entity type names. But you could use reflection to find the property whose type matches the TEntity. I leave this as an exercise :-)

chiccodoro
  • 14,407
  • 19
  • 87
  • 130
  • How can I do this using reflection? Type returned by ToList does not matter, because GetData return type is dynamic - so it can be any type – user1209216 Sep 17 '12 at 08:21
  • @user1209216 - you are using dynamic to work around a problem as it seems to me. The `dynamic` keyword should be used conciously. It has its very specific use cases where it is reasonable to use, but in your case you are perfectly able to strongly type your return type. This is the preferred way to go. – chiccodoro Sep 17 '12 at 08:38
  • Sorry, I'm still little confused. What is "TEntity" and how should I declare it? I'm little newbie with this, so I need to ask. – user1209216 Sep 17 '12 at 08:46
  • @user1209216 - I am sorry, the most important part was missing, added it to the code snipped above. It is the ``. This is generics. When you call the method you will need to add the type of entity that you want in brackets like e.g. `GetData("Rabbits")` for an fictive case where you have Rabbit entities. Inside the method Visual Studio will treat `TEntity` as if it was an existing type. At runtime it is replaced(-ish) by whatever you call it with (Rabbit, e.g.) If you don't know generics yet, read up on it on the web. It is important basics. – chiccodoro Sep 17 '12 at 08:53
  • Hmm, ok I understand. But, the problem: I'm rather looking for my method with "switch" equivalent. I don't know entity type when my code calls it - only name is known. This method will get data to be printed on report, and report name is the same as entity name. So, in this case, called method can return dynamic- this is safe in this case. I need to pass report type as string. Of course, to avoid switch, I can use Dictionary but I thought there is better solution. – user1209216 Sep 17 '12 at 09:06
  • @user1209216 - I see. The reflection solution I mentioned is still valid, you just skip the generics stuff. – chiccodoro Sep 17 '12 at 12:37
  • typeof([ClassNameOfContext]) wouldn't have a concrete class if all that is known is a string. Wouldn't you need to invoke a class from a string? – Code Uniquely Jul 22 '13 at 04:37
  • @CodeUniquely: ClassNameOfContext is not the class of the entity but of the context. If the name is not known the type can be retrieved by using `GetObjectContext().GetType()` in favor of `typeof([ClassNameOfContext])`. – chiccodoro Jul 22 '13 at 06:25
  • @chiccodoro can you look at my question please ? I am trying to do the same but I didn't get your answer http://stackoverflow.com/questions/33416789/how-to-have-a-unique-c-sharp-code-with-different-entities-ef-having-the-same-col – ben Oct 29 '15 at 14:47
  • I was not able to make the suggested Generic method to work. The compiler could not deduct the TEntity type. It worked after specifying a common Interface implemented by the entities I was retrieving: TEntity Get(string name, ClassNameOfContext ctx ). – ywwy Jul 23 '19 at 12:59
  • @chiccodoro - would kindly post the full code - im not making heads or tails of what your wriitng with respect to the OP – toy Feb 26 '20 at 05:23
1

You can use code like this

private IEnumerable<TEntity> GetList<TEntity>(string connectionString, Func<object, T> caster)
{
    using (var ctx = new DbContext(connectionString))
    {
        var setMethod = ctx.GetType().GetMethod("Set").MakeGenericMethod(typeof(T));

        var querable = ((DbSet<object>)setMethod
        .Invoke(this, null))
        .AsNoTracking()
        .AsQueryable();

        return querable
            .Select(x => caster(x))
            .ToList();
    }
}

To call like this:

var branchList = GetList<Branch>("connectionStringName", x => (Branch)x);

You can remove .AsNoTracking() and remove .ToList(), then you will get pure IQueryable which you can query further.

Oleg Polezky
  • 1,006
  • 14
  • 13
0

I've created a method to include all related entities with some help of the great answer of @chiccodoro.

Using the entity "Product" which has 7 navigation properties

public static IQueryable<T> IncludeAllEntities<T>(this DbSet<T> entity, DataContext context) where T : class
{
        var queryable = entity.AsQueryable();

        var type = typeof(T);
        var entityType= context.Model.FindEntityType(type);
        var navs = entityType?.GetNavigations();

        if (navs == null)
        {
            return null;
        }

        List<string> navNames = new List<string>();

        foreach (var nav in navs)
        {
            navNames.Add(nav.Name);
        }

        try
        {
            var agg = navNames.Aggregate(queryable, (acc, name) => acc.Include(name));
            return agg;
        }
        catch (Exception ex)
        {
            throw;
        }
}

I am getting the type of the entity to get the navigation properties, then adding the name of each one to a list of string, then aggregating over the list to include each entity.

Then we can use this extension method like this:

var record = await _context.Products
                           .IncludeAllEntities(_context)
                           .FirstOrDefaultAsync(x => x.Id == key);
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
James.mikael
  • 99
  • 1
  • 9