2011-12-28 60 views
5

下面是我经常遇到的情况:我有Parent和Child对象,并且Child对象有一个Parent属性。我想运行一个查询,得到的子对象,并加入每一个正确的父对象:什么是选择和组合对象与LINQ的正确方法?

Dim db = New DataContextEx() 

get the children, along with the corresponding parent 
Dim Children = From x In db.ChildTable 
       Join y In db.ParentTable 
       On x.ParentId Equals y.Id 
       Execute x.Parent = y  <-- pseudocode 
       Select x 

的伪代码显示我想要完成的任务:返回子对象X,但与之后的代码(假)执行语句执行。我可以想出很多方法来实现最终目标,但它们都有更多的代码行和/或创建临时对象或函数,我觉得它们不够优雅。 (注意,这是VB.NET语法,但它不是VB语法问题,因为AFAIK C#会有同样的问题。)

那么最干净的方法来做我想做的事情?人们询问我使用的是什么ORM,但这实际上是一个普通的vanilla LINQ问题;我没有试图将其转换为在服务器上运行的逻辑,我只是​​希望在查询在服务器上运行后,运行代码客户端的一些语法糖。

+0

如果x和y是表记录,代码中的x.Parent是什么? – Paul 2011-12-28 18:22:27

+0

如果您说明了您正在使用哪个提供商,它可能会有所帮助。 Linq-to-SQL,Linq-to-Entities等,此外如果您可能碰巧在使用EF Code First。 – 2011-12-28 18:24:10

+0

它们只是来自我的ORM的强类型对象,但实际上这同样适用于Linq To Objects。 – 2011-12-28 18:25:34

回答

2

这些建议,特别是@Paul's,是非常有帮助的,但我的最终版本是不同的,我要写它作为我自己的答案。

我采取了以下完全通用的扩展功能:

<Extension()> _ 
Public Function Apply(Of T)(ByVal Enumerable As IEnumerable(Of T), ByVal action As Action(Of T)) As IEnumerable(Of T) 
    For Each item In Enumerable 
     action(item) 
    Next 

    Return Enumerable 
End Function 

这是一样的ForEach,只不过它返回输入序列,而且我觉得这个名字应用明确指出有副作用效果。然后,我可以写我的查询为:

Dim Children = (
      From x In db.ChildTable 
      Join y In db.ParentTable 
      On x.ParentId Equals y.Id 
      ). 
      Apply(Sub(item) item.x.Parent = item.y). 
      Select(Function(item) item.x) 

当然,这将是更好,如果有一种方法来定制查询操作,所以我就不必使用lambda语法,但即便如此,这似乎很干净且可重复使用。再次感谢所有的帮助。

+1

为了改善这个问题,我建议修改'Apply'来操作一个单独的项目。然后,语法将变得更清晰:'选择x.Apply(Sub(c)c.Parent = y)'(这是@ Paul的答案的更通用版本) – 2011-12-28 22:20:32

+0

@ScottRippey:+1为好的建议。它确实看起来更清洁,并带走了副作用。我将尝试两种方式,看看哪种感觉更自然。 – 2011-12-29 14:08:47

+0

@ScottRippey:我这样做并决定保留两种Apply方法,因为它们在各种情况下都适用。再次感谢。 – 2012-02-27 21:41:04

11

您可以在投影结果时使用匿名类型。 C#示例:

var items = from x In db.ChildTable 
      join y In db.ParentTable on x.ParentId equals y.Id 
      select new { Child =x , Parent=y }; 

然后指定父属性。你

foreach(var item in items) 
{ 
    item.Child.Parent = item.Parent; 
} 

return items.Select(item => item.Child); 

可能还需要使用一些ORM解决方案,这不是滚动你自己的。

+0

这产生了一个IEnumerable(Of ),而不是IEnumerable(Of Child),这正是我想要做的。 – 2011-12-28 18:24:15

+1

好吧,我更新了代码。你在操作数据时最好使用foreach循环 – 2011-12-28 18:33:12

+4

@JoshuaFrank - 你试图将副作用(设置属性)引入到LINQ查询中。 LINQ(以及一般的函数式编程)并不是用来做这件事的。在选择或添加后续的foreach中创建一个新的实例(子类或匿名类型)。 – TrueWill 2011-12-28 18:36:20

2

你可以这样说:

添加的方法做你的逻辑,以返回你选择的查询做逻辑VB thisMe

class Child 
{ 
    public Child GetWithAssignedParent(Parent y) 
    { 
     this.Parent = y; 
     return this; 
    } 
} 

使用这种方法子类并返回子类的实例更新:

var children = from x In db.ChildTable 
join y In db.ParentTable on x.ParentId equals y.Id 
select x.GetWithAssignedParent(y); 

或使用Func键<>

var children = from x in children 
join y in parents on x.ParentId equals y.Id 
select 
    new Func<Child>(() => 
    { 
     x.Parent = y; 
     return x; 
    }).Invoke(); 
+0

+1为好的建议,我这样做了一段时间,但它并没有坐在我身上很好,因为(1)它需要修改类以添加效用函数的情况下,可能只是有用的一个地方; (2)在被调用的地方,没有深入研究这种方法并不明显。 – 2011-12-28 18:57:02

+0

你可以创建扩展方法,不要向原始类添加额外的代码..我不熟悉你的代码 - 但可能你会想出一个适当的方法名称,这将在你的具体情况下明确。 – Paul 2011-12-28 19:00:17

+0

或者您可以创建实用方法(静态方法),将父项和子项作为两个参数,将父项分配给子项的属性并返回更新后的子项。 – Paul 2011-12-28 19:02:00

0

这里可能是最简单的解决方案:

var children = from x in db.ChildTable 
       join y in db.ParentTable 
       on x.ParentId equals y.Id 
       // Assign the Parent to the Child: 
       let parent = (x.Parent = y) // <-- Inline assignment (with a dummy variable "parent") 
       select x; 

声明:此查询有直列分配修改状态 ...这两者都是不被认为是“最佳实践”。
但是,阅读,理解,维护和轻松生成正确的结果非常容易,所以我认为这是一个很好的解决方案。

此外,这只适用于C#,因为VB.NET没有内嵌分配。

+0

对,仅C#,但IMO也是不应该用C#编写的代码类型,因为它明显依赖于棘手的问题;我不想在一个月后看这个,并试图弄清楚它在做什么。 – 2011-12-28 20:32:16

+0

同意,但在这种情况下,一个简单的评论'//分配父对象:'足以自行记录代码。 其他解决方案('foreach'循环,扩展方法)肯定需要更多的时间来弄清楚,并且需要更多的代码来读取。 – 2011-12-28 22:13:00

0

如果您正在使用LinqToObjects,请使用此代码将父项分配给子项。

ILookup<int, Child> lookup = children.ToLookup(c => c.ParentId) 

foreach(Parent p in parents) 
{ 
    foreach(Child c in lookup[p.Id]) 
    { 
    c.Parent = p; 
    } 
} 

如果您正在使用LinqToSql,使用DataLoadOptions加载相关的记录。


如果您使用的是LinqToEntities,请使用.Include加载相关记录。

+0

正如我在我的编辑中提到的,我一般在Linq中寻求解决这个问题,而不仅仅是从db中拉出的对象。 – 2011-12-28 20:33:04

+0

@Joshua Frank,正如我在我的答案的前50%中所提到的那样,有一种简单的方法可以修改不在数据库中的对象。 – 2011-12-28 20:52:53

+0

简单的是,但非常详细,这是我试图避免的大部分。 – 2011-12-29 13:47:31

相关问题