user793468
user793468

Reputation: 4966

passing entity type as parameter in linq

How would I go about passing an entity type as a parameter in linq?

For e.g. The method will receive the entity name value as a string and I would like to pass the entity name to the below linq query. Is it possible to make the linq query generic ?

public ActionResult EntityRecords(string entityTypeName)
{
    var entityResults = context.<EntityType>.Tolist();
    return View(entityResults);
}

I would like to pass the Entity type as a parameter and return all property values.

Also, is it possible to filter results based the some property?

Upvotes: 9

Views: 2312

Answers (4)

Gert Arnold
Gert Arnold

Reputation: 109109

You can achieve what you want even if the context doesn't have DbSet properties (and if it does, that doesn't harm). It is by calling the DbContext.Set<TEntity>() method by reflection:

var nameSpace = "<the full namespace of your entity types here>";

// Get the entity type:
var entType = context.GetType().Assembly.GetType($"{nameSpace}.{entityTypeName}");

// Get the MethodInfo of DbContext.Set<TEntity>():
var setMethod = context.GetType().GetMethods().First(m => m.Name == "Set" && m.IsGenericMethod);
// Now we have DbContext.Set<>(), turn it into DbContext.Set<TEntity>()
var genset = setMethod.MakeGenericMethod(entType);

// Create the DbSet:
var dbSet = genset.Invoke(context, null);

// Call the generic static method Enumerable.ToList<TEntity>() on it:
var listMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(entType);
var entityList = listMethod.Invoke(null, new[] { dbSet });

Now you've got your list of entities.

One remark: To get rid of some performance impact due to reflection you could cache some types and non-generic method infos.

Another remark: I don't think I would recommend this. As said in a comment: this raises a couple of concerns. For example: are you going to allow a client application to get all unfiltered data of any entity table? Whatever it is you're doing: handle with care.

Upvotes: 1

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174309

You have two options:

Option 1: You know the entity type at compile time

If you know the entity type at compile time, use a generic method:

public ActionResult EntityRecords<TEntity>()
{
    var entityResults = context.Set<TEntity>.ToList();
    return View(entityResults);
}

Usage:

public ActionResult UserRecords()
{
    return EntityRecords<User>();
}

Option 2: You know the entity type only at runtime

If you actually want to pass the entity type as a string, use the other overload of Set that takes a type:

public ActionResult EntityRecords(string entityType)
{
    var type = Type.GetType(entityType);
    var entityResults = context.Set(type).ToList();
    return View(entityResults);
}

This assumes that entityType is a fully qualified type name including assembly. See this answer for details.
If the entities are all inside the same assembly as the context - or in another well known assembly - you can use this code instead to get the entity type:

var type = context.GetType().Assembly.GetType(entityType);

This allows you to omit the assembly in the string, but it still requires the namespace.

Upvotes: 4

Aleks Andreev
Aleks Andreev

Reputation: 7054

Assuming your context class is looking like this:

public class MyContext : DbContext
{
    public DbSet<Entity1> Entity1 { get; set; }
    public DbSet<Entity2> Entity2 { get; set; }

    // and so on ...
}

simplest solution is to write method that looks like

private List<object> Selector(string entityTypeName)
{
  if (entityTypeName == "Entity1") 
    return context.Entity1.ToList();

  if (entityTypeName == "Entity2")
    return context.Entity2.ToList()

  // and so on  

  // you may add a custom message here, like "Unknown type"
  throw new Exception(); 
}

But we don't want to hardcode this stuff, so let create Selector dynamically with Linq.Expressions

Define a Func field within your controller:

private readonly Func<string, List<object>> selector;

Now you can create a factory for this member:

private Func<string, List<object>> SelectByType()
{
    var myContext = Expression.Constant(context);
    var entityTypeName = Expression.Parameter(typeof(string), "entityTypeName");

    var label = Expression.Label(typeof(List<object>));
    var body = Expression.Block(typeof(MyContext).GetProperties()
        .Where(p => typeof(IQueryable).IsAssignableFrom(p.PropertyType) && p.PropertyType.IsGenericType)
        .ToDictionary(
            k => Expression.Constant(k.PropertyType.GetGenericArguments().First().Name),
            v => Expression.Call(typeof(Enumerable), "ToList", new[] {typeof(object)}, Expression.Property(myContext, v.Name))
        )
        .Select(kv =>
            Expression.IfThen(Expression.Equal(kv.Key, entityTypeName),
              Expression.Return(label, kv.Value))
        )
        .Concat(new Expression[]
        {
            Expression.Throw(Expression.New(typeof(Exception))),
            Expression.Label(label, Expression.Constant(null, typeof(List<object>))),
        })
    );

    var lambda = Expression.Lambda<Func<string, List<object>>>(body, entityTypeName);
    return lambda.Compile();
}

and assign Func with it (somewhere in constructor)

selector = SelectByType();

Now you can use it like

public ActionResult EntityRecords(string entityTypeName)
{
    var entityResults = selector(entityTypeName);
    return View(entityResults);
}

Upvotes: 4

StriplingWarrior
StriplingWarrior

Reputation: 156524

In your example, it looks like you have a controller action that's taking the entity name as a parameter, so you won't be able to make your method generic. But you can use reflection and avoid the use of generics for the most part.

public ActionResult EntityRecords(string entityTypeName)
{
    var entityProperty = context.GetType().GetProperty(entityTypeName);
    var entityQueryObject = (IQueryable)entityProperty.GetValue(context);
    var entityResults = entityQueryObject.Cast<object>().ToList();
    return View(entityResults);
}

There are a few things to keep in mind, though:

  1. The assumption is that you've got a property on your context corresponding to the given entityTypeName argument. If entityTypeName is actually the type name instead of the property name, you'll need to do extra work to find the appropriate property.
  2. Your View will have to know what to do with a collection of objects where the type of the objects is not known at compile time. It'll probably have to use reflection to do whatever you intend for it to do.
  3. There may be some security concerns in a method like this. For example, if the user provides "Database" or "Configuration", you could end up exposing information like your connection string, which has nothing to do with the actual entities you've stored.

Also, is it possible to filter results based the some property?

Yes, and it will involve a similar use of reflection and/or dynamic. You could use a library like Dynamic LINQ to pass strings into LINQ-like method overloads (Where, Select, etc.).

public ActionResult EntityRecords(string entityTypeName, FilterOptions options)
{
    var entityProperty = context.GetType().GetProperty(entityTypeName);
    var entityQueryObject = entityProperty.GetValue(context);
    var entityResults = ApplyFiltersAndSuch((IQueryable)entityQueryObject);
    return View(entityResults);
}

private IEnumerable<object> ApplyFiltersAndSuch(IQueryable query, FilterOptions options)
{
    var dynamicFilterString = BuildDynamicFilterString(options);
    return query.Where(dynamicFilterString)
        // you can add .OrderBy... etc.
        .Cast<object>()
        .ToList();
}

Upvotes: 0

Related Questions