简体   繁体   English

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

[英]passing entity type as parameter in linq

How would I go about passing an entity type as a parameter in linq? 我如何在linq中将实体类型作为参数传递?

For eg 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. 例如,该方法将接收实体名称值作为字符串,我想将实体名称传递给下面的linq查询。 Is it possible to make the linq query generic ? 是否可以使linq查询通用?

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? 此外,是否可以根据某些属性过滤结果?

Assuming your context class is looking like this: 假设您的context类看起来像这样:

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 但是我们不想对这些东西进行硬编码,所以让我们使用Linq.Expressions动态创建Selector

Define a Func field within your controller: 在控制器中定义Func字段:

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) 并使用它分配Func (在构造函数中的某处)

selector = SelectByType();

Now you can use it like 现在你可以像使用它一样

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

You have two options: 您有两种选择:

Option 1: You know the entity type at compile time 选项1:您在编译时知道实体类型

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 选项2:仅在运行时知道实体类型

If you actually want to pass the entity type as a string, use the other overload of Set that takes a type: 如果您确实希望将实体类型作为字符串传递,请使用带有类型的Set的另一个重载:

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. 这假设entityType是包含程序集的完全限定类型名称。 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. 这允许您省略字符串中的程序集,但它仍然需要命名空间。

You can achieve what you want even if the context doesn't have DbSet properties (and if it does, that doesn't harm). 即使上下文没有DbSet属性(如果有,也没有损害),你可以实现你想要的。 It is by calling the DbContext.Set<TEntity>() method by reflection: 通过反射调用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 });

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. 无论你在做什么:小心处理。

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. 假设您在上下文中有一个与给定entityTypeName参数对应的属性。 If entityTypeName is actually the type name instead of the property name, you'll need to do extra work to find the appropriate property. 如果entityTypeName实际上是类型名称而不是属性名称,则需要执行额外的工作来查找相应的属性。
  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. 您的View必须知道如何处理在编译时未知对象类型的对象集合。 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 . 是的,它将涉及类似的反射和/或dynamic You could use a library like Dynamic LINQ to pass strings into LINQ-like method overloads ( Where , Select , etc.). 您可以使用像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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM