实体框架:坚持在多对多中添加新实体,而不是重新使用现有的FK

bgs*_*264 17 asp.net .net-4.0 entity-framework-4

简短地说,我有多对多的关系
Cases -----< CaseSubjectRelationships >------ CaseSubjects

更全面:案例(ID,CaseTypeID,.......)
CaseSubjects(ID,DisplayName,CRMSPIN)
CaseSubjectsRelationships(CaseID,SubjectID,PrimarySubject,RelationToCase,...)

在我的多对多链接表中是与主题与特定案例的关联相关的其他属性 - 例如,开始日期,结束日期,与案例的自由文本关系(观察者,创建者等)

已创建实体框架数据模型 - ASP.NET 4.0版

我有一个带有一个方法的WCF服务,该方法CreateNewCase接受一个Case对象(由实体框架创建的实体)作为其参数- 它的工作是将案例保存到数据库中.

WCF服务由第三方工具调用.这是发送的SOAP:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <CreateNewCase xmlns="http://tempuri.org/">
            <c xmlns:a="http://schemas.datacontract.org/2004/07/CAMSModel">
                <a:CaseSubjectsRelationships>
                    <a:CaseSubjectsRelationship>
                        <a:CaseSubject>
                            <a:CRMSPIN>601</a:CRMSPIN>
                            <a:DisplayName>Fred Flintstone</a:DisplayName>
                        </a:CaseSubject>
                        <a:PrimarySubject>true</a:PrimarySubject>
                        <a:RelationToCase>Interested</a:RelationToCase>
                        <a:StartDate>2011-07-12T00:00:00</a:StartDate>
                    </a:CaseSubjectsRelationship>
                    <a:CaseSubjectsRelationship>
                        <a:CaseSubject>
                            <a:CRMSPIN>602</a:CRMSPIN>
                            <a:DisplayName>Barney Rubble</a:DisplayName>
                        </a:CaseSubject>
                        <a:RelationToCase>Observer</a:RelationToCase>
                        <a:StartDate>2011-07-12T00:00:00</a:StartDate>
                    </a:CaseSubjectsRelationship>
                </a:CaseSubjectsRelationships>
                <a:CaseType>
                    <a:Identifier>Change of Occupier</a:Identifier>
                </a:CaseType>
                <a:Description>Case description</a:Description>
                <a:Priority>5</a:Priority>
                <a:QueueIdentifier>Queue One</a:QueueIdentifier>
                <a:Title>Case title</a:Title>
            </c>
        </CreateNewCase>
    </s:Body>
</s:Envelope>
Run Code Online (Sandbox Code Playgroud)

WCF引擎正确地将其反序列化为Case实体,当我查看调试器时,所有内容都已正确设置.

我想要做的是,CaseSubject如果数据库中没有CRMSPIN指定的条目(CRMSPIN是来自中央客户数据库的参考号),则只创建一个新的.

因此,在下面的示例中,我想看看我是否已经CaseSubjects为CRMSPIN 601 的用户输入了一个条目,如果我这样做,我不想创建另一个(重复)条目,而是将新案例链接到现有的主题(虽然显然需要一个新行,需要在CaseSubjectsRelationships中创建特定的'附加'信息,如关系等)

这是我尝试过的.NET代码.

Public Class CamsService
    Implements ICamsService

    Public Function CreateNewCase(c As CAMSModel.Case) As String Implements ICamsService.CreateNewCase

        Using ctx As New CAMSEntities
            ' Find the case type '
            Dim ct = ctx.CaseTypes.SingleOrDefault(Function(x) x.Identifier.ToUpper = c.CaseType.Identifier.ToUpper)

            ' Give an error if no such case type '
            If ct Is Nothing Then
                Throw New CaseTypeInvalidException(String.Format("The case type {0} is not valid.", c.CaseType.Identifier.ToString))
            End If

            ' Set the case type based on that found in database: '
            c.CaseType = ct

            For Each csr In c.CaseSubjectsRelationships
                Dim spin As String = csr.CaseSubject.CRMSPIN
                Dim s As CaseSubject = ctx.CaseSubjects.SingleOrDefault(Function(x) x.CRMSPIN = spin)

                If Not s Is Nothing Then
                    ' The subject has been found based on CRMSPIN so set the subject in the relationship '
                    csr.CaseSubject = s
                End If
            Next

            c.CreationChannel = "Web service"
            c.CreationDate = Now.Date

            ' Save it '
            ctx.AddToCases(c)
            ctx.SaveChanges()
        End Using

        ' Return the case reference '
        Return c.ID.ToString
    End Function
End Class
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,而不是For Each循环,我尝试基于CRMSPIN得到一个主题,如果我得到了什么,那么我更新"CaseSubject"实体.(我也试过csr.SubjectID = s.ID而不是设置整个实体,我也试过设置它们!).

但是,即使ctx.SaveChanges()在线上放置一个断点并查看主题如何设置并在调试器中看到它看起来很好,它总是在CaseSubjects表中创建一个新行.

我原则上可以看到它应该工作 - 你会看到我对Case Type做了完全相同的事情 - 我选择了XML中发送的标识符,通过上下文找到了带有该标识符的实体,然后将case更改.CaseType为我找到的实体.当它保存时,它完美且按预期工作,没有重复的行.

我只是在尝试将同一理论应用于多对多关系的一方时遇到了麻烦.

以下是.edmx中的一些(希望是相关的)摘录

<EntitySet Name="Cases" EntityType="CAMSModel.Store.Cases" store:Type="Tables" Schema="dbo" />
          <EntitySet Name="CaseSubjects" EntityType="CAMSModel.Store.CaseSubjects" store:Type="Tables" Schema="dbo" />
          <EntitySet Name="CaseSubjectsRelationships" EntityType="CAMSModel.Store.CaseSubjectsRelationships" store:Type="Tables" Schema="dbo" />



 <AssociationSet Name="FK_CaseSubjectsRelationships_Cases" Association="CAMSModel.Store.FK_CaseSubjectsRelationships_Cases">
            <End Role="Cases" EntitySet="Cases" />
            <End Role="CaseSubjectsRelationships" EntitySet="CaseSubjectsRelationships" />
          </AssociationSet>
          <AssociationSet Name="FK_CaseSubjectsRelationships_CaseSubjects" Association="CAMSModel.Store.FK_CaseSubjectsRelationships_CaseSubjects">
            <End Role="CaseSubjects" EntitySet="CaseSubjects" />
            <End Role="CaseSubjectsRelationships" EntitySet="CaseSubjectsRelationships" />
          </AssociationSet>
Run Code Online (Sandbox Code Playgroud)

编辑:对象CaseSubject属性的属性设置器CaseSubjectsRelationships:

/// <summary>
/// No Metadata Documentation available.
/// </summary>
<XmlIgnoreAttribute()>
<SoapIgnoreAttribute()>
<DataMemberAttribute()>
<EdmRelationshipNavigationPropertyAttribute("CAMSModel", "FK_CaseSubjectsRelationships_CaseSubjects", "CaseSubject")>
Public Property CaseSubject() As CaseSubject
    Get
        Return CType(Me, IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of CaseSubject)("CAMSModel.FK_CaseSubjectsRelationships_CaseSubjects", "CaseSubject").Value
    End Get
    Set
        CType(Me, IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of CaseSubject)("CAMSModel.FK_CaseSubjectsRelationships_CaseSubjects", "CaseSubject").Value = value
    End Set
End Property
Run Code Online (Sandbox Code Playgroud)

bgs*_*264 1

好的:因此,这个解决方案是@veljkoz 在他的回答中所说的内容的组合(这对于帮助我达成最终解决方案非常有用,但其本身并不是完整的解决方案)

通过将For Each循环移动到在其他任何事情之前完成的第一件事(正如 @veljkoz 所暗示的),这消除了Collection was modified, enumeration may not continue我在设置csr.CaseSubject = Nothing.

事实证明,不附加实体(例如不设置csr.CaseSubject为实体而仅设置为Nothing)而是使用.SubjectID属性也很重要。上述所有内容的组合使我得到了以下代码,该代码运行良好并且不会尝试插入重复的行。

+1 给 @veljkoz 寻求帮助,但还要注意,该解决方案包括设置实体引用Nothing和使用该ID属性。

完整的工作代码:

 Public Function CreateNewCase(c As CAMSModel.Case) As String Implements ICamsService.CreateNewCase

    Using ctx As New CAMSEntities
        ' Subjects first, otherwise when you try to set csr.CaseSubject = Nothing you get an exception '
        For Each csr In c.CaseSubjectsRelationships
            Dim spin As String = csr.CaseSubject.CRMSPIN
            Dim s As CaseSubject = ctx.CaseSubjects.SingleOrDefault(Function(x) x.CRMSPIN = spin)

            If Not s Is Nothing Then
                ' The subject has been found based on CRMSPIN so set the subject in the relationship '
                csr.CaseSubject = Nothing
                csr.SubjectID = s.ID
            End If
        Next

        ' Find the case type '
        Dim ct = ctx.CaseTypes.SingleOrDefault(Function(x) x.Identifier.ToUpper = c.CaseType.Identifier.ToUpper)

        ' Give an error if no such case type '
        If ct Is Nothing Then
            Throw New CaseTypeInvalidException(String.Format("The case type {0} is not valid.", c.CaseType.Identifier.ToString))
        End If

        ' Set the case type based on that found in database: '
        c.CaseType = ct

        c.CreationChannel = "Web service"
        c.CreationDate = Now.Date

        ' Save it '
        ctx.AddToCases(c)
        ctx.SaveChanges()
    End Using

    ' Return the case reference '
    Return c.ID.ToString
End Function
Run Code Online (Sandbox Code Playgroud)