【问题标题】:Entity Framework Updating Many-To-Many Relationships - POCO实体框架更新多对多关系 - POCO
【发布时间】:2012-04-25 12:06:04
【问题描述】:

我在两个实体 RelayConfigStandardContact

之间有以下多对多关系

实体:

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 > 0,更新代码会将所有实体更改为修改状态(更新方法末尾设置的已删除记录除外)。

我真的不明白为什么实体框架试图在链接表中插入行并且由于上述异常而失败。我已经将现有 RelayConfig.StandardContacts 实体的 EntityState 更改为 Modified。

简而言之,为什么我会在上面粘贴异常。

问候, 涅槃。

编辑: 上面 Update 方法的参数(addedContacts 和 deletedContacts)已经是 Id > 0 的现有实体。

编辑2: 根据您的建议,我从更新方法中删除了用于插入新(数据库中不存在)记录的代码。所以现在我的更新方法只将现有的 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 集合时,StandardContact 也会添加到数据库中,从而创建重复条目。不仅如此,还创建了一个新的 RelayConfig 对象(我不知道从哪里开始)并插入到 RelayConfigs 表中。我真的无法理解实体框架处理多对多关系的方式。

@Ladislav,如果您有一些适用于多对多关系更新(用于分离实体)的示例代码,那么我可以请您给我看一下。

问候, 涅槃

Edit3(解决方案):

最终我使用了一种完全不同的方法。这是更新的代码

    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);
            }
        });
    }

问候, 涅槃。

【问题讨论】:

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


    【解决方案1】:

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

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

    您告诉 EF 所有添加的联系人都是新的关系,它们将被插入到您的联结表中,用于多对多关系,但调用它:

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

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

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

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

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

    【讨论】:

    • 感谢您的回复。但我仍有疑问。让我们假设没有 addedRecords 或 deletedRecords。假设 RelayConfig 有 2 个 StandardContact 记录,并且我想保留对这 2 个 StandardContact 记录的更改,除了将它们的 EntityState 更改为 modified 之外,我是否还必须更改这 2 个 StandardContact 的 RelationshipState?我的意思是我们如何更新关系的多方面(暂时忽略添加和删除的记录)。
    • 如果您添加了 realayConfig,您必须将关系状态更改为未更改。如果您只是 Attache realayConfig,您不必对这些关系做任何事情,但是当您想添加新关系时,附加可能会导致另一个问题。
    • 拉迪斯拉夫,感谢您的帮助。我使用了一种不同的方法来使多对多更新工作。我已将代码粘贴到 Edit3 中。
    猜你喜欢
    • 2014-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-14
    • 1970-01-01
    • 1970-01-01
    • 2017-01-28
    • 2011-12-17
    相关资源
    最近更新 更多