2017-02-15 103 views
0

我写我试图用一个可选的虚拟方法来实现Get方法包括但遇到一些麻烦,因为FindAsync仅在一个DbSet声明的属性通用的CRUD服务:实体框架筛选通过的PrimaryKey

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) 

,但我知道这是行不通的,因为我们需要的DB设置,所以我会设置做这样的事情:

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

有谁知道我可以如何通过主键从DbSet过滤?

回答

2

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

首先我们需要一个方法来收集关于实体主键属性的信息。

对于EF核心很简单:

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或任何其他过滤方法。

+0

谢谢我急需这个 –

0

你的问题似乎有点困难。在我看来,通过一个通用的方法来实现你的目标是不可能的,通过主键向所有表进行过滤。 上面的代码中的Id表示表(DBSet)的键。而且你必须根据不同的表查询以不同的方式处理id。 通过这种方式,我认为you`d更好的使用抽象方法,下面来获取数据

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

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

+0

@WangJihun .FindAsync(id)方法已经存在,它完全正确地找到了具有该主键的实体。 EntityFramework将主键存储在我的组合主键中。含义我想做什么,但返回一个IQueryable。这应该在EF开源代码中的某处可用,但我想知道如何通过深入挖掘来实现这一点。我知道我可以使用抽象方法,但是我编写了这个程序,所以我不会被迫实现这些方法,并且在描述它的情况下最好使用虚拟方法 –

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