Как в Entity Framework 6 получить доступ к полям объекта, тип которого задан как дженерик?
Хочу создать метод наподобие
var doc = FindDocument<Накладная>(25);
чтобы искать документы любого типа по id.
Реализация например такая:
public T FindDocument<T>(long id)
{
context.???.FirstOrDefault(d => d.DocumentID == id);
}
То как я могу это сделать?
Вроде в EF есть возможность обращаться к таблицам не явно через context.DOCUMENTS.FirstOrDefault(...), а через context.Set<T>, но когда используются дженерики, то там нельзя задать условие .DocumentID == id, потому что он не знает о полях таблицы.
UPD: Накладная - это класс, сгенерированный EF'ом, т.е. "таблица" и она не имеет базового класса. Под каждый вид документа - Накладная, НалоговыйВычет и т.д. - отдельная таблица, но у них всех есть ID.
В обычной ситуации приходится писать:
context.Накладная.FirstOrDefault(d => d.Id == 25);
а мне нужно (если это возможно) вот так:
var doc = FindDocument<Накладная>(25);
поскольку поиск сопровождается еще дополнительной логикой, которую каждый раз копировать не хочется, и я мог бы ее спрятать в этот метод.
Ответы (2 шт):
Допустим, имеется интерфейс:
public interface IDocument
{
long DocumentID { get; set; }
}
Тогда можно ваш метод записать так:
public T FindDocument<T>(long id)
where T : class, IDocument
{
return context.Set<T>().FirstOrDefault(d => d.DocumentID == id);
}
Обратите внимание на ограничение (constraint) where.
Сущность Накладная должна реализовывать этот интерфейс.
Решение навеянное ответом отсюда: EntityFramework 6 How to get identity-field with reflection?.
Получив имя свойства, мы можем сгенерировать LINQ выражение динамически:
public T FindDocument<T>(long id) where T : class {
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
ObjectSet<T> set = objectContext.CreateObjectSet<T>();
ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
Expression body = Expression.Equal(Expression.Property(objParam, GetEntityKey(set)), Expression.Constant(id));
var predicate = Expression.Lambda<Func<T, bool>>(body, objParam);
return set.FirstOrDefault(predicate);
}
static string GetEntityKey<T>(ObjectSet<T> set) where T : class {
if(set.EntitySet.ElementType.KeyMembers.Count != 1) {
throw new NotSupportedException();
}
return set.EntitySet.ElementType.KeyMembers[0].Name;
}