具有实体框架的存储库模式5.更新相关对象

时间:2013-09-05 14:10:00

标签: .net vb.net entity-framework .net-4.5 repository-pattern

我正在为使用Entity Framework 5作为后端数据源的新应用程序构建存储库。我有基本的CRUD操作适用于简单的模型,但我很难理解如何在更新现有对象时更新相关对象。

模型

enter image description here

进口系统 Imports System.Collections.Generic

Partial Public Class tblUser
    Public Property idUser As Integer
    Public Property username As String
    Public Property pwd As String

    Public Overridable Property tblUsermmRoles As ICollection(Of tblUsermmRole) = New HashSet(Of tblUsermmRole)
End Class

Imports System
Imports System.Collections.Generic

Partial Public Class tblUsermmRole
    Public Property idUser As Integer
    Public Property idRole As Integer

    Public Overridable Property tblUser As tblUser
    Public Overridable Property tblUserRole As tblUserRole

End Class

我正在尝试针对相关的tblUser记录向tblUsermmRole对象添加记录,但我无法使更新生效。

POCO

Public Class User

    Public Property ID As Int32
    Public Property Username As String
    Public Property Password As String
    Public Property AccessRoles As IEnumerable(Of Int32)

    Public Sub New()
    End Sub

    Public Sub New(id As Int32, userName As String, password As String, roles As List(Of Int32))
        Me.ID = id
        Me.Username = userName
        Me.Password = password
        Me.AccessRoles = roles
    End Sub

End Class

Public Class UserRoles

    Public Property RoleID As Int32

    Public Sub New(roleID As Int32)
        Me.RoleID = roleID
    End Sub

End Class

存储库

Imports System.Data.Entity

Namespace DataAccess.Repository

    Public MustInherit Class EntityFramworkContextBase
        Inherits DbContext
        Implements IUnitOfWork

        Public Sub New(entityConnectionStringOrName As String)
            MyBase.New(entityConnectionStringOrName)
        End Sub

        Public Sub Add(Of T As Class)(obj As T) Implements IUnitOfWork.Add
            [Set](Of T).Add(obj)
        End Sub

        Public Sub Attach(Of T As Class)(obj As T) Implements IUnitOfWork.Attach

            Dim entity As T

            If ExistsInContext(obj) Then
                entity = ObjectInContext(obj)
                Entry(entity).CurrentValues.SetValues(obj)

            Else
                entity = [Set](Of T).Attach(obj)
            End If

            Entry(entity).State = EntityState.Modified

        End Sub

        Public Sub Commit() Implements IUnitOfWork.Commit
            MyBase.SaveChanges()
        End Sub

        Public Function [Get](Of T As Class)() As IQueryable(Of T) Implements IUnitOfWork.Get
            Return [Set](Of T)()
        End Function

        Public Function Remove(Of T As Class)(obj As T) As Boolean Implements IUnitOfWork.Remove

            Dim entity As T

            If ExistsInContext(obj) Then
                entity = ObjectInContext(obj)
            Else
                entity = [Set](Of T).Attach(obj)
            End If

            [Set](Of T).Remove(entity)

            Return True

        End Function

        Private Function ExistsInContext(Of T As Class)(obj As T) As Boolean
            Return [Set](Of T).Local.Any(Function(o) o.Equals(obj))
        End Function

        Private Function ObjectInContext(Of T As Class)(obj As T) As T
            Return [Set](Of T).Local.FirstOrDefault(Function(o) o.Equals(obj))
        End Function

    End Class

End Namespace

问题

Entry(entity).CurrentValues.SetValues(obj)方法中调用Attach行时,将复制基本属性,但不会复制tblUsermmRole对象的新元素。

更新后的新对象

enter image description here

SetValues后的实体

enter image description here

从最初的研究看,SetValues方法似乎不会复制this post

中讨论的相关导航属性

问题

鉴于我正在使用的存储库模式(加上UnitOfWork模式)如何维护对象图关系并更新数据库?

附加说明

此方法对于附加了tblUsermmRoles的tblUsers对象的新实例的预期效果。添加两个表的记录,保持外键。

2 个答案:

答案 0 :(得分:1)

我发现实体框架并不支持这一点,这让我感到非常惊讶,但是像往常一样,社区已经通过RefactorThis' GraphDiff来解救,可以通过their GitHub repository

下载

看起来实体框架团队会将此功能视为EF6后的当前编号2 on there issue list,而Rowan Miller似乎认为这是一个好主意

  

RoMiller于2月14日晚上11:15写道

     

EF Team Triage:我们同意这是一个很好的启用方案。   考虑到我们在EF6版本中的位置以及   尺寸和我们团队不打算这个功能的影响   在EF6中实现它。因此,我们正在将其发布到Future版本   在下一个版本中重新考虑。

同时RefactorThis' GraphDiff解决了大多数用例。谢谢布伦特

更新的方法如下所示

Public Sub AttachObjectGraph(Of T As Class)(obj As T, mapping As Expression(Of Func(Of IUpdateConfiguration(Of T), Object))) Implements IUnitOfWork.AttachObjectGraph
        Me.UpdateGraph(obj, mapping)
End Sub

调用方法如下所示,指定父级 - >子实体映射

_usersRepository.AttachObjectGraph(dbUser, Function(map) map.OwnedCollection(Function(u) u.tblUsermmRoles))
_unitOfWork.Commit()

答案 1 :(得分:0)

基本上,实体框架缺乏这样做的能力。 或者说它更好,它缺乏适当的优化方式。

假设您有一个具有4个角色的用户,并且您想要删除1,并添加2个新角色。

在选择用户时,您需要加载足够的导航属性,以便填充您的角色集合。然后使用经典的Remove from collection删除角色,并使用Add。

添加新角色

点击SaveChanges后,已删除的角色将从数据库中删除(如果是1到多个),或者至少删除了2个以上的关系。将保存新角色 - 将更新m2m关系。

对于一小组实体来说,这通常是相当不错的。

但是假设用户有100.000个朋友,并且您想要从集合中删除一个。显然加载100.000只是为了删除1是完全奇怪的,这是实体框架不是很好的解决方案(或者至少我不熟悉它)。

在上面的场景中,我更倾向于通过用用户ID和朋友ID查询直接关系,而不是拉动所有朋友的列表并对该集合进行操作。

使用DbContext.Database.ExecuteSqlCommand执行ESQL命令还有一种更快的方法,用于发送批量更新和删除命令。请记住,虽然Entity Framework没有跟踪这些更改,因此您的DbContext将不会意识到您已更新了enything。

希望这个澄清对你至少有所帮助。