实体框架按PrimaryKey过滤

时间:2017-02-15 04:51:05

标签: c# entity-framework find

我正在编写一个通用的crud服务我尝试使用可选的虚拟方法来实现Get方法以包含属性但是我遇到了一些麻烦,因为{{1} }仅在FindAsync

上声明
DbSet

我想做如上所述的事情:

public async virtual Task<TDTO> Get(object[] id)
{
     // I want to do something like this
     var entity = await this.ApplyGetIncludes(this.GetEntityDBSet()).FindAsync(id)
     return this.AdaptToDTO(entity);
}

protected virtual DbSet<TEntity> GetEntityDBSet()
{
    return this._context.Set<TEntity>();
}

protected virtual IQueryable<TEntity> ApplyGetIncludes(IQueryable<TEntity> queryable)
{
    return queryable;
}

但我知道因为我们需要数据库集而无法工作,所以我会设置这样的事情:

var entity = await this.ApplyGetIncludes(this.GetEntityDBSet()).FindAsync(id)

有谁知道如何按var entity = await this.ApplyGetIncludes(this.GetEntityDBSet().FilterByPK(id)) .FirstOrDefaultAsync();

的主键进行过滤

3 个答案:

答案 0 :(得分:2)

这是可能的,但该方法需要访问DbContext才能获取描述主键的元数据。然后,它可以根据元数据和传递的值构建动态谓词lambda表达式。

首先,我们需要一种收集实体主键属性信息的方法。

对于EF Core,它很简单:

static IReadOnlyList<IProperty> GetPrimaryKeyProperties(DbContext dbContext, Type clrEntityType)
{
    return dbContext.Model.FindEntityType(clrEntityType).FindPrimaryKey().Properties;
}

对于EF6来说,它有点复杂,但仍然可行:

struct KeyPropertyInfo
{
    public string Name;
    public Type ClrType;
}

public static IReadOnlyList<KeyPropertyInfo> GetPrimaryKeyProperties(DbContext dbContext, Type clrEntityType)
{
    var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
    var metadata = objectContext.MetadataWorkspace;
    var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
    var entityType = metadata.GetItems<EntityType>(DataSpace.OSpace)
        .Single(e => objectItemCollection.GetClrType(e) == clrEntityType);
    return entityType.KeyProperties
        .Select(p => new KeyPropertyInfo
        {
            Name = p.Name,
            ClrType = p.PrimitiveType.ClrEquivalentType
        })
        .ToList();
}

现在构建谓词的方法是这样的:

static Expression<Func<T, bool>> BuildKeyPredicate<T>(DbContext dbContext, object[] id)
{
    var keyProperties = GetPrimaryKeyProperties(dbContext, typeof(T));
    var parameter = Expression.Parameter(typeof(T), "e");
    var body = keyProperties
        // e => e.PK[i] == id[i]
        .Select((p, i) => Expression.Equal(
            Expression.Property(parameter, p.Name),
            Expression.Convert(
                Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                p.ClrType)))
        .Aggregate(Expression.AndAlso);
    return Expression.Lambda<Func<T, bool>>(body, parameter);
}

这里棘手的部分是如何让EF使用参数化查询。如果我们只使用Expression.Constant(id[i]),生成的SQL将使用常量值而不是参数。所以诀窍是使用持有该值的临时匿名类型的常量表达式的成员访问表达式(即属性或字段)(基本上模拟闭包)。

从上述方法获得谓词后,您可以将其用于FirstOrDefaultAsync或任何其他过滤方法。

答案 1 :(得分:0)

你的问题似乎有点困难。在我看来,不可能通过泛型方法实现目标,即按主键过滤所有表。 上面代码中的Id表示表(DBSet)的键。并且您必须根据不同的表查询区别对待id。 通过这种方式,我认为您最好使用抽象方法来获取数据

.map()

您必须根据具体表格实现Get方法(而每个表通常具有不同的主键)。

答案 2 :(得分:0)

我冒昧地制作了一些扩展方法来使这更容易,目前你必须传递上下文,因为从其私有字段获取上下文很痛苦。

public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
{
    return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
}

public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
{
    var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
    var parameter = Expression.Parameter(typeof(T), "e");
    var body = keyProperties
        // e => e.PK[i] == id[i]
        .Select((p, i) => Expression.Equal(
            Expression.Property(parameter, p.Name),
            Expression.Convert(
                Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                p.ClrType)))
        .Aggregate(Expression.AndAlso);
    return Expression.Lambda<Func<T, bool>>(body, parameter);
}

public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
    where TEntity : class
{
    return dbSet.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
}