C# 在编译查询中重用现有linq表达式
考虑以下代码,它提供了两种方法:一种是返回IQueryable,另一种是利用已编译的查询非常高效地返回与特定ID匹配的位置: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 &
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();
}