假设我对某些服务方法有以下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);
如果因子分解代码是过滤器的谓词,那么这一点尤为有趣。
所以我的问题是:
答案 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,
};
}