Как в 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 шт):

Автор решения: Alexander Petrov

Допустим, имеется интерфейс:

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.

Сущность Накладная должна реализовывать этот интерфейс.

→ Ссылка
Автор решения: Uranus

Решение навеянное ответом отсюда: 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;
}
→ Ссылка