简体   繁体   English

当我有一个实体时,动态地在 DbContext 中查找指定的通用 DbSet

[英]Find a specified generic DbSet in a DbContext dynamically when I have an entity

I have following classes and DbContext :我有以下课程和DbContext

public class Order : BaseEntity
{
    public Number {get; set;}
}
public class Product : BaseEntity;
{
    public Name {get; set;} 
}

public class Context : DbContext
{
    ....
    public DbSet<Order> Orders { set; get; }
    public DbSet<Product> Products { set; get; }
    ....
}   

I have a list of objects that want to add to my context, too, but I don't know how can I find appropriate generic DbSet according each entity type dynamically.我也有一个要添加到我的上下文中的对象列表,但我不知道如何根据每个实体类型动态地找到合适的通用DbSet

IList<BaseEntity> list = new List<BaseEntity>();
Order o1 = new Order();
o1.Numner = "Ord1";
list.Add(o1);

Product p1 = new Product();
p1.Name = "Pencil";
list.Add(p1);

Context cntx = new Context();  
foreach (BaseEntity entity in list)
{
      cntx.Set<?>().Add(entity);         
}

How can I do that?我怎样才能做到这一点?

DbContext has a method called Set , that you can use to get a non-generic DbSet , such as: DbContext有一个名为Set的方法,您可以使用它来获取非泛型DbSet ,例如:

var someDbSet = this.Set(typeof(SomeEntity));

So in your case:所以在你的情况下:

foreach (BaseEntity entity in list)
{
      cntx.Set(entity.GetType()).Add(entity);         
}

The question does not specify EF version and the proposed answer does not work anymore for Entity Framework Core (in EF Core, DbContext does not have a non-generic Set method, at least at the date of this answer).该问题没有指定 EF 版本,并且建议的答案不再适用于 Entity Framework Core(在 EF Core 中, DbContext没有非通用的Set方法,至少在此答案的日期)。

Yet you can still have a working extension method using Jon Skeet's answer to this question .然而,您仍然可以使用 Jon Skeet 对这个问题的回答来获得一个有效的扩展方法。 My code is added below for convenience.为方便起见,下面添加了我的代码。

Update: Added the generic function call as well returning IQueryable<T> thanks to the comment from Shaddix.更新:由于来自 Shaddix 的评论,添加了通用函数调用并返回IQueryable<T>

public static IQueryable Set(this DbContext context, Type T)
{
    // Get the generic type definition
    MethodInfo method = typeof(DbContext).GetMethod(nameof(DbContext.Set), BindingFlags.Public | BindingFlags.Instance);

    // Build a method with the specific type argument you're interested in
    method = method.MakeGenericMethod(T);

    return method.Invoke(context, null) as IQueryable;
}

public static IQueryable<T> Set<T>(this DbContext context)
{
    // Get the generic type definition 
    MethodInfo method = typeof(DbContext).GetMethod(nameof(DbContext.Set), BindingFlags.Public | BindingFlags.Instance);

    // Build a method with the specific type argument you're interested in 
    method = method.MakeGenericMethod(typeof(T)); 

    return method.Invoke(context, null) as IQueryable<T>;
} 

Unfortunately, the below proposed version does not work since .NET Core 3.0.不幸的是,以下建议的版本自 .NET Core 3.0 起不起作用。 You still can get an IQueryable back, but you cannot cast it to DbSet anymore.您仍然可以返回IQueryable ,但不能DbSet其转换为DbSet

IQueryable<TEntity> as DbSet<TEntity> => null

What's worse is that starting with EF Core 3.0, the new FromSqlRaw and FromSqlInterpolated methods (which replace FromSql ) can only be specified on query roots, ie directly on the DbSet<> and not on IQueryable .更糟糕的是,从 EF Core 3.0 开始,新的FromSqlRawFromSqlInterpolated方法(替换FromSql )只能在查询根上指定,即直接在DbSet<>而不是在IQueryable Attempting to specify them anywhere else will result in a compilation error.尝试在其他任何地方指定它们将导致编译错误。

See https://github.com/dotnet/efcore/issues/15704#issuecomment-493230352https://github.com/dotnet/efcore/issues/15704#issuecomment-493230352

To avoid error "System.Reflection.AmbiguousMatchException: 'Ambiguous match found.'" I used version below:为了避免错误“System.Reflection.AmbiguousMatchException: 'Ambiguous match found.'”我使用了以下版本:

    public static IQueryable Set(this DbContext context, Type T)
    {
        MethodInfo method = typeof(DbContext).GetMethods()
            .Where(p => p.Name == "Set" && p.ContainsGenericParameters).FirstOrDefault();

        // Build a method with the specific type argument you're interested in
        method = method.MakeGenericMethod(T);

        return method.Invoke(context, null) as IQueryable;
    }

In EF Core we only have Set<TEntity>() method(s) which have a constraint that TEntity must be a type of class , so this prevents you accidentally passing a interface type that EF is impossible to find out any existing DbSet.在 EF Core 中,我们只有Set<TEntity>()方法,它有一个约束,即TEntity必须是 class 的类型,因此这可以防止您意外传递一个 EF 无法找到任何现有 DbSet 的接口类型。

So if it's possible, you can always restrict your generic type parameter as the same as Set<TEntity>() requiring, for example this will not work:因此,如果可能的话,您始终可以将泛型类型参数限制为与Set<TEntity>()要求相同,例如,这将不起作用:

public void GetDbSet<T>(DbContext db) {
    db.Set<T>()
//     ~~~~~~~~
// Error CS0452 The type 'T' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method 'DbContext.Set<TEntity>()'
}

but this will:但这将:

public void GetDbSet<T>(DbContext db) where T : class {
    db.Set<T>()
}

In addition to Pablo's answer, you can add a class to your project:除了 Pablo 的回答之外,您还可以在您的项目中添加一个类:

namespace System.Data.Entity
{
    public static class EntityFrameworkExtensions
    {
        public static IEnumerable<object> AsEnumerable(this DbSet set)
        {
            foreach (var entity in set)
            {
                yield return entity;
            }
        }
    }
}

This class adds an extention method AsEnumerable to DbSet instance.此类向DbSet实例添加了扩展方法AsEnumerable

When you want to use it, for example to Count or filter a row:当你想使用它时,例如Count或过滤一行:

var someDbSet = this.Set(typeof(SomeEntity));
var count = (from a in someDbSet.AsEnumerable() select a).Count();

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

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