Linq解析在尝试创建关注点分离时出错

时间:2011-12-07 19:19:20

标签: linq linq-to-nhibernate fluent

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

   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:无法解析表达式'value(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为null):

  

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

有没有办法让我重构一下这个代码,它会正确地解析成一个将转到SQL的表达式?

2 个答案:

答案 0 :(得分:0)

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

那么,你能做什么?

说实话,我更喜欢扩展方法。你转而使用有状态的方法,它与linq的无状态范例不能很好地融合。所以你可以考虑回溯你的步骤。

另一种选择:.Any(pod => pod.Route.RouteID == route.RouteID));中的表达式可以是paremeterized(.Any(podExpression)

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

(伪代码)。

希望这有任何意义。

答案 1 :(得分:0)

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

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

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参数)。


在&#34;关注点分离&#34;的名称中,您将查询构造方法查询定义表达式混合在一起。 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));

采用这种方法,现在的问题是 - 如何从表达树中流畅地挂饰?