Reputation: 4966
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
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
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
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
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:
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.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