EF Core' DbSet
有一个名为Find
的方法:
查找具有给定主键值的实体。如果是实体 上下文跟踪给定的主键值,然后是它 在不向数据库发出请求的情况下立即返回。 否则,将对具有该实体的实体的数据库进行查询 给定主键值,如果找到该实体,则附加到 上下文并返回。如果未找到任何实体,则返回null。
我需要根据给定的主键值数组返回多个项目,当然所有这些都在一个请求中。在EF Core中有没有办法做到这一点?
更新:我知道在正常情况下我可以使用Where
子句。但我正在创建一个通用的辅助工具,在其中我无法访问模型的强类型属性。因此,我无法使用Where(x => ids.Contains(x.Id))
条款。
更新2 :理想的方法可以使用简单的签名来获取long
值列表,并返回T
列表。 public static List<T> FindSet(List<long> ids)
可以像这样使用:
var foundRecords = dbset.FindSet(new List<long> { 5, 17, 93, 178, 15400 });
答案 0 :(得分:4)
正如评论中所提到的,以天真的方式使用extern int a;
(例如循环遍历所有键值)将最终为每个值运行查询,因此这不是您想要做的。正确的解决方案是使用Find
查询一次获取所有项目。这里的问题只是您需要为主键动态请求它。
当然,数据库上下文本身确实知道给定实体类型的主键是什么。 Where
内部工作的方式是它使用该信息来构建动态查询,在该查询中检查主键上的相等性。因此,为了获得一些Find
,我们必须这样做。
以下是此的快速解决方案。这基本上为您构建了FindAll
查询。
请注意,我构建它的方式,它只适用于每个实体类型的单个主键。如果您尝试将它与复合键一起使用,它将抛出dbSet.Where(e => keyValues.Contains(e.<PrimaryKey>))
。你绝对可以扩展它,但增加了对复合键的支持;我只是没有这样做,因为它使一切变得更加复杂(特别是因为你不能使用NotSupportedException
)。
Contains
用法是这样的:
public static class DbContextFindAllExtensions
{
private static readonly MethodInfo ContainsMethod = typeof(Enumerable).GetMethods()
.FirstOrDefault(m => m.Name == "Contains" && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(object));
public static Task<T[]> FindAllAsync<T>(this DbContext dbContext, params object[] keyValues)
where T : class
{
var entityType = dbContext.Model.FindEntityType(typeof(T));
var primaryKey = entityType.FindPrimaryKey();
if (primaryKey.Properties.Count != 1)
throw new NotSupportedException("Only a single primary key is supported");
var pkProperty = primaryKey.Properties[0];
var pkPropertyType = pkProperty.ClrType;
// validate passed key values
foreach (var keyValue in keyValues)
{
if (!pkPropertyType.IsAssignableFrom(keyValue.GetType()))
throw new ArgumentException($"Key value '{keyValue}' is not of the right type");
}
// retrieve member info for primary key
var pkMemberInfo = typeof(T).GetProperty(pkProperty.Name);
if (pkMemberInfo == null)
throw new ArgumentException("Type does not contain the primary key as an accessible property");
// build lambda expression
var parameter = Expression.Parameter(typeof(T), "e");
var body = Expression.Call(null, ContainsMethod,
Expression.Constant(keyValues),
Expression.Convert(Expression.MakeMemberAccess(parameter, pkMemberInfo), typeof(object)));
var predicateExpression = Expression.Lambda<Func<T, bool>>(body, parameter);
// run query
return dbContext.Set<T>().Where(predicateExpression).ToArrayAsync();
}
}
我还添加了一些基本验证,因此// pass in params
var result = await dbContext.FindAllAsync<MyEntity>(1, 2, 3, 4);
// or an object array
var result = await dbContext.FindAllAsync<MyEntity>(new object[] { 1, 2, 3, 4 });
之类的内容会提前失败。
答案 1 :(得分:1)
如果要创建查找与主键列表匹配的所有行的通用查找方法,可以通过从基类继承这些实体类型来实现此目的,在基类中,它们共享主键列的相同名称。可以这样考虑:如果您的实体(数据库表)具有复合键,该方法将如何表现?因此,如果您可以遵循此类设计,则以下实现显示了使用.NET Core实现此目的的简单逻辑。 (实际上,您也可以使用EF6实现相同的行为)
public class MyBaseEntity
{
public int Id { get; set; }
}
public class MyTable : MyBaseEntity
{
public string MyProperty { get; set; }
}
public static class RepositoryExtensions
{
public static IQueryable<T> FindMacthes<T>(this DbContext db, IEnumerable<int> keys)
where T : MyBaseEntity
=> db.Set<T>().Where(x => keys.Contains(x.Id));
}
class Program
{
static void Main(string[] args)
{
// Initialize your own DbContext.
var db = new DbContext(null);
// Usage:
var lookupKeys = new[] { 1, 2, 3 };
var results = db.FindMacthes<MyTable>(lookupKeys).ToList();
}
}
答案 2 :(得分:0)
最近,我一直在寻找与您相同的东西,经过一些研究和反复试验,最终自己实现了这一目标。
我知道这个问题很旧,但我(尽管我也可能会寻求其他解决方案)
在.Net Core 2中工作,最终我为DBContext创建了两个扩展方法,如下所示:
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 IEnumerable<object> FindAll(this DbContext context, Type T, IEnumerable<object> ids)
{
// Set the base entity (T) parameter for the lambda and property expressions
var xParameter = Expression.Parameter(T, "a");
// Retrieve the primary key name from the model and set the property expression
var primaryKeyName = context.Model.FindEntityType(T).FindPrimaryKey().Properties.First().Name;
var xId = Expression.Property(xParameter, primaryKeyName);
var idType = xId.Type;
// Set the constant expression with the list of id you want to search for
var xIds = Expression.Constant(ids, typeof(IEnumerable<object>));
// Create the Expression call for the CastEnumerable extension method below
var xCastEnumerable = Expression.Call(typeof(IEnumerableExtensions), "CastEnumerable",new[]{idType},xIds);
// Create the expression call for the "Contains" method that will be called on the list
// of id that was cast just above with the id property expression as the parameter
var xContainsMethod = Expression.Call(typeof(Enumerable), "Contains",new[]{idType},xCastEnumerable, xId);
// Create a lambda expression with the "Contains" expression joined with the base entity (T) parameter
var xWhereLambda = Expression.Lambda(xContainsMethod, xParameter);
// Get the "Queryable.Where" method info
var whereMethodInfo = typeof(Queryable).GetMethods().SingleOrDefault(x => x.Name.Equals("Where") && x.GetParameters()[1].ParameterType.GetGenericType().GenericTypeArguments.Length == 2).MakeGenericMethod(T);
// Call the where method on the DbSet<T> with the lambda expression that compares the list of id with the entity's Id
return whereMethodInfo.Invoke(null, new object[] {context.Set(T),xWhereLambda}) as IEnumerable<object>;
}
第二个扩展方法取决于一个名为CastToList的IEnumerable扩展方法,如下所示:
public static class IEnumerableExtensions
{
public static IEnumerable<T> CastEnumerable<T>(this IEnumerable<object> sourceEnum)
{
if(sourceEnum == null)
return new List<T>();
try
{
// Covert the objects in the list to the target type (T)
// (this allows to receive other types and then convert in the desired type)
var convertedEnum = sourceEnum.Select(x => Convert.ChangeType(x, typeof(T)));
// Cast the IEnumerable<object> to IEnumerable<T>
return convertedEnum.Cast<T>();
}
catch (Exception e)
{
throw new InvalidCastException($"There was a problem converting {sourceEnum.GetType()} to {typeof(IEnumerable<T>)}", e);
}
}
}
我在代码中添加了注释,以帮助您更好地了解我的所作所为。
您可以这样称呼“ FindAll”:
yourDbContext.FindAll(entityType, ids)
很显然,这可能无法完全满足每个人的需求,并且可能需要进行一些调整才能达到预期的结果,但它应该提供一个坚实的起点。
在上面的代码中,我假设主键仅由单个属性组成。绝对有可能修改代码以涵盖组合键,但这超出了您的期望。
我希望这会帮助其他人寻找解决方案。
答案 3 :(得分:0)
受@poke答案启发的解决方案。它满足以下条件
// FIND ALL
// ===============================================================
/// <summary>
/// Tries to get all entities by their primary keys. Return all/partial/empty array of database entities.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="dbContext"></param>
/// <param name="args"></param>
/// <returns></returns>
public static async Task<TEntity[]> FindAllAsync<TEntity>(this DbContext dbContext, IEnumerable<TEntity> args) where TEntity : class
{
return await Task.Run(() => {
var dbParameter = Expression.Parameter(typeof(TEntity), typeof(TEntity).Name);
var properties = dbContext.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey()?.Properties;
if (properties == null)
throw new ArgumentException($"{typeof(TEntity).FullName} does not have a primary key specified.");
if (args == null)
throw new ArgumentNullException($"Entities to find argument cannot be null");
if (!args.Any())
return Enumerable.Empty<TEntity>().ToArray();
var aggregatedExpression = args.Select(entity =>
{
var entry = dbContext.Entry(entity);
return properties.Select(p =>
{
var dbProp = dbParameter.Type.GetProperty(p.Name);
var left = Expression.Property(dbParameter, dbProp);
var argValue = entry.Property(p.Name).CurrentValue;
var right = Expression.Constant(argValue);
return Expression.Equal(left, right);
})
.Aggregate((acc, next) => Expression.And(acc, next));
})
.Aggregate((acc, next) => Expression.OrElse(acc, next));
var whereMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Where" && m.GetParameters().Length == 2);
MethodInfo genericWhereMethod = whereMethod.MakeGenericMethod(typeof(TEntity));
var whereLambda = Expression.Lambda(aggregatedExpression, dbParameter);
var set = dbContext.Set<TEntity>();
var func = whereLambda.Compile();
var result = genericWhereMethod.Invoke(null, new object[] { set, func}) as IEnumerable<TEntity>;
return result.ToArray();
});
}
注释(if)ノ ✲゚ 。⋆