实体框架更新多对多关系 - POCO

时间:2012-04-23 06:16:40

标签: entity-framework entity-framework-4.1 ef-code-first

我遵循两个实体之间的多对多关系 RelayConfig StandardContact

实体:

public class RelayConfig : EntityBase, IDataErrorInfo {
    ...
    //Associations
    public virtual ICollection<StandardContact> StandardContacts { get; set; }
}


public class StandardContact :EntityBase, IDataErrorInfo {
    ...
    //Associations
    public virtual ICollection<RelayConfig> RelayConfigs { get; set; }
}

现在我正在尝试更新RelayConfig及其与StandardContact的关系。以下是更新RelayConfig的代码。

public class RelayConfigRepository : GenericRepository<RelayConfig> {
    ....

    public void Update(RelayConfig relayConfig, List<StandardContact> addedContacts, List<StandardContact> deletedContacts) {
        context.RelayConfigs.Add(relayConfig);
        if (relayConfig.Id > 0) {
            context.Entry(relayConfig).State = EntityState.Modified;
        }

        addedContacts.ForEach(ad => relayConfig.StandardContacts.Add(ad));

        foreach (StandardContact standardContact in relayConfig.StandardContacts) {
            if (standardContact.Id > 0) {
                context.Entry(standardContact).State = EntityState.Modified;
            }
        }

        relayConfig.StandardContacts.ToList().ForEach(s => {
            if (deletedContacts.Any(ds => ds.Id == s.Id)) {
                context.Entry(s).State = EntityState.Deleted;
            }
        });
    }
    ...
}

当我运行更新时,我遇到异常,其内部异常如下所示。

InnerException: System.Data.SqlClient.SqlException
        Message=Violation of PRIMARY KEY constraint 'PK__Standard__EE33D91D1A14E395'. Cannot insert duplicate key in object 'dbo.StandardContactRelayConfigs'.

dbo.StandardContactRelayConfigs是链接RelayConfig和StandardContact的链接表。如您所见,如果Id&gt;,更新代码会将所有实体更改为已修改状态。 0(在Update方法结束时设置的已删除记录除外)。

我真的无法理解为什么实体框架试图在链接表中插入行并因上述异常而失败。我已经将现有RelayConfig.StandardContacts实体的EntityState更改为Modified。

简而言之,为什么我会粘贴上面的例外。

的问候, NIRVAN。

修改 上面的Update方法的参数(addedContacts和deletedContacts)已经是Id&gt;的现有实体。 0

EDIT2: 根据您的建议,我删除了从update方法插入新鲜(不存在于数据库中)记录的代码。所以现在我的更新方法只将现有的StandardContact记录添加到RelayConfig集合中。但我仍然无法使代码正常工作。首先是我正在使用的代码

    public void Update(RelayConfig relayConfig, List<StandardContact> addedContacts, List<StandardContact> deletedContacts) {
        context.RelayConfigs.Add(relayConfig);

        if (relayConfig.Id > 0) {
            context.Entry(relayConfig).State = EntityState.Modified;
        }


        addedContacts.ForEach(contact => {
            context.StandardContacts.Attach(contact);
            relayConfig.StandardContacts.Add(contact);
            objectContext.ObjectStateManager.
                ChangeRelationshipState(relayConfig, contact, rs => rs.StandardContacts, EntityState.Added);
        });
    }

现在我只专注于添加记录。当StandardContact(联系变量)与任何其他现有的RelayConfig对象没有任何关系时,上面的代码很有效。在这种情况下,将在联结表中为添加到RelayConfig.StandardContacts集合的每个联系人创建一个新条目。但是当StandardContact(联系变量)已经与其他RelayConfig对象关系时,事情会变得很丑陋(不可预测的行为)。在这种情况下,当StandardContact添加到RelayConfig.StandardContacts Collection时,StandardContact也会添加到数据库中,从而创建重复条目。不仅如此,还创建了一个新的RelayConfig对象(我不知道从哪里开始)并插入到RelayConfigs表中。我真的无法理解实体框架与多对多关系的工作方式。

@Ladislav,如果您有一些适用于多对多关系更新的示例代码(对于已分离的实体),那么我可以请求您向我展示相同的内容。

的问候, NIRVAN

编辑3(解决方案):

最终我最终采用了完全不同的方法。以下是更新的代码

    public void Update(RelayConfig relayConfig, List<StandardContact> exposedContacts) {

        context.Entry(relayConfig).State = EntityState.Modified;

        relayConfig.StandardContacts.Clear();
        exposedContacts.ForEach(exposedContact => {
            StandardContact exposedContactEntity = null;
            exposedContactEntity = context.StandardContacts.SingleOrDefault(sc => sc.Id == exposedContact.Id);
            if (exposedContactEntity != null) {
                relayConfig.StandardContacts.Add(exposedContactEntity);
            }
        });
    }

的问候, NIRVAN。

1 个答案:

答案 0 :(得分:10)

问题在于多对多关系有自己的状态。所以,如果你这样称呼:

addedContacts.ForEach(ad => relayConfig.StandardContacts.Add(ad));

你告诉EF所有添加的联系人都是新的关系,这些联系人将被插入你的联结表中以获得多对多关系,但是要调用它:

foreach (StandardContact standardContact in relayConfig.StandardContacts) {
    if (standardContact.Id > 0) {
        context.Entry(standardContact).State = EntityState.Modified;
    }
}

将改变联系人实体的状态,但不会改变关系的状态 - 它仍然被跟踪为新的(顺便说一句,它不能被修改,只能添加,删除或保持不变)。因此,当您保存更改时,所有联系人的关系都会添加到联结表中,如果数据库中已存在相同的关系,您将获得异常(因为联结表只包含两个也是PK的FK,在这种情况下相同的关系= PK违规)。

您还需要使用以下方式设置关系状态:

var objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.ObjectStateManager.ChangeRelatioshipState(...);

但问题出在这里:你必须区分刚刚与现有或新的依赖配置创建新关系的现有联系人以及全新的联系人 - 我建议你单独处理全新的联系人,否则你的代码会非常复杂。