3

我有两个实体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。

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

问候,尼尔文。

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

Edit2: 根据您的建议,我从更新方法中删除了用于插入新(数据库中不存在)记录的代码。所以现在我的更新方法只将现有的 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);
            }
        });
    }

问候,尼尔文。

4

1 回答 1

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

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

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

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

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

于 2012-04-23T10:13:55.680 回答