2014-01-10 2 views
2

Допустим, у меня есть следующий Linq2Entities запрос для некоторого метода обслуживания:Факторинговые из выражений в LINQ запросов

public IQueryable<CustomerProjection>() 
{ 
    return 
     from i in this.DbContext.Customers 
     select new CustomerProjection() 
     { 
      Reference = new EntityReference() 
      { 
       Id = i.Id, 
       Name = i.Name 
      } 
     } 
} 

Все мои модели объектов, в том числе клиентов, все поддерживают следующий интерфейс:

interface INamedEntity 
{ 
    String Name { get; set; } 
    Guid Id { get; set; } 
} 

так что в принципе у меня возникает соблазн провести рефакторинг:

public IQueryable<CustomerProjection>() 
{ 
    return 
     from i in this.DbContext.Customers 
     select new CustomerProjection() 
     { 
      // I want something something like this: 
      Reference = GetReference(i) 
     } 
} 

Очевидно, я не могу наивно определить GetRefe Renče таким образом:

public EntityReference GetReference<E>(E i) 
    where E : INamedEntity 
{ 
    return new EntityReference() 
    { 
     Id = i.Id, 
     Name = i.Name, 
    }; 
} 

Что мне нужно вынесем логика создать выражение для запроса, а не как EntityReference непосредственно. Итак, давайте определим его таким образом:

public Expression<Func<INamedEntity, EntityReference>> GetReferenceExpression() 
    where E : INamedEntity 
{ 
    return i => new EntityReference() 
    { 
     Id = i.Id, 
     Name = i.Name, 
    }; 
} 

Это правильно отводит логику. Тем не менее, я не могу использовать его в основном запросе:

public IQueryable<CustomerProjection>() 
{ 
    return 
     from i in this.DbContext.Customers 
     select new CustomerProjection() 
     { 
      // Something like "Invoke" doesn't exist! 
      Reference = GetReferenceExpression().Invoke(i) 
     } 
} 

мне нужно что-то вроде этого метода «Invoke» расширения поддерживаемых Linq2Entities, чтобы помочь мне на самом деле использовать мою преобразованную выход логику.

Обратите внимание, что это примерный сценарий со сниженной сложностью. Я понимаю, что факторинг в этом простом экземпляре действительно не нужен, но у меня были случаи более сложные, когда я хотел бы сделать что-то подобное.

Кроме того, есть второй, связанный сценарий, в котором не только хочет использовать преобразованная выход кода в более чем один запрос, но оценить его непосредственно:

GetReferenceExpression().Compile()(myEntity); 

Это особенно интересно, если factored-out code является предикатом для фильтра.

Так что мой вопрос:

  1. Поддерживает ли Linq2Entities что-то вроде этого? Если да, то как?
  2. Есть ли другой провайдер linq независимый способ разложения выражений?
+0

Я не понимаю ваше обновление. Вы можете по-прежнему фильтровать «правильные цели», где x.Entity.Property1 == value' – Aducci

+0

@Aducci Я вернул обновление - я бы принял ответ, если он не подведет на меня работу.Я думаю, что вы подход, как правило, является хорошим решением для такого рода проблем, и я должен задать другой вопрос, если я хочу получить другой. – John

+0

Примечание для меня и других: мне нужно проверить linqkit, linq для сущностей и linq для sql-хелперной библиотеки, которая, как представляется, предназначена для решения этой проблемы - у нее даже есть метод расширения, называемый «Invoke» для этой пусковой. – John

ответ

1

Вы можете сделать это:

Интерфейсы:

public interface IReference 
{ 
    int ID { get; } 
    string Name { get; } 
} 

Бетонные классы, реализующие интерфейсы

public class Reference : IReference 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

public class ReferenceAndEntity<T> 
{ 
    public Reference Reference { get; set; } 
    public T Entity { get; set } 
} 

IQueryable

public static IQueryable<ReferenceAndEntity<T>> GetReferenceAndEntityQuery<T>(IQueryable<T> set) where T: class, IReference 
{ 
    var query = from x in set 
       select new ReferenceAndEntity<T>() 
       { 
       Reference = new Reference() 
       { 
        ID = x.ID, 
        Name = x.Name 
       }, 
       Entity = x 
       }; 
    return query; 
} 

Использование

using(DbContext context = new Context()) 
{ 
    var query = from x in GetReferenceAndEntityQuery(context.dataSet) 
       select new 
       { 
       Reference = x.Reference, 
       EntityProperty1 = x.Entity.Property1, 
       }; 
} 
+1

Это хороший ответ, как я задал свой вопрос, но только потому, что слишком сильно уменьшил пример. Я сделаю пример более реалистичным, чтобы подобное решение больше не применялось. – John

+0

Я подумал об этом немного больше, и этот подход будет работать в большем количестве случаев, чем я думал. Я не могу заставить его работать. EF говорит мне: «Невозможно отбросить тип« MonkeyBusters.MonkeyManage.Web.Models.Customer », чтобы набрать« MonkeyBusters.MonkeyManage.Web.Models.INamedEntity ». LINQ to Entities поддерживает только приведение типов примитивов или перечислений EDM». (INamedEntity - это то, что вы назвали IReference). Я не пробовал точно, что вы сделали: мне нужен GetReferenceAndEntityQuery для получения произвольного IQuerable s вместо DbSet s. Я не думаю, что Entity Framework понравится. – John

+0

@John, я удалил дополнительный интерфейс, убедитесь, что вы возвращаете конкретный класс (а не интерфейс). Переход с DbSet на IQueryable прекрасен. Если вы все еще сталкиваетесь с ошибкой, напишите свой код – Aducci

2

Вы можете использовать поставщика LINQ прокси, который сидит перед поставщиком LINQ EF в. Вы можете сделать любое переписывание, которое вы хотите в этом месте.

Я сделал это. Это много работы, но это, безусловно, можно сделать. Можно, например, сделать ваш провайдер переписать так:

Expression<Func<MyEntity, bool>> someFilter = e => e.SomeProperty == 1234; 
... 
from e in db.Entities 
where MyCustomLINQProvider.CallExpression(someFilter, e) 
select e 

к этому:

from e in db.Entities 
where e.SomeProperty == 1234 
select e 

который является приемлемым для поставщика LINQ EF в. MyCustomLINQProvider.CallExpression будет помощником, который никогда не вызывается во время выполнения. Это просто как маркер вашего механизма перезаписи, чтобы встроить данное выражение. Без этого помощника код не будет компилироваться.

Для этого вам необходимо реализовать пользовательский IQueryable. Этот интерфейс определяется как:

public interface IQueryable : IEnumerable 
{ 
    Type ElementType { get; } 
    Expression Expression { get; } 
    IQueryProvider Provider { get; } 
} 

public interface IQueryProvider 
{ 
    IQueryable CreateQuery(Expression expression); 
    IQueryable<TElement> CreateQuery<TElement>(Expression expression); 
    object Execute(Expression expression); 
    TResult Execute<TResult>(Expression expression); 
} 

В IQueryProvider.Execute вы выполняете перезапись и передать запрос на EF-х IQueryProvider.


Вы могли бы сделать MyCustomLINQProvider.CallExpression расширение, так что это менее неудобно использовать:

static bool Call<TTarget, TArg0>(
    this Expression<Func<TArg0, TReturn>> target, TArg0 arg0) { 
throw new NotSupportedException("cannot call this statically"); 
} 
... 
where someFilter.Call(e) //looks almost like a func 

Вам нужен слой выше EF абстрагироваться от поставщика LINQ:

ObjectContext objectContext; 
IQueryable<T> Query<T>() { 
return new MyCustomQueryable<T>(objectContext.CreateObjectSet<T>()); 
} 

Так DON» t спросить EF для запроса, спросите свой код, чтобы вы могли передать прокси.

В вашем переписывающем устройстве вы не должны много делать. Вы ищете правильный шаблон и просто замените предикат ParameterExpression тем, что было передано как аргумент Call. Вот эскиз:

protected virtual Expression VisitMethodCall(MethodCallExpression node) 
{ 
    if (IsCall(node)) { 
    var expressionToInline = GetExpressionToInline(node.Arguments[0]); 
    var arg0 = node.Arguments[1]; 
    var parameter = expressionToInline.Parameters[0]; 
    var predicateExpression = ReplaceExpression(original: expressionToInline.Body, toReplace: parameter, replaceWith: arg0); 
    return predicateExpression; 
    } 

    return base.VisitMethodCall(node); 
} 

Там пример кода для ReplaceExpression доступны в Интернете.

+0

Это то, на что я надеялся, за исключением бит «большой работы». Я просто написал еще один вопрос, чтобы получить такой ответ, но я думаю, что сейчас просто отбрось его ... – John

+0

@ Джон не стесняйтесь задавать мне вопросы. Я не всегда пишу полный ответ, который я имею в виду, потому что я не знаю, что конкретно интересует ОП и будет ли он ценить его. Эта работа или свободное время для вас? Если это свободное время, сделайте это всеми средствами. Написание выражающего переписывающего - это очень весело. Сгибает ваш разум и делает вас лучше. – usr

+0

Это работа и письмо провайдера linq не были бы экономичными для меня, хотя я сам занят и не всегда делаю самую экономичную вещь. Я играл с linq-провайдерами и действительно понимаю, что происходит. Я надеялся, что в этом случае уже было что-то, о чем я не знал. Например, может существовать метод расширения «Invoke» в некотором секретном пространстве имен, CallExpression которого EF будет интерпретировать как такую ​​оценку. Вероятно, более реалистично взломать EF и получить одобрение патча. – John

Смежные вопросы