在Linq中,如何在多个级别的“包含子句”中添加类型检查?

时间:2018-03-19 22:01:22

标签: vb.net linq linq-to-sql entity-framework-6

这是模型:

Public Class Parent
    Public Property Name As String
    Public Property ID As Integer
    Public Property child1 As New List(Of Child1)
End Class

Public Class Child1
    Public Property ID As Integer
    Public Property Name As String
    Public Property Child2 As New List(Of Child2)
End Class

Public Class Child2
    Public Property ID As Integer
    Public Property Name As String
    Public Property Child3 As New List(Of Child3)
End Class

Public Class Child3
    Public Property ID As Integer
    Public Property Name As String
    Public Property Child4 As New List(Of Child4)
End Class

Public Class Child4
    Public Property ID As Integer
    Public Property Name As String
End Class

要在我一直在使用的查询中包含所有表:

TheDataContext.Parent.Include("Child1").Include("Child1.Child2").Include("Child1.Child2.Child3").Include("Child1.Child2.Child3.Child4").Where(some condition).ToList()

这太荒谬了,没有类型检查。如果我只需要深入一个儿童桌,我确实学到了以下作品:

TheDataContext.Parent.Include(NameOf(Child1)).Where(some condition).ToList()

但是如果我尝试在下一个子级别上应用'NameOf'技术,就像这样

.Include(NameOf(Child1.Child2))...

在运行时失败,说Parent没有声明名为Child1.Child2的导航属性,回想起来有意义,但我不知道如何解决这个问题。如果我使用

.Include(NameOf(Parent.Child1.Child2))...

在编辑器中失败,说对非共享成员的引用需要一个对象引用,我理解,但我不相信这是一个解决方案,我的模型不应该使用Shared。

这个SO Post在Include中建议一个lambda,一个Select包含另一个lambda,我在C#中理解,但在vb.net中.include似乎甚至不允许第一个lambda。编辑器中的以下内容失败:

.include(function(x) x.Child1) or anything else in the lambda

也许我错过了一个Imports,但是我只是不知道在vb.net中要做的语法在SO帖子中看起来如此优雅。

如何转换为所有级别的孩子的类型检查?

在应用以下建议和答案后添加:

不要忽视Harald Coppoolse的优秀答案,这就是我如何将基于字符串的.Include转换为带有类型检查的.Include。虽然这在技术上是我的问题的答案,正如Harald的答案所示,答案不一定是正确的答案,因为可能有一个重要的问题没有被问到。

TheDataContext.Parent.Include(Function(a) a.child1.Select(Function(b) b.child2.Select(Function(c) c.child3.Select(Function(d) d.child4)))).ToList()

1 个答案:

答案 0 :(得分:1)

我的回答是在C#中,但这个想法也适用于VB。我猜你得到了要点。

使用Select代替Include。选择是完全类型安全的

数据库查询的一个较慢的部分是从数据库管理系统到本地计算机的数据传输。明智的做法是将此数据仅限于您实际计划使用的数据。

您使用Include传输的方式比您使用的数据多。例如,您的Child1有一个Id。 Child1的所有Child2子女都有一个外键Child1Id,它等于Child1的Id。

因此,如果选择带有Id 4的Child1,并且此Child1有100个子节点,则所有这些子节点将具有等于4的外键Child1Id。因此,您将传递Child1的主键值101次,而您已经知道他们都将拥有相同的价值。

如果您使用Select代替Include,则只能选择您真正计划使用的属性。 Select的另一个副作用是它完全是类型安全的,这可以解决您的问题。

根据我使用Entity Framework的经验是,如果我打算更改获取的值,我只使用Include。 DbContext只能在获取后更改值(或者您必须使用变通方法)。

因此,如果您希望所有(或某些)父母与他们所有的孩子,GrandChildren等,并且您希望它类型安全,请执行以下操作:

var parentsWithTheirDescendants = myDbContext.Parents
    .Where(parent => ...)
    .Select(parent => new
    {
        // select only the properties you plan to use
        Id = parent.Id,
        Name = parent.Name,

        Children = parent.Child1s
            .Where(child1 => ...)           // only if you don't want all Children
            .Select(child1 => new
            {
                  // again: select only the properties you plan to use
                Id = child1.Id,
                Name = child1.Name

                // not needed:
                // ParentId = child1.ParentId

                GrandChildren = child1.Child2s
                    .Where(child2 => ...)
                    .Select(child2 => new
                    {   // etc.
                    })
                }),
            });

这完全是类型安全的。选择您没有的任何属性是不可能的。在限制范围内,您甚至可以创建新属性:

    .Select(child4 => new
    {
         FullName = child4.FirstName + child4.LastName,
         Age = (int)(Today - child4.BirthDay).TotalYears,
    });

顺便说一下,有一个Q ueryable.Include的类型安全版本,你不能在VB中使用它吗?

总结:

  

对于查询,请使用选择。仅选择您计划使用的属性
  如果您打算更改/删除提取的项目,请仅使用包含