在linq查询中分解表达式

时间:2014-01-10 17:40:25

标签: linq linq-to-entities

假设我对某些服务方法有以下Linq2Entities查询:

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

我的所有模型实体(包括Customer)都支持以下界面:

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)
        }
}

显然我不能用这种方式天真地定义GetReference:

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)
        }
}

我需要Linq2Entities支持的类似“Invoke”扩展方法,以帮助我实际使用我的分解逻辑。

请注意,这是一个复杂度降低的示例场景。我理解在这个简单的实例中考虑并不是真的有必要,但我希望我可以做类似的事情让案件更加复杂。

此外,还有第二种相关场景,其中不仅要在多个查询中使用分解代码,而且要直接对其进行评估:

GetReferenceExpression().Compile()(myEntity);

如果因子分解代码是过滤器的谓词,那么这一点尤为有趣。

所以我的问题是:

  1. Linq2Entities是否支持此类内容?如果是这样,怎么样?
  2. 是否有另一种linq提供者独立的分解表达式的方式?

2 个答案:

答案 0 :(得分:2)

您可以使用位于EF的LINQ提供程序前面的代理LINQ提供程序。你可以在那个地方做任何想要的重写。

我做到了这一点。这是很多工作,但肯定可以做到。例如,您可以让您的提供者重写:

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

这是EF的LINQ提供商可以接受的。 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>());
}

因此,不要向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的示例代码。

答案 1 :(得分: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,
              };
}