3

RelayConfigStandardContactの 2 つのエンティティ間に多対多の関係があります。

エンティティ:

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 の場合、Update コードはすべてのエンティティを変更済み状態に変更します (Update メソッドの最後に設定された削除済みレコードを除く)。

エンティティ フレームワークがリンク テーブルに行を挿入しようとして、上記の例外で失敗する理由を本当に理解できません。既存の RelayConfig.StandardContacts エンティティの EntityState を既に Modified に変更しています。

要するに、なぜ上に貼り付けられた例外が発生するのですか。

よろしく、ニルヴァン。

編集: 上記の Update メソッドのパラメーター (addedContacts および deletedContacts) は、Id > 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 (contact 変数) が他の既存の RelayConfig オブジェクトと何の関係も持た​​ない場合にうまく機能します。その場合、RelayConfig.StandardContacts コレクションに追加された連絡先ごとに、ジャンクション テーブルに新しいエントリが作成されます。しかし、StandardContact (contact 変数) が既に他の 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;
    }
}

連絡先エンティティの状態は変更されますが、関係の状態は変更されません。新しいものとして追跡されます (変更はできませんが、追加、削除、または変更のみ可能です)。したがって、変更を保存すると、すべての連絡先のリレーションがジャンクション テーブルに追加され、同じリレーションがデータベースに既に存在する場合は例外が発生します (ジャンクション テーブルには PK でもある 2 つの FK しか含まれていないため、この場合は同じリレーション = PK 違反であるため) )。

以下を使用して、リレーションの状態を設定する必要もあります。

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

ただし、ここで問題が発生します。既存または新しい依存構成との新しい関係を作成したばかりの既存の連絡先と、完全に新しい連絡先を区別する必要があります。完全に新しい連絡先を個別に処理することをお勧めします。そうしないと、コードが非常に複雑になります。

于 2012-04-23T10:13:55.680 に答える