使用条件来延迟加载Entity Framework的EntityCollection

6
在实体框架中(特别是EF 3.5,但如果在EF 4中存在,那么我有理由进行升级),是否可以仅惰性加载集合的一部分? 我可能也以错误的方式处理了这个问题,所以我愿意听取建议。我的表/实体类看起来类似于这样:
Person            PersonMeal           Meal
------    1---*   ----------   *---1   -----
ID                ID                   ID
...               PersonID             ...
                  MealID
                  Value
                  ...

我有一个通过存储过程使用Entity Framework检索到的Person对象列表。我有一个视图,一次只显示一个Meal,因此我只想要与该餐相关的信息。目前我的代码看起来像这样:

Function GetPersons() As List(Of Person)
    Dim personList = context.StoredProcedureCall(param1, param2, param3).ToList()
    personList.ForEach(Function(x) LazyLoadProperties(x))
    Return personList
End Function

' Work around function because VB lambdas don't take Sub's
Function LazyLoadProperties(ByVal person As Person) As Object
    If (Not person.PersonMeal.IsLoaded) Then
        person.PersonMeal.Load()
    End If
    Return Nothing
End Function

问题在于这会加载整个集合。虽然这是一个小集合,所以最坏的情况是我可以将其全部加载,然后删除除我需要的之外的所有内容,但这远非理想。而且我不确定是否可能在不触发修改集合事件的情况下完成此操作,因为它们本应该不在其中。

在你的问题中,Person是否与PersonMeal具有多对多关系,而PersonMealMeal也具有多对多关系?或者PersonMeal之间是多对多关系,而PersonMeal是连接表?如果是这样,就不应该为PersonMeal生成实体。我还注意到了一个Value字段。您能否稍微澄清一下您的数据结构?您的EDM截图以及数据库结构也可能会有所帮助。 - Yakimych
@ Yakimych 你是对的,这是 Person 和 Meal 之间的 M2M 关系,PersonMeal 是连接表/实体。 - Agent_9191
@Agent_9191 - 好的,那么在这种情况下应该只有一个PersonEntity表,但不生成实体。 Person实体应该有一个Meals集合,而Meal实体应该有一个People集合。您是从数据库生成模型还是在设计器中自己创建的?Value属性是什么(这可能是EF生成PersonMeal实体的原因)? - Yakimych
@Yakimych 这是从数据库生成的,而PersonMeal实体是必需的,因为它具有特定于组合的属性(食物类型、饮料、维生素等)。Value是列表中通用属性之一。问题是大多数数据视图仅需要一个人的1餐,正如我所说,人员列表是从存储过程生成的,因此链接LINQ Where子句无法工作。 - Agent_9191
@Agent_9191 - 好的,但是你的导航属性应该叫做PersonMeals而不是PersonMeal,因为它是一个集合。关于你的问题:"我有一个视图,每次只显示一餐,所以我只想要与那餐相关的信息"。你如何确定要加载和显示哪一餐? - Yakimych
@Yakimych 我在GetPersons()函数中有餐点的ID可用,因此我可以将其与给定的餐点ID或单独检索的餐点进行比较。理想情况下,我希望将该Meal.ID(或Meal本身)传递给EF,以便仅加载集合的那一部分。 - Agent_9191
3个回答

2

在这种情况下,您可以直接查询数据库来获取数据,而不是使用Load方法:

Function GetPersons() As List(Of Person)
    Dim personList = context.StoredProcedureCall(param1, param2, param3).ToList()

    Dim person As Person
    For Each person in personList
        person.PersonMeals = From pm in context.PersonMeals.Include("Meal")
                             Where pm.Person.Id == person.Id And pm.Meal.Id == Meal_ID
                             Take 1
    Next person

    Return personList
End Function

我假设person.PersonMeals是一个集合,否则您可以使用FirstOrDefault代替Take
在这个查询中,我们基本上选择所有的PersonMeals实体(连同Meal),其中人员ID作为循环中的当前人员,并且餐点ID为您所需。如果您的数据库没有损坏数据(具有相同的PersonID-MealID组合的多行),则将写入0或1个结果到您的PersonMeals属性。

这不是我所期望的那么优雅的解决方案,但它能够工作。 - Agent_9191

1

你的问题非常清晰:是否可能只惰性地加载集合的一部分,答案是否定的!EF1和EF4都不支持。顺便说一句,如果最后一餐是主题,请从这里开始查询!而不是检索人和他们的餐点,检索最后的餐点和与之相关的人。


仅供澄清,实体框架(EF)没有提供一个开箱即用的方法来部分地进行集合延迟加载。而延迟加载意味着 Meals 集合不会与 Person 一起加载,而是在 People 加载后的某个时间点进行加载。当然,你可以通过查询 Meals 来实现这一点。因此,我不会简单地说这是不可能的。 - Yakimych
为什么答案是否定的?我认为EF团队实现解决这个问题并不难。 - Acaz Souza

0

Yakimych的答案应该是可行的,但代码有一些错误。

正确语法应该是:

Private Function GetPersons() As List(Of Person)
    Dim personList As List(Of Person) = Context.StoredProcedureCall(param1, param2, param3).ToList()
    For Each p In personList
        Dim pId As Integer = p.Id
        p.PersonMeals = (From pm As PersonMeal In context.PersonMeals.Include("Meal")
                         Where (pm.Person.Id = pId And pm.Meal.Id = Meal_ID) Take 1).ToList
    Next
    Return personList
End Function

希望它有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接