问题
I have following classes and DbContext
:
public class Order:BaseEntity
{
public Number {get; set;}
}
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.
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?
回答1:
DbContext
has a method called Set
, that you can use to get a non-generic DbSet
, such as:
var someDbSet = this.Set(typeof(SomeEntity));
So in your case:
foreach (BaseEntity entity in list)
{
cntx.Set(entity.GetType()).Add(entity);
}
回答2:
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).
Yet you can still have a working extension method using Jon Skeet's answer to this question. My code is added below for convenience.
Update: Added the generic function call as well returning IQueryable<T>
thanks to the comment from Shaddix.
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>;
}
回答3:
To avoid error "System.Reflection.AmbiguousMatchException: 'Ambiguous match found.'" I used version below:
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;
}
回答4:
Unfortunately, the below proposed version does not work since .NET Core 3.0. You still can get an IQueryable
back, but you cannot cast it to DbSet
anymore.
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, i.e. directly on the DbSet<>
and not on IQueryable
. Attempting to specify them anywhere else will result in a compilation error.
See https://github.com/dotnet/efcore/issues/15704#issuecomment-493230352
回答5:
In addition to Pablo's answer, you can add a class to your project:
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.
When you want to use it, for example to Count
or filter a row:
var someDbSet = this.Set(typeof(SomeEntity));
var count = (from a in someDbSet.AsEnumerable() select a).Count();
来源:https://stackoverflow.com/questions/21533506/find-a-specified-generic-dbset-in-a-dbcontext-dynamically-when-i-have-an-entity