如何为装饰器实现自定义LINQ Provider
本文关键字:实现 自定义 LINQ Provider | 更新日期: 2023-09-27 18:18:20
我有一个项目,它允许通过装饰器和接口实现一些计算属性和业务逻辑,这些装饰器和接口控制对EF Code第一层的所有访问。我想通过oData公开这个业务逻辑层,并允许标准的IQueryable功能来过滤、排序和页面。我需要查询应用于db级别,而不仅仅是由于各种原因通过Linq到对象查询产生IEnumerable。
我的类结构看起来像LogicClass(Repository)> Interface> Decorator> Poco。类看起来像这样:
public class PeopleLogicLayer
{
// ... business and query logic ...
// basic query used internally
private System.Linq.IQueryable<PersonEfPoco> GetQuery()
{
if (this.CurrentQuery == null) this.ResetQuery();
var skipQuantity = (this.Page <= 1) ? 0 : (this.Page - 1) * this.PageSize;
return this.CurrentQuery.Skip(skipQuantity)
.Take(this.PageSize)
.AsQueryable();
}
}
public interface IPerson
{
int Id { get; set; }
String FirstName { get; set; }
String LastName { get; set; }
String FullName { get; }
}
public class PersonEfPoco
{
public int Id { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
public class PersonDecorator : IPerson
{
private PersonEfPoco _person;
public PersonDecorator(PersonEfPoco person)
{
this._person = person;
}
public int Id
{
get { return this._person.Id; }
set { this._person.Id = value; }
}
public String FirstName
{
get { return this._person.FirstName; }
set { this._person.FirstName = value; }
}
public String LastName
{
get { return this._person.LastName }
set { this._person.LastName = value }
}
public String FullName
{
get { return $"{this._person.FirstName} {this._person.LastName}"; }
}
}
我想要做的是:
List<IPerson> peopleNamedBob =
from o in (new PeopleHiddenBehindBusinessLogic()) where o.FirstName == "Bob" select o;
或
List<IPerson> peopleNamedBob =
(new PeopleHiddenBehindBusinessLogic()).Where(o => o.FirstName == "Bob").ToList();
这是一种过度简化。通过'select new PersonDecorator(o)'来进行查询内转换是不可能的,因为在decorator层中有复杂的逻辑,这是在那里处理的,我们不想允许直接访问EF层,而是更愿意将查询保留在decorator的更抽象的层上。
我考虑过从零开始实现一个自定义Linq提供程序,就像这里提到的那样。然而,这篇文章非常过时,我认为在过去的5年里有更好的方法。我找到了"重灵"听起来很有潜力。然而,当我搜索关于re-linq的教程时,没有太多的东西可以找到。
从我可以收集到的,高级步骤是创建一个访问者来替换查询的主题,并转换过滤器表达式以匹配poco(如果可以,大多数属性名称将匹配),并将其传递给EF。然后保存与EF Poco不兼容的表达式,以便稍后过滤最终的装饰集合。(现在故意忽略分页的复杂性) 我最近发现了支持Linq的流体方法,但我仍然缺乏关于如何分解'Where'表达式的信息,以便在PersonEfPoco
上为IPerson
使用过滤器。
这让我想到了我的选择。
- 像这样完全自定义Linq提供程序
- 使用re-linq -可以使用帮助查找教程
- 或者最近的Linq是否提供了更精确的实现方法
那么什么是最新的方法呢?
re-linq非常适合实现LINQ提供程序,这些提供程序可以将查询转换为另一种表示形式,例如SQL或其他查询语言(免责声明:我是原作者之一)。你也可以用它来实现一个提供程序,"只是"想要一个查询模型,比c#编译器生成的Expression
AST更容易理解,但是如果你真的需要你的结果看起来很像原来的CC_4 AST,你的经验可能会有所不同。
关于重新链接的资源,有(过时的,但仍然很好)CodeProject示例,我的旧博客和邮件列表。
对于您的场景,我想建议第四个选项,它可能比前两个更简单(不,当前LINQ不提供更简单的提供程序实现方法):提供您自己版本的LINQ查询操作符方法。
。,创建一个DecoratorLayerQuery<...>
类,虽然不实现IEnumerable<T>
或IQueryable<T>
,但它定义了您需要的查询操作符(Where
、Select
、SelectMany
等)。然后可以在实际数据源上构造一个底层LINQ查询。因为c#将使用它找到的任何Where
, Select
等方法,这将与使用"真正的"枚举一样好。
我的意思是:
public interface IDecoratorLayerQuery<TDecorated>
{
IDecoratorLayerQuery<TDecorated> Where (Expression<Func<TDecorated, bool>> predicate);
// etc.
}
public class DecoratorLayerQuery<TDecorated, TUnderlying> : IDecoratorLayerQuery<TDecorated>
{
private IQueryable<TUnderlying> _underlyingQuery;
public DecoratorLayerQuery(IQueryable<TUnderlying> underlyingQuery)
{
_underlyingQuery = underlyingQuery;
}
public IDecoratorLayerQuery<TDecorated> Where (Expression<Func<TDecorated, bool>> predicate)
{
var newUnderlyingQuery = _underlyingQuery.Where(TranslateToUnderlying(predicate));
return new DecoratorLayerQuery<TDecorated, TUnderlying> (newUnderlyingQuery);
}
private Expression<Func<TUnderlying, bool>> TranslateToUnderlying(Expression<Func<TDecorated, bool>> predicate)
{
var decoratedParameter = predicate.Parameters.Single();
var underlyingParameter = Expression.Parameter(typeof(TUnderlying), decoratedParameter.Name + "_underlying");
var bodyWithUnderlyingParameter = ReplaceDecoratedItem (decoratedParameter, underlyingParameter, predicate.Body);
return Expression.Lambda<Func<TUnderlying, bool>> (bodyWithUnderlyingParameter, underlyingParameter);
}
private Expression ReplaceDecoratedItem(Expression decorated, Expression underlying, Expression body)
{
// Magic happens here: Implement an expression visitor that iterates over body and replaces all occurrences with _corresponding_ occurrences of _underlying_.
// This will probably involve translating member expressions as well. E.g., if decorated is of type IPerson, decorated.FullName must instead become
// the Expression equivalent of 'underlying.FirstName + " " + underlying.FullName'.
}
public List<TDecorated> ToList() // And AsEnumerable, AsQueryable, etc.
{
var projection = /* construct Expression that transforms TUnderlying to TDecorated here */;
return _underlyingQuery.Select(projection).ToList();
}
}
public static class DecoratorLayerQueryFactory
{
public static IDecoratorLayerQuery<TDecorated> CreateQuery<TDecorated>()
{
var underlyingType = /* calculate underlying type for TDecorated here */;
var queryType = typeof (DecoratorLayerQuery<,>).MakeGenericType (typeof (TDecorated), underlyingType);
var initialSource = DbContext.Set(underlyingType);
return (IDecoratorLayerQuery<TDecorated>) Activator.CreateInstance (queryType, initialSource);
}
}
var exampleQuery =
from p in DecoratorLayerQueryFactory.CreateQuery<IPerson>
where p.FullName == "John Doe"
select p.FirstName;
TranslateToUnderlying
和ReplaceDecoratedItem
方法是这种方法中真正的困难,因为它们需要知道如何(并生成表达式)将程序员编写的内容转换为EF可以理解的内容。作为扩展版本,它们还可能提取一些查询内容以便在内存中执行。然而,这是你努力的本质复杂性:)
当你需要支持子查询时,一些额外的(IMO意外的)复杂性会引起它的难看的头,例如,一个包含另一个查询的Where
谓词。在这些情况下,我建议看看re-linq是如何检测和处理这种情况的。如果您可以避免此功能,我建议您这样做。