5

2 つのオブジェクト間の多対多接続を保存しようとするコードに問題がありますが、何らかの理由で保存されません。

コードファーストの方法を使用してデータベースを作成しました。データベースには、この問題が発生する次のエンティティがあります。

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<ProductTag> ProductTags { get; set; }
}
public class ProductTag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

テーブル ProductTagProducts が自動的に作成されますが、これはもちろん、2 つの間の単なる接続テーブルです。

現在、製品の作成は正常に機能しています。以下を実行するだけで、ProductTagProducts テーブルに接続が作成されます。

Product.ProductTags.Add(productTag);

データベースに重複したタスクがないようにするために、私たちは自分で保存を処理します。productTag には常に、既存の ID を持つ製品タグが含まれています。

問題は、同じ製品または別の製品を編集するときに発生します。商品タグは既存のものです。そして、次のプロセスを使用して保存します。

List<ProductTag> productTags = new List<ProductTag>();
string[] splittedTags = productLanguagePost.TagList.Split(',');
foreach (string tag in splittedTags) {
    ProductTag productTag = new ProductTag();
    productTag.Name = tag;
    productTags.Add(productTagRepository.InsertAndOrUse(productTag));
}

タグをカンマで分割します。これが HTML 要素から受け取る方法です。次に、新しいエンティティを定義し、InsertAndOrUse を使用して、タグが既に存在するかどうかを判断します。タグが既に存在する場合は、同じエンティティを返しますが、ID が入力されています。まだ存在しない場合は、タグをデータベースに追加し、ID を含むエンティティも返します。製品に重複する ID がないことを確認するために、新しいリストを作成します (製品の既存のタグ リストに直接追加して試してみましたが、結果は同じです)。

product.ProductTags = productTags;
productRepository.InsertOrUpdate(product);
productRepository.Save();

次に、リストを ProductTags に設定し、リポジトリに挿入または更新を処理させます。もちろん、更新が行われます。念のため、これは InsertOrUpdate 関数です。

public void InsertOrUpdate(Product product) {
    if (product.Id == default(int)) {
        context.Products.Add(product);
    } else {
        context.Entry(product).State = EntityState.Modified;
    }
}

save メソッドは、コンテキストの SaveChanges メソッドを呼び出すだけです。商品を編集して別のタグを追加すると、新しいタグが保存されません。ただし、保存機能にブレークポイントを設定すると、両方が存在することがわかります。 ここに画像の説明を入力

そして、新しく追加されたタグ「Oeh-la-la」を開くと、そこから製品を参照することさえできます。 ここに画像の説明を入力

しかし、保存が行われると、他のすべての値で成功し、ProductTagProducts テーブルで接続が確立されません。たぶんそれは本当に単純なことですが、現時点では私にはわかりません。他の誰かが明るい表情を見せてくれることを本当に願っています。

前もって感謝します。

編集: ProductTag の InsertAndOrUse メソッドの要求どおり。呼び出す InsertOrUpdate メソッドは、上記とまったく同じです。

public ProductTag InsertAndOrUse(ProductTag productTag)
{
    ProductTag resultingdProductTag = context.ProductTags.FirstOrDefault(t => t.Name.ToLower() == productTag.Name.ToLower());

    if (resultingdProductTag != null)
    {
        return resultingdProductTag;
    }
    else
    {
        this.InsertOrUpdate(productTag);
        this.Save();
        return productTag;
    }
}
4

1 に答える 1

6

この行を知っておく必要があります...

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

...関係の状態には影響しません。product渡されるエンティティをEntryとしてマークするだけです。Modifiedつまり、スカラー プロパティProduct.Nameは変更済みとしてマークされ、他には何もマークされません。データベースに送信されるSQLUPDATEステートメントは、Nameプロパティを更新するだけです。多対多リンク テーブルには何も書き込まれません。

その行との関係を変更できる唯一の状況は、外部キーの関連付け、つまり、モデルでプロパティとして公開されている外部キーを持つ関連付けです。

外部キーはモデル内に対応するエンティティを持たないリンク テーブルにあるため、モデル内で外部キーを公開できないため、多対多の関係は外部キーの関連付けにはなりません。多対多の関係は、常に独立した関連付けです。

リレーションシップ状態エントリの直接操作 (かなり高度で、 に移動する必要があります) を除いて、ObjectContext独立した関連付けは、Entity Framework の変更追跡を使用してのみ追加または削除できます。さらに、ユーザーがタグを削除した可能性があることを考慮する必要があります。この場合、リンク テーブル内の関係エントリを削除する必要があります。このような変更を追跡するには、最初にデータベースから特定の製品に関連するすべての既存のタグをロードする必要があります。

これらすべてをまとめるには、InsertOrUpdateメソッドを変更する (または新しい特殊なメソッドを導入する)必要があります。

public void InsertOrUpdate(Product product) {
    if (product.Id == default(int)) {
        context.Products.Add(product);
    } else {
        var productInDb = context.Products.Include(p => p.ProductTags)
            .SingleOrDefault(p => p.Id == product.Id);

        if (productInDb != null) {
            // To take changes of scalar properties like Name into account...
            context.Entry(productInDb).CurrentValues.SetValues(product);

            // Delete relationship
            foreach (var tagInDb in productInDb.ProductTags.ToList())
                if (!product.ProductTags.Any(t => t.Id == tagInDb.Id))
                    productInDb.ProductTags.Remove(tagInDb);

            // Add relationship
            foreach (var tag in product.ProductTags)
                if (!productInDb.ProductTags.Any(t => t.Id == tag.Id)) {
                    var tagInDb = context.ProductTags.Find(tag.Id);
                    if (tagInDb != null)
                        productInDb.ProductTags.Add(tagInDb);
                }
        }
    }

コレクション内のタグがインスタンスにアタッチされているかどうFindか、コード スニペット (の正確なコードが欠落している) からはわからないため、上記のコードで使用していました。タグをロードするためのデータベース ラウンドトリップが犠牲になる可能性があります。InsertAndOrUseproduct.ProductTagscontextFind

すべてのタグproduct.ProductTagsが付いている場合は、交換できます...

                    var tagInDb = context.ProductTags.Find(tag.Id);
                    if (tagInDb != null)
                        productInDb.ProductTags.Add(tagInDb);

...ちょうど

                    productInDb.ProductTags.Add(tag);

または、それらがすべて添付されていることが保証されておらず、データベースへのラウンドトリップを回避したい場合 (添付されているかどうかにかかわらず、少なくともタグがデータベースに存在することが確実にわかっているため)、コードを次のように置き換えることができます。

                    var tagInDb = context.ProductTags.Local
                        .SingleOrDefault(t => t.Id == tag.Id);
                    if (tagInDb == null) {
                        tagInDb = tag;
                        context.ProductTags.Attach(tagInDb);
                    }
                    productInDb.ProductTags.Add(tagInDb);
于 2013-08-03T15:07:52.430 に答える