2017-02-15 13 views
0

Я пишу общую службу Crud Я пытаюсь реализовать метод Get с дополнительным виртуальным способом, чтобы включать в себя свойство, однако у меня возникают некоторые проблемы, потому что FindAsync только объявлена ​​на DbSet:Entity Framework Filter By 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. Затем он может создавать динамически предикативное выражение лямбда на основе этих метаданных и переданных значений.

Сначала нам нужен метод, который собирает информацию о свойствах первичного ключа сущности.

Для 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

Вы сомневаетесь, что это немного сложно. На мой взгляд, невозможно достичь цели с помощью общего метода фильтрации по первичному ключу ко всем таблицам. Идентификатор в коде выше, означает ключи таблицы (DBSet). И вы должны обрабатывать id по-разному в соответствии с другим запросом таблицы. Таким образом, я думаю, что лучше использовать `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); 
} 

Вы должны реализовать Получ в соответствии с вами конкретными таблицами (в то время как каждая таблица обычно имеет различные первичные ключи) ,

+0

@WangJihun. Метод FindAsync (id) уже существует, что делает именно то, что он находит сущность с этим первичным ключом. EntityFramework хранит первичные ключи в моем случае составные первичные ключи. Значение Я хочу делать то, что находит, но возвращает IQueryable.Это должно быть доступно где-то в коде EF Opensource, но мне было интересно, как это сделать, не выкапывая глубину этого. Я знаю, что могу использовать абстрактный метод, но я кодирую это, поэтому я не буду вынужден внедрять эти методы, и в случае, если вы описали его, было бы лучше использовать виртуальный метод. –

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