28

I am using System.Linq.Dynamic.Core to dynamically add in lambda expressions to queries in EF.

I want to also be able to select the table by name. I found this answer:

https://stackoverflow.com/a/28101268/657477

But it is not working in asp.net core 2.0. I cannot use DbSet I must use DbSet<TEntity> it says in error message.

I want to be able to do db.GetTable("Namespace.MyTable").Where(...)

How can I do this?

Guerrilla
  • 13,375
  • 31
  • 109
  • 210

3 Answers3

37

First you need to get the type of the entity from the name (in case you have the type, just use it directly). You can use reflection for that, but probably the correct way for EF Core is to use FindEntityType method.

Once you have the type, the problem is how to get the corresponding DbSet<T>. EF Core currently does not provide non generic Set(Type) method similar to EF6, mainly because there is no non generic DbSet class. But you can still get the corresponding DbSet<T> as IQueryable by either using some EF Core internals:

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomExtensions
    {
        public static IQueryable Query(this DbContext context, string entityName) =>
            context.Query(context.Model.FindEntityType(entityName).ClrType);

        public static IQueryable Query(this DbContext context, Type entityType) =>
            (IQueryable)((IDbSetCache)context).GetOrAddSet(context.GetDependencies().SetSource, entityType);
    }
}

or (preferable) invoking the generic Set<T> method via reflection:

using System;
using System.Linq;
using System.Reflection;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomExtensions
    {
        public static IQueryable Query(this DbContext context, string entityName) =>
            context.Query(context.Model.FindEntityType(entityName).ClrType);

        static readonly MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set), Type.EmptyTypes);

        public static IQueryable Query(this DbContext context, Type entityType) =>
            (IQueryable)SetMethod.MakeGenericMethod(entityType).Invoke(context, null);
    }
}

In both cases you can use something like this:

db.Query("Namespace.MyTable").Where(...)

or

db.Query(typeof(MyTable)).Where(...)
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 1
    This works great. Thanks for helping me out, I was really struggling with this one. – Guerrilla Jan 01 '18 at 00:52
  • I came across an issue with this method. I am not able to include nested entities as there is no definition for Include(). Any way around this? – Guerrilla Jan 01 '18 at 02:54
  • This is different question. But the solution would be similar - taking `IQueryable` and `string`, call `Include` method via reflection. – Ivan Stoev Jan 01 '18 at 08:57
  • I saw your new question and I'm confused from the usage `IQueryable dbSet = (IQueryable)_db.Query(entityName);` - looks like we are solving the wrong problem here. If you have generic type argument `T`, there is no need of custom extension method because you simply use `_db.Set()`, which returns `IQueryable`, hence you have access to all extension methods available for `IQuerable` - standard as well as custom defined by Dynamic LINQ. – Ivan Stoev Jan 01 '18 at 10:12
  • Yes, my code was convoluted and I refactored it so I can get the type. I have it working now, thanks for the help – Guerrilla Jan 03 '18 at 03:20
  • 3
    in my case IQueryable does not contain definition of Where :( – mzain Sep 11 '18 at 14:56
  • 1
    @mzain It doesn't. All the LINQ extension methods are defined for `IQuerabley` and not for `IQueryable`. So while the answer shows how to retrieve the `DbSet` by `Type`, the returned result is not directly usable. If you know the type `T` in advance, better use `Set()` method. – Ivan Stoev Sep 12 '18 at 15:16
  • @Ivan, Thanks for suggestion but i don't know the type, only have string name of table and string column names. Any idea would be helpful. – mzain Sep 13 '18 at 10:59
  • @mzain It's hard to give you good advice inside comments. Consider posting your own question explaining the usage scenario - what you have, what you want to do with that etc. I'm watching the `entity-framework-core` tag and will be happy to help. – Ivan Stoev Sep 13 '18 at 11:40
  • @mzain I am on the exact same situation. Did you make the question or found a solution elsewhere? Thanks – Leo May 14 '19 at 15:09
  • 2
    @Guerrilla How does this work great? You can't perform .Where on Iqueriable without generic typing. – DiscipleMichael Jan 18 '20 at 21:22
  • @DiscipleMichael By looking at my second comment it seems I refactored my code so I could get the type. FYI this was 2 years ago – Guerrilla Jan 20 '20 at 08:16
  • Nope doesn't work. All LinQ operations are gone (EFC 3.1) – Desolator Jun 04 '21 at 18:54
  • @SolidSnake Define "doesn't work". The concrete question here is how to obtain non generic db set by name or `Type`, so the above perfectly does that. As of further LINQ operators, if you don't use generic `Set()` method, I don't see what "LINQ operators" do you expect on **non generic** `IQueryable` since all LINQ operators are **generic**. Or even on generic `IQueryable` if you cast the result. To be able to do something with them, you need 3rd party extension like `System.Linq.Dynamic.Core` as mentioned at the beginning of the OP (*"I am using System.Linq.Dynamic.Core"*). – Ivan Stoev Jun 05 '21 at 09:06
7

EF Core no longer has a non generic Set method but This extension class makes it easy to query your table based on a string using dynamic linq. Consider TableTypeDictionary to be a Dictionary<string, Type> mapping DbSet types with their type names.

public static class DbContextExtensions
{
    public static IQueryable<Object> Set(this DbContext _context, Type t)
    {
        return (IQueryable<Object>)_context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
    }


    public static IQueryable<Object> Set(this DbContext _context, String table)
    {
        Type tableType;
        //One way to get the Type
        tableType = _context.GetType().Assembly.GetExportedTypes().FirstOrDefault(t => t.Name == table);
        //The Second way, get from the dictionary which we've initialized at startup
        tableType = TableTypeDictionary[table];
        //The third way, works only if 'table' is an 'assembly qualified type name'
        tableType = Type.GetType(table);

        IQueryable<Object> objectContext = _context.Set(tableType);
        return objectContext;
    }
}

Usage:

IQueryable<Object> query = db.Set("TableName");
//or if you're using the third way:
query = db.Set(MyTable.GetType().AssemblyQualifiedName);

// Filter against "query" variable below...
List<Object> result = query.ToList();
// or use further dynamic Linq
IQueryable<Object> query = db.Set("TableName").Where("t => t.TableFilter == \"MyFilter\"");
Bamdad
  • 726
  • 1
  • 11
  • 28
jdmneon
  • 444
  • 7
  • 12
1

For those of you like me reading this question now and on newer versions of Ef Core you can just do the following (ef core 5):

// Add extension method:
public static class DBContextExtensions
{
    public static IQueryable<Object> Get(this DbContext _context, Type t)
    {
        return (IQueryable<Object>)_context.GetType().GetMethod($"get_{t.Name}").Invoke(_context, null);
    }

    // Or the modified set which handles ambigous ref issues:
    public static IQueryable<Object> Set(this DbContext_context, Type t)
    {
        return (IQueryable<Object>)_context.GetType().GetMethods()
                .First(x => x.Name == "Set" && x.ContainsGenericParameters)
                .MakeGenericMethod(t).Invoke(_context, null);
    }
}

// Important using for string value in where clause of set query
using System.Linq.Dynamic.Core;

// Use get as follows
var type = MyDbType.ClrType.AssemblyQualifiedName; // var MyDbType is an IEntityType
context.Get(type).ToList();

// Use set as follows:
var myRecord = dbContext.someTable.First().GetType();
var myType = myRecord.ClrType; // var myRecord is an IEntityType
var myWhereClause = "myColumnNameFromTable = 5";
var query = dbContext.Set(fkType).AsNoTracking().OfType(fkType).Where(myWhereClause).ToDynamicList();
John
  • 214
  • 3
  • 14