【问题标题】:Linq Parsing Error when trying to create seperation of concerns尝试创建关注点分离时出现 Linq 解析错误
【发布时间】:2011-12-07 19:19:19
【问题描述】:

我正处于重构周期的中间,我转换了一些过去看起来像这样的扩展方法:

   public static IQueryable<Family> FilterOnRoute(this IQueryable<Family> families, WicRoute route)
   {
        return families.Where(fam => fam.PODs
            .Any(pod => pod.Route.RouteID == route.RouteID));
    }

像这样更流畅的实现:

public class SimplifiedFamilyLinqBuilder
{
    private IQueryable<Family> _families;

    public SimplifiedFamilyLinqBuilder Load(IQueryable<Family> families)
    {
        _families = families;
        return this;
    }

    public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
    {
        _families = _families.Where(fam => fam.PODs
            .Any(pod => pod.Route.RouteID == route.RouteID));
        return this;
    }
    public IQueryable<Family> AsQueryable()
    {
        return _families;
    }
}

我可以这样称呼:(注意这是使用 Linq-to-Nhibernate)

 var families =
            new SimplifiedFamilyLinqBuilder()
            .Load(session.Query<Family>())
            .OnRoute(new WicRoute() {RouteID = 1})
            .AsQueryable()
            .ToList();

这会产生以下 SQL,这对我来说很好:(值得注意的是上面的 Linq 正在被转换为 SQL 查询)

select ... from "Family" family0_ 
where exists (select pods1_.PODID from "POD" pods1_ 
inner join Route wicroute2_ on pods1_.RouteID=wicroute2_.RouteID
where family0_.FamilyID=pods1_.FamilyID
and wicroute2_.RouteID=@p0);
@p0 = 1 

我的下一个重构工作是将处理孩子的查询部分移动到另一个类,如下所示:

public class SimplifiedPODLinqBuilder
{
    private IQueryable<POD> _pods;

    public SimplifiedPODLinqBuilder Load(IQueryable<POD> pods)
    {
        _pods = pods;
        return this;
    }

    public SimplifiedPODLinqBuilder OnRoute(WicRoute route)
    {
        _pods = _pods.Where(pod => pod.Route.RouteID == route.RouteID);
        return this;
    }
    public IQueryable<POD> AsQueryable()
    {
        return _pods;
    }
}

将 SimplifiedFamilyLinqBuilder 更改为:

public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
    _families = _families.Where(fam => 
        _podLinqBuilder.Load(fam.PODs.AsQueryable())
        .OnRoute(route)
        .AsQueryable()
        .Any()
    );
    return this;
}

只有我现在得到这个错误:

Remotion.Linq.Parsing.ParserException:无法解析表达式“值(Wic.DataTests.LinqBuilders.SimplifiedPODLinqBuilder)”,因为它具有不受支持的类型。只能解析查询源(即实现 IEnumerable 的表达式)和查询运算符。

我开始在 SimplifiedPODLinqBuilder 上实现 IQueryable(因为这似乎比实现 IEnumberable 更合乎逻辑)并认为这样做会很聪明:

public class SimplifiedPODLinqBuilder : IQueryable
{
    private IQueryable<POD> _pods;

    ...

    public IEnumerator GetEnumerator()
    {
        return _pods.GetEnumerator();
    }

    public Expression Expression
    {
        get { return _pods.Expression; }
    }

    public Type ElementType
    {
        get { return _pods.ElementType; }
    }

    public IQueryProvider Provider
    {
        get { return _pods.Provider; }
    }
}

只是为了得到这个异常(显然 Load 没有被调用并且 _pods 为空):

System.NullReferenceException : 对象引用未设置为对象的实例。

有没有办法让我重构这段代码,将其正确解析为将转到 SQL 的表达式?

【问题讨论】:

    标签: linq linq-to-nhibernate fluent


    【解决方案1】:

    fam =&gt; _podLinqBuilder.Load(fam.PODs.AsQueryable() 部分永远不会起作用,因为 linq 提供程序会尝试将其解析为 SQL,为此它需要在 =&gt; 之后映射 Family 的成员,或者可能是映射的用户定义函数但我不知道 Linq-to-Nhibernate 是否支持这一点(我从未真正使用过它,因为我仍然怀疑它是否可以投入生产)。

    那么,你能做什么呢?

    说实话,我更喜欢扩展方法。您切换到有状态的方法,它与 linq 的无状态范式不能很好地混合。所以你可以考虑回溯你的步骤。

    另一种选择:.Any(pod =&gt; pod.Route.RouteID == route.RouteID)); 中的表达式可以参数化(.Any(podExpression),与

    OnRoute(WicRoute route, Expression<Func<POD,bool>> podExpression)
    

    (伪代码)。

    希望这有任何意义。

    【讨论】:

    • 感谢您抽出宝贵时间回复。我希望有一种方法可以通知提供者如何处理这个表达式 - 但似乎即使有一种方法 - 它可能不值得付出努力(毕竟 - 扩展方法工作得很好)。我发现扩展方法“有味道”——尤其是当它们封装的 linq 表达式对我的业务逻辑如此核心时,这难道不是遵循 SOLID 原则的首要条件吗?让这个重要的代码不是我的 IOC 的一部分,而不是我可以扩展的类或我可以实现的接口,感觉不对。
    • 在我采用 Fluent 方法之前我正在考虑的另一个选择是让扩展类仅依赖于某种策略(由 IOC 提供)...public static IQueryable&lt;Family&gt; FilterOnRoute(this IQueryable&lt;Family&gt; families, WicRoute route) { return _someStrategy.FilterOnRoute(families, route); //OR return families.Where(_someStrategy.FilterOnRouteExpression); } 在一天结束时 -用一种不太实用的语言传递函数感觉需要做很多工作
    • 无状态方法中的业务逻辑对我来说完全没问题。策略和 IoC 听起来也不错。也许这就是我们对像 C# 这样的多范式语言的奢侈问题:太多的选择。但有一件事通常是正确的:无状态优于有状态。
    • 随着语言(和框架)的成熟 - 我们当然有很多选择 这里的期望是什么 - 我是否将问题标记为已回答,以便您获得时间的信任 - 我不觉得就像我在回答 (b/c) 中的问题一样,必须有一种方法可以使表达式可解析 - 但你是唯一一个回答过的人,并且可以说是我将得到的最接近的答案......我是否将其标记为已回答 - 我试图至少给你一票......但我不够“特权”:(
    • 这取决于你,没有难过的感觉:)。你可以等几天看看别人有没有好主意。
    【解决方案2】:

    您需要将您打算调用的方法与您打算翻译的表达式分开

    这很好,您希望每个方法都运行。它们返回一个实现IQueryable&lt;Family&gt; 的实例并对该实例进行操作。

    var families = new SimplifiedFamilyLinqBuilder()
      .Load(session.Query<Family>())
      .OnRoute(new WicRoute() {RouteID = 1})
      .AsQueryable()
      .ToList(); 
    

    这不好。你不希望 Queryable.Where 被调用,你希望它是一个可以翻译成 SQL 的表达式树。但是 PodLinqBuilder.Load 是该表达式树中的一个节点,无法转换为 SQL!

    families = _families
      .Where(fam => _podLinqBuilder.Load(fam.PODs.AsQueryable())
      .OnRoute(route)
      .AsQueryable()
      .Any();
    

    您不能在 Where 表达式中调用 .Load(它不会转换为 sql)。

    您不能在 Where 表达式之外调用 .Load(您没有 fam 参数)。


    以“关注点分离”的名义,您将查询构造方法查询定义表达式混合在一起。 LINQ 的集成特性鼓励您尝试这种行不通的事情。


    考虑制作表达式构造方法而不是查询构造方法

    public static Expression<Func<Pod, bool>> GetOnRouteExpr(WicRoute route)
    {
      int routeId = route.RouteID;
      Expression<Func<Pod, bool>> result = pod => pod.Route.RouteID == route.RouteID;
      return  result;
    }
    

    调用者:

    Expression<Func<Pod, bool>> onRoute = GetOnRouteExpr(route);
    families = _families.Where(fam => fam.PODs.Any(onRoute));
    

    使用这种方法,现在的问题是 - 我如何流畅地将我的饰品挂在表情树上?

    【讨论】:

    • 大卫感谢您的出色回应和彻底性...我发现自己在您的最后陈述中是我开始的地方...我如何流利地做到这一点...显然这是一个反问-我我将在我的脑海中反复思考这个问题,看看我是否能想到一种简单、直观和可重用的方法。这是一种实验/概念证明,我真的很喜欢直观且可重用的输出 - 遗憾的是我在简单方面失败了。
    猜你喜欢
    • 1970-01-01
    • 2016-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-02
    • 2022-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多