C# 在编译查询中重用现有linq表达式

C# 在编译查询中重用现有linq表达式,c#,.net,linq-to-sql,expression,C#,.net,Linq To Sql,Expression,考虑以下代码,它提供了两种方法:一种是返回IQueryable,另一种是利用已编译的查询非常高效地返回与特定ID匹配的位置: public IQueryable<Location> GetAllLocations() { return from location in Context.Location where location.DeletedDate == null &

考虑以下代码,它提供了两种方法:一种是返回IQueryable,另一种是利用已编译的查询非常高效地返回与特定ID匹配的位置:

    public IQueryable<Location> GetAllLocations()
    {
        return from location in Context.Location
               where location.DeletedDate == null
                     && location.Field1 = false
                     && location.Field2 = true
                     && location.Field3 > 5
               select new LocationDto 
               {
                     Id = location.Id,
                     Name = location.Name
               }
    }


    private static Func<MyDataContext, int, Location> _getByIdCompiled;

    public Location GetLocationById(int locationId)
    {
        if (_getByIdCompiled == null) // precompile the query if needed
        {
            _getByIdCompiled = CompiledQuery.Compile<MyDataContext, int, Location>((context, id) => 

                (from location in Context.Location
                where location.DeletedDate == null
                      && location.Field1 = false
                      && location.Field2 = true
                      && location.Field3 > 5
                      && location.Id == id
                select new LocationDto {
                     Id = location.Id,
                     Name = location.Name
                })).First());
        }

        // Context is a public property on the repository all of this lives in
        return _getByIdCompiled(Context, locationId);
    }
我怎样才能在变量或函数中捕获它,并在多个已编译查询中重用它?到目前为止,我的尝试导致了一些错误,抱怨无法转换为SQL、不允许成员访问等等

更新:我可以问这个问题的另一个可能更好的方法如下:

考虑以下两个已编译的查询:

_getByIdCompiled = CompiledQuery.Compile<MyDataContext, int, LocationDto>((context, id) => 
      (from location in Context.Location // here
      where location.DeletedDate == null // here
            && location.Field1 = false // here
            && location.Field2 = true // here
            && location.Field3 > 5 // here
            && location.Id == id
      select new LocationDto { // here
          Id = location.Id, // here
          Name = location.Name
      })).First()); // here

_getByNameCompiled = CompiledQuery.Compile<MyDataContext, int, LocationDto>((context, name) => 
       (from location in Context.Location // here
     where location.DeletedDate == null // here
         && location.Field1 = false // here
         && location.Field2 = true // here
         && location.Field3 > 5 // here
         && location.Name == name
     select new LocationDto { // here
       Id = location.Id, // here
       Name = location.Name // here
     })).First()); // here
\u getByIdCompiled=CompiledQuery.Compile((上下文,id)=>
(来自上下文中的位置。位置//此处
其中location.DeletedDate==null//here
&&location.Field1=false//此处
&&location.Field2=true//此处
&&location.Field3>5//此处
&&location.Id==Id
选择新位置到{//此处
Id=location.Id,//此处
Name=位置。Name
})).First());//在这里
_getByNameCompiled=CompiledQuery.Compile((上下文,名称)=>
(来自上下文中的位置。位置//此处
其中location.DeletedDate==null//here
&&location.Field1=false//此处
&&location.Field2=true//此处
&&location.Field3>5//此处
&&location.Name==Name
选择新位置到{//此处
Id=location.Id,//此处
Name=位置。Name//此处
})).First());//在这里

所有标有
//的行在这里都是重复的非常不干的代码片段。(在我的代码库中,这实际上是30多行代码。)我如何将其分解并使其可重用?

因此,这整件事有点奇怪,因为
Compile
方法不仅需要将传递给每个查询操作符的
表达式
对象(
Where
Select
等)视为它可以理解的东西,但它需要将整个查询(包括所有运算符的使用)视为可以理解为
Expression
对象的内容。这几乎删除了作为选项的更传统的查询组合

这会变得有点乱;这比我真正想要的要多,但我看不到太多好的选择

我们要做的是创建一个方法来构造查询。它将接受一个
过滤器
作为参数,该过滤器将是一个
表达式
,表示某个对象的过滤器

接下来,我们将定义一个lambda,它看起来几乎与传递给
Compile
的内容完全相同,但有一个额外的参数。这个额外的参数将与我们的过滤器类型相同,它将表示实际的过滤器。我们将在整个lambda中使用该参数,而不是过滤器。然后,我们将使用
UseIn
方法将新lambda中第三个参数的所有实例替换为我们提供给该方法的
filter
表达式

以下是构造查询的方法:

private static Expression<Func<MyDataContext, int, IQueryable<LocationDto>>>
    ConstructQuery(Expression<Func<Location, bool>> filter)
{
    return filter.UseIn((MyDataContext context, int id,
        Expression<Func<Location, bool>> predicate) =>
        from location in context.Location.Where(predicate)
        where location.DeletedDate == null
                && location.Field1 == false
                && location.Field2 == true
                && location.Field3 > 5
                && location.Id == id
        select new LocationDto
        {
            Id = location.Id,
            Name = location.Name
        });
}
(这里的键入很混乱,我不知道如何给泛型类型赋予有意义的名称。)

以下方法用于用另一个表达式替换一个表达式的所有实例:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}
既然我们已经度过了这场血淋淋的混乱,那么简单的部分就来了
ConstructQuery
此时应该能够修改,以表示您的真实查询,而不会有太多困难

要调用此方法,我们只需提供希望应用于此查询更改的任何筛选器,例如:

var constructedQuery = ConstructQuery(location => location.Id == locationId); 

Linq语句必须以
select
group
子句结尾,这样您就不能剪切查询的一部分并将其存储在其他地方,但是如果您总是按照相同的四个条件进行筛选,则可以改为使用lambda语法,然后在新查询中添加任何附加的
where
子句

正如@Servy所指出的,您需要调用
First()
FirstOrDefault()
从查询结果中获取单个元素

IQueryable<Location> GetAllLocations()
{
    return Context.Location.Where( x => x.DeletedDate == null
                                     && x.Field1      == false
                                     && x.Field2      == true
                                     && x.Field3       > 5
                                 ).Select( x => x );
}

Location GetLocationById( int id )
{
    return ( from x in GetAllLocations()
             where x.Id == id
             select x ).FirstOrDefault();
}

//or with lambda syntax

Location GetLocationById( int id )
{
    return GetAllLocations()
               .Where( x => x.Id == id )
               .Select( x => x )
               .FirstOrDefault();
}
IQueryable GetAllLocations()
{
返回Context.Location.Where(x=>x.DeletedDate==null
&&x.Field1==false
&&x.Field2==true
&&x.3>5
).选择(x=>x);
}
位置GetLocationById(int id)
{
返回(来自GetAllLocations()中的x)
其中x.Id==Id
选择x).FirstOrDefault();
}
//或者使用lambda语法
位置GetLocationById(int id)
{
返回GetAllLocations()
.其中(x=>x.Id==Id)
.选择(x=>x)
.FirstOrDefault();
}

您在
GetAllLocations
GetLocationsById
中的身份投影是多余的。此外,“lambda语法”不是合适的术语。正确的术语是“方法语法”。谢谢,缺少First(),这只是将我的问题翻译成更容易发布的内容时的一个错误。这个例子就是如何链接IQueryables,我没有任何问题。你能看看我的更新吗?我想我可能把我的例子简化得太多了。实际上,我也在对DTO进行相当广泛的投影。您的示例向我展示了如何重用表达式,但实际上我正试图做的是相反的事情—重用“基本筛选器和投影”逻辑,仅从已编译的查询中使用。@ShaunRowan您只需创建两个静态
表达式
对象,一个用于过滤器,另一个用于对DTO的投影。如何在已编译的查询定义中应用投影表达式?@ShaunRowan您可以调用
Select
,并将表达式传递给它。似乎可以工作到:
private static expression\u projection=(location)=>new location{Id
var constructedQuery = ConstructQuery(location => location.Id == locationId); 
IQueryable<Location> GetAllLocations()
{
    return Context.Location.Where( x => x.DeletedDate == null
                                     && x.Field1      == false
                                     && x.Field2      == true
                                     && x.Field3       > 5
                                 ).Select( x => x );
}

Location GetLocationById( int id )
{
    return ( from x in GetAllLocations()
             where x.Id == id
             select x ).FirstOrDefault();
}

//or with lambda syntax

Location GetLocationById( int id )
{
    return GetAllLocations()
               .Where( x => x.Id == id )
               .Select( x => x )
               .FirstOrDefault();
}