在linq中将实体类型作为参数传递

时间:2018-10-20 13:47:10

标签: c# linq generics c#-4.0 reflection

我该如何在linq中传递实体类型作为参数?

例如该方法将以字符串形式接收实体名称值,我想将实体名称传递给下面的linq查询。是否可以使linq查询通用?

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

我想将Entity类型作为参数传递并返回所有属性值。

还可以根据某些属性过滤结果吗?

4 个答案:

答案 0 :(得分:4)

假设您的context类看起来像这样:

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

    // and so on ...
}

最简单的解决方案是编写看起来像

的方法
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(); 
}

但是我们不想对这些内容进行硬编码,因此让我们使用Selector动态创建Linq.Expressions

在控制器内定义一个Func字段:

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

现在您可以为此成员创建工厂:

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();
}

并为其分配Func(在构造函数中的某个位置)

selector = SelectByType();

现在您可以像使用它了

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

答案 1 :(得分:3)

您有两个选择:

选项1:您在编译时就知道实体类型

如果在编译时知道实体类型,请使用通用方法:

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

用法:

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

选项2:您仅在运行时知道实体类型

如果您实际上想将实体类型作为字符串传递,请使用Set的另一个重载类型:

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

这假定entityType是包括程序集的完全限定的类型名称。有关详细信息,请参见this answer
如果所有实体都与上下文位于同一程序集中-或位于另一个众所周知的程序集中-您可以使用以下代码代替获取实体类型:

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

这允许您省略字符串中的程序集,但仍需要名称空间。

答案 2 :(得分:1)

即使上下文没有DbSet属性,您也可以实现自己想要的(如果有的话,那也没有害处)。通过反射调用DbContext.Set<TEntity>()方法:

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 });

现在您有了实体列表。

备注:为了消除反射带来的性能影响,您可以缓存一些类型和非泛型方法信息。

另一句话:我不建议这样做。正如评论中所说:这引起了一些关注。例如:您是否要允许客户端应用程序获取 any 实体表的所有未经过滤的数据?无论您在做什么,都要小心处理。

答案 3 :(得分:0)

在您的示例中,您似乎具有将实体名称作为参数的控制器操作,因此您将无法使方法通用。但是您可以使用反射,并且在很大程度上避免使用泛型。

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);
}

但是要记住一些事情:

  1. 假设您在上下文中具有与给定entityTypeName参数相对应的属性。如果entityTypeName实际上是类型名称而不是属性名称,则您需要做更多的工作才能找到合适的属性。
  2. 您的View必须知道在编译时不知道对象类型的情况下如何处理对象集合。可能必须使用反射来做您打算做的任何事情。
  3. 在这种方法中可能存在一些安全问题。例如,如果用户提供“数据库”或“配置”,则最终可能会暴露诸如连接字符串之类的信息,而这些信息与您存储的实际实体无关。
  

还可以根据某些属性过滤结果吗?

是的,它将涉及反射和/或dynamic的类似用法。您可以使用类似Dynamic LINQ的库来将字符串传递给类似LINQ的方法重载(WhereSelect等)。

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();
}