C# 在导航属性中封装LINQ查询以便重用?
我首先在SQL Server中使用实体框架代码,域实体与此类似:C# 在导航属性中封装LINQ查询以便重用?,c#,linq,entity-framework,C#,Linq,Entity Framework,我首先在SQL Server中使用实体框架代码,域实体与此类似: public class Item { public ICollection<ItemLocation> ItemLocations { get; set; } } 如果加载整个item对象,此属性将按预期工作: var item = (from i in db.Items select i).FirstOrDefault(); Console.WriteLine(item.Location.Name); 但是
public class Item
{
public ICollection<ItemLocation> ItemLocations { get; set; }
}
如果加载整个item对象,此属性将按预期工作:
var item = (from i in db.Items select i).FirstOrDefault();
Console.WriteLine(item.Location.Name);
但是,我不能在需要返回匿名类型的LINQ查询中使用此选项,如下所示:
var items = from i in db.Items
select new
{
ItemId = i.ItemId,
LocationName = i.Location.Name
};
相反,我每次都必须使用完整查询:
var items = from i in db.Items
select new
{
ItemId = i.ItemId,
LocationName = i.ItemLocations.Where(x => x.IsActive).Select(x => x.Location).FirstOrDefault().Name
};
理想情况下,我希望保留在一个位置(如属性)检索项目位置的逻辑,而不必将它们分散到所有位置
实现这一目标的最佳方式是什么 因此,首先,如果我们希望能够将此子查询与另一个查询相结合,那么我们需要将其定义为
表达式
对象,而不是C#code。如果它已经编译成IL代码,那么查询提供程序无法检查它以查看执行了哪些操作并将其转换为SQL代码。创建表示此操作的表达式
非常简单:
public static readonly Expression<Func<Item, ItemLocation>> LocationSelector =
item => item.ItemLocations.Where(x => x.IsActive)
.Select(x => x.Location)
.FirstOrDefault();
在内部,这只是将第二个表达式的参数的所有实例替换为第一个表达式的主体;代码的其余部分只是确保整个过程中只有一个参数,并将结果包装回新的lambda。此代码取决于将一个表达式的所有实例替换为另一个表达式的能力,我们可以使用:
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);
}
}
现在我们有了Combine
方法,我们需要做的就是调用它:
db.Items.Select(Item.LocationSelector.Combine((item, location) => new
{
ItemId = item.ItemId,
LocationName = location.Name
}));
瞧
如果需要,我们可以打印调用Combine
生成的表达式,而不是将其传递给Select
。这样,它会打印出:
param => new <>f__AnonymousType3`2(ItemId = param.ItemId,
LocationName = param.ItemLocations.Where(x => x.IsActive)
.Select(x => x.Location).FirstOrDefault().Name)
param=>新的f__匿名类型3`2(ItemId=param.ItemId,
LocationName=param.ItemLocations.Where(x=>x.IsActive)
.Select(x=>x.Location).FirstOrDefault().Name)
(我自己加的空格)
这正是您手动指定的查询,但是在这里,我们使用现有的子查询,而不需要每次都键入它。对此我确实感到困惑。我建议调试这两个版本,以确保无论是直接访问还是通过
Location
属性访问ItemLocations
都是相同的。这可能是一个缓存问题,不过…@Bobson它目前无法正常工作。在位置
属性中完成的查询已编译为IL代码。查询提供程序不知道如何将其解析为SQL代码;它看到的只是一个不映射到数据库列的Location
属性。谢谢。我希望有一个更简单的解决方案,但这看起来是可行的。
db.Items.Select(Item.LocationSelector.Combine((item, location) => new
{
ItemId = item.ItemId,
LocationName = location.Name
}));
param => new <>f__AnonymousType3`2(ItemId = param.ItemId,
LocationName = param.ItemLocations.Where(x => x.IsActive)
.Select(x => x.Location).FirstOrDefault().Name)