简体   繁体   English

实体框架按PrimaryKey筛选

[英]Entity Framework Filter By PrimaryKey

I'm writing a generic crud service I'm trying to implement the Get method with an optional virtual method to include properties However I'm having some trouble because FindAsync is only declared on a DbSet : 我正在编写一个通用的Crud服务,我试图通过一个可选的虚拟方法来实现Get方法以包含属性,但是我遇到了一些麻烦,因为FindAsync仅在DbSetDbSet

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;
}

I want to do something like this as depicted above: 我想做如上所述的事情:

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

but I know that won't work because we need the DB set so I would setting for doing something like this: 但我知道这行不通,因为我们需要数据库集,因此我将设置为执行以下操作:

var entity = await this.ApplyGetIncludes(this.GetEntityDBSet().FilterByPK(id))
                      .FirstOrDefaultAsync();

Does anyone know how I can filter by primary key from a DbSet ? 有谁知道我如何通过DbSet主键进行DbSet

It's possible, but the method needs access to the DbContext in order to get the metadata describing the primary key. 有可能,但是该方法需要访问DbContext才能获取描述主键的元数据。 Then it can build dynamically predicate lambda expression based on that metadata and the passed values. 然后,它可以基于该元数据和传递的值构建动态谓词lambda表达式。

First we need a method which gathers information about entity primary key properties. 首先,我们需要一种收集有关实体主键属性信息的方法。

For EF Core it's simple: 对于EF Core,它很简单:

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

For EF6 it's a bit more complicated, but still doable: 对于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();
}

Now the method for building the predicate is like this: 现在,构建谓词的方法如下:

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);
}

The tricky part here is how to let EF use parameterized query. 这里最棘手的部分是如何让EF使用参数化查询。 If we simply use Expression.Constant(id[i]) , the generated SQL will use constant values instead of parameters. 如果我们仅使用Expression.Constant(id[i]) ,则生成的SQL将使用常量值而不是参数。 So the trick is to use member access expression (ie property or field) of a constant expression of temporary anonymous type holding the value (basically simulating closure). 因此,诀窍是使用持有值的临时匿名类型的常量表达式的成员访问表达式(即属性或字段)(基本上模拟闭包)。

Once you obtain predicate from the above method, you can use it for FirstOrDefaultAsync or any other filtering method. 从上述方法获得谓词后,可以将其用于FirstOrDefaultAsync或任何其他过滤方法。

You question seems a little difficult. 您的问题似乎有点困难。 In my view, it is impossible to achieve you aim by a generic method to Filter by primary key to all Tables. 我认为,用通用方法按主键过滤所有表是不可能实现目标的。 The Id in you code above, means the keys of the Table(DBSet). 上面的代码中的ID表示表(DBSet)的键。 And you have to treat the id differently according to different Table query. 而且您必须根据不同的表查询对ID进行不同的处理。 In this way, I think you`d better use a abstract method as the following to get the data 这样,我认为您最好使用以下抽象方法来获取数据

public async abstract 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);
}

You must implement your Get method according to you concrete tables(while each table usually have different primary keys). 您必须根据具体表实现Get方法(而每个表通常具有不同的主键)。

I took the liberty of making some extension methods to make this easier, Currently you have to pass in the context, because it's a pain getting the context from its private field. 我乐于提供一些扩展方法来简化此操作,目前,您必须传递上下文,因为从私有字段中获取上下文很痛苦。

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));
}

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

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