entity-framework - Entity Framework 更新多对多关系 - POCO

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

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

我真的不明白为什么 Entity Framework 试图在链接表中插入行并且由于上述异常而失败。我已经将现有 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 表中。我真的无法理解 Entity Framework 处理多对多关系的方式。

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

问候, 涅槃。

最佳答案

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

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(...);

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

关于entity-framework - Entity Framework 更新多对多关系 - POCO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10275699/

相关文章:

c# - 如何禁用 EF 代码优先中链接表的级联删除?

sql-server - 数据库拦截导致 SQL 嵌套太深

c# - 如何忽略 EF 4.3 迁移中的表/类

.net - Entity Framework 模型是否可以传递 SQL 脚本以针对数据库运行

c# - 添加相关实体时报错 "Database operation expected to affect 1 row(s) but actually affected 0 row(s)"

asp.net-mvc - Entity Framework - 选择特定列并返回强类型而不丢失强制转换

c# - 仅填充相关对象的 ID 时不加载导航属性

c# - linq to entities 的 linq 更新问题

.net - 我可以使用数据注释通过 Entity Framework 4.1 RC 执行级联删除吗?

c# - 如何使 Entity Framework CTP5 与 SQLite 一起工作?