IQueryable‹T› с EntityObject с использованием обобщений и интерфейсов (возможно?)

У меня есть репозиторий поиска для 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(). Есть ли способ, которым я могу собрать это вместе, что мне не хватает?


person JohnMetta    schedule 02.02.2012    source источник
comment
Можно ли показать немного больше кода о том, как вы использовали эту функцию SubsetByUser (где вы бы вызвали ее из своего запроса). Пытаюсь сделать что-то подобное.   -  person AaronLS    schedule 01.06.2012
comment
@AaronLS, я больше не участвую в этом проекте, но в основном — IIRC, аргумент set — это возвращаемый набор из другого запроса. Так что это просто _subsetContainer.SubsetByUser‹Widget›(_container.Search‹Widget›(предикат), someUser);   -  person JohnMetta    schedule 15.06.2012


Ответы (1)


The natural response to this is to actually define the SubsetByUser() method to use a constraint with a GUID property: ... But this doesn't work. I'm using LinqKit and the Expandable() method results in: 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

Вы очень близки с этим. Вы можете сделать это, если используете 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.*, чтобы избежать включения приведения в первую очередь.

person Community    schedule 02.02.2012
comment
Это потрясающе. Именно то, что я хотел. Большое спасибо! - person JohnMetta; 03.02.2012
comment
@hvd Думаю, я пытаюсь сделать что-то подобное. Можете ли вы показать пример вызова SubsetByUser в запросе? Я смущен, так как это не метод расширения. Я пытаюсь создать метод, который может использоваться linq2entities: stackoverflow.com/questions/10826275/ - person AaronLS; 31.05.2012
comment
@AaronLS Вы бы использовали from x in SubsetByUser(...) ... select x - это не то, что вы пытаетесь сделать. Это функция, которая возвращает запрос. Вы пытаетесь вызвать функцию из запроса. (Тем не менее, с тех пор я обнаружил, что мой ответ излишне сложен и будет обновляться, когда я могу.) - person ; 31.05.2012
comment
@hvd Где объявляется SubsetByUser? В статическом классе? Метод не статичен? Я только понял, как использовать linqkit для объявления переменной локального выражения с именем asOfCurrent, чтобы я мог сделать .Where(asOfCurrent). Это шаг в правильном направлении. Поэтому я хочу сделать его немного более доступным по всему миру, а также иметь возможность принимать параметр (DateTime), подобно тому, как вышеприведенный параметр принимает пользовательский параметр. - person AaronLS; 01.06.2012
comment
@AaronLS Это могла быть статическая функция, за исключением _searcher, которую использует эта функция, которая (насколько я могу судить) специфична для этого вопроса. Я предполагаю, что это метод экземпляра в контексте. В вашем случае это может быть статический метод в любом классе, статическом или нестатическом, который вы хотите. - person ; 01.06.2012
comment
@hvd Ошибки объектов linq 2, не распознающих метод. Обычно это не проблема, потому что это будет связано с вызовом AsExpandable или с .Invoke, но я не могу понять, как заставить его работать здесь или что передать параметру set, поскольку он уже from x, но поскольку это не метода расширения первый параметр не является this и поэтому не получает автоматически текущий запрос. - person AaronLS; 01.06.2012