【问题标题】:passing entity type as parameter in linq在linq中将实体类型作为参数传递
【发布时间】:2018-10-20 13:47:10
【问题描述】:

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

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

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

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

另外,是否可以根据某些属性过滤结果?

【问题讨论】:

  • 那么理想情况下,您想动态生成 linq 表达式吗?请明确说明您要做什么。
  • 名字怎么传。完全限定名称?
  • 你能稍微改一下你的问题吗?因为要么你问的东西有一个简单的答案,要么真的很困难,即使用 linq 动态构建查询..?传递参数很简单,即 bool value = true; var er = context.Table.Where(r => r.Prop1 == value);
  • @Nkosi 基本上,我想让 Linq 查询对实体类型通用,所以我可以传入实体名称,然后 linq 将输出结果
  • @user793468 显示您希望如何调用所需的 API。应该有助于澄清你在问什么。

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


【解决方案1】:

假设您的 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(); 
}

但是我们不想硬编码这些东西,所以让我们用Linq.Expressions动态创建Selector

在您的控制器中定义一个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);
}

【讨论】:

    【解决方案2】:

    你有两个选择:

    选项 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);
    

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

    【讨论】:

      【解决方案3】:

      即使上下文没有DbSet 属性,您也可以实现您想要的(如果有,那也无妨)。就是通过反射调用DbContext.Set&lt;TEntity&gt;()方法:

      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 实体表的所有 未过滤 数据?无论你在做什么:小心处理。

      【讨论】:

        【解决方案4】:

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

        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. 您的视图必须知道如何处理在编译时不知道对象类型的对象集合。它可能必须使用反射来执行您想要执行的任何操作。
        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();
        }
        

        【讨论】:

        • 我在 entityResults 收到以下错误 - 无法将类型“Entity.Employee”转换为类型“System.Object”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。
        • 您提到存在安全问题;您将如何更好地设计它以避免此类安全问题?
        • @user793468:这取决于您的安全状况。例如,您可以拥有一个允许客户端提供的entityTypeName 值的白名单,如果他们提供了该值列表之外的任何内容,它们就会被阻止。您可以设置允许人们查询的 DTO 中间层,以确保他们只看到您打算从此控制器操作中公开的属性。
        • 我只提出了安全问题,因为大多数时候人们将 EF 上下文重用于各种数据,但他们只是意味着从特定端点公开该数据的某些部分。通过获取未经处理的用户输入并使用它来公开反映的属性值,您将创建一个端点,有人可以从该端点访问与您的整个 EF 上下文关联的所有数据。如果那是您的意图,那就太好了;如果没有,那么你需要想办法限制事物只向他们展示他们应该看到的东西。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-09
        • 2019-02-27
        • 1970-01-01
        • 2015-03-22
        • 1970-01-01
        • 2012-06-12
        • 1970-01-01
        相关资源
        最近更新 更多