IQueryable <T>с EntityObject, используя Generics & Interfaces (возможно?) - PullRequest
1 голос
/ 03 февраля 2012

У меня есть поисковый репозиторий для EntityFramework 4.0, использующий LinqKit со следующей функцией поиска:

public IQueryable<T> Search<T>(Expression<Func<T, bool>> predicate) 
    where T : EntityObject
{
    return _unitOfWork.ObjectSet<T>().AsExpandable().Where(predicate);
}

И другой класс, который использует возвращаемое значение IQueryable для поднабора запроса способами, которые не возможны с использованием логического значенияВыражения LinqKit PredicateBuilder:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : EntityObject
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GUID,
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

Проблема здесь в том, что «T» как EntityObject не определяет GUID, поэтому я не могу использовать это.Естественным ответом на это является определение метода SubsetByUser () для использования ограничения со свойством GUID:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : IHaveMetadata
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GUID,
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

Но это не работает.Я использую LinqKit и метод Expandable () приводит к:

System.NotSupportedException: Unable to cast the type 'Oasis.DataModel.Arc' to
type 'Oasis.DataModel.Interfaces.IHaveMetadata'. LINQ to Entities only supports 
casting Entity Data Model primitive types

Мне нужен IQueryable для возврата.Я могу сделать подделку, как это:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : EntityObject
{
    return set.AsEnumerable()
              .Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GUID,
                    meta => meta.ElementGUID,
                    (arc, meta) => arc)
              .AsQueryable();
}

Что, конечно, работает, но это, конечно же, безумная вещь, которую надо делать.(Единственная причина, по которой я хочу IQueryable, заключается в том, чтобы не выполнять запрос до тех пор, пока мы не закончим.

Я даже попробовал это:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : EntityObject
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GetType().GetProperty("GUID").GetValue(arc,null),
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

, который использует отражение для получения коллекции Runs -обойти ошибку компилятора. Я думал, что это было довольно умно, но это приводит к исключению LINQ:

System.NotSupportedException: LINQ to Entities does not recognize the 
method 'System.Object GetValue(System.Object, System.Object[])' method, 
and this method cannot be translated into a store expression.

Я также мог бы попытаться изменить метод поиска:

public IQueryable<T> Search<T>(Expression<Func<T, bool>> predicate) 
    where T : IRunElement
{
    return _unitOfWork.ObjectSet<T>().AsExpandable().Where(predicate);
}

Ноконечно, это не скомпилируется, потому что IRunElement не является EntityObject, а ObjectSet ограничивает T как класс.

Последняя возможность - просто сделать все параметры и возвращаемые значения IEnumerable:

public IEnumerable<T> SubsetByUser<T>(IEnumerable<T> set, User user) 
    where T : EntityObject
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GetType().GetProperty("GUID").GetValue(arc,null),
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

Это также работает, но, опять же, не позволяет нам откладывать создание экземпляров до конца.

Так что, похоже, я мало что могу сделать, чтобы сделать эту работу, не создавая все экземпляры как IEnumerable, а затемвозвращая его с помощью AsQueryable (). Есть ли какой-нибудь способ, которым я могу собрать это вместе, что мне не хватает?

1 Ответ

2 голосов
/ 03 февраля 2012
Естественным ответом на это является фактическое определение метода SubsetByUser () для использования ограничения со свойством GUID: ... Но это не работает. Я использую LinqKit и метод Expandable () приводит к: System.NotSupportedException: невозможно преобразовать тип 'Oasis.DataModel.Arc' в введите 'Oasis.DataModel.Interfaces.IHaveMetadata'. LINQ to Entities поддерживает только типы примитивов модели данных Entity

Вы очень близки с этим. Вы можете сделать это, если используете ExpressionVisitor, который удаляет все ненужные приведения (приведения к базовому типу или реализованному интерфейсу), которые генерируются автоматически.

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : IHaveMetadata
{
    Expression<Func<T, Guid>> GetGUID = arc => arc.GUID;
    GetGUID = (Expression<Func<T, Guid>>)RemoveUnnecessaryConversions.Instance.Visit(GetGUID);
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
        GetGUID,
        meta => meta.ElementGUID,
        (arc, meta) => arc);
}

public class RemoveUnnecessaryConversions : ExpressionVisitor
{
    public static readonly RemoveUnnecessaryConversions Instance = new RemoveUnnecessaryConversions();

    protected RemoveUnnecessaryConversions() { }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert
            && node.Type.IsAssignableFrom(node.Operand.Type))
        {
            return base.Visit(node.Operand);
        }
        return base.VisitUnary(node);
    }
}

В качестве альтернативы, создайте дерево выражений вручную, используя функции Expression.*, чтобы можно было не включать приведение в первую очередь.

...