web-dev-qa-db-ja.com

EntityFrameworkを使用したエンティティの追加と更新

前回のプロジェクトでは、Entity Framework5コードファーストを使用しました。私はプロジェクトを完了しましたが、開発プロセス中に多くの苦痛を感じました。

私は自分の痛みを以下に説明しようとしました:

データアクセスロジックレイヤーには、Product、ProductCategory、Order、Company、Manufacturerなどのいくつかのデータクラスがありました。各クラスには、他のいくつかのクラスへの参照がいくつかあります。たとえば、ProductインスタンスにはProductCategoryプロパティがあります。

データアクセスオブジェクトクラスのAddメソッドとUpdateメソッドの内部で、各プロパティ(プリミティブ型を除く)の状態をコンテキスト内でUnchangedまたはModifiedに設定しました。以下は、いくつかのdaoクラスのupdateメソッドの一部です。

context.Entry(entity).State = System.Data.EntityState.Modified;
if (entity.Category != null)
    context.Entry(entity.Category).State = System.Data.EntityState.Unchanged;

if (entity.Manufacturer != null)
    context.Entry(entity.Manufacturer).State = System.Data.EntityState.Unchanged;

foreach (var specificationDefinition in entity.SpecificationDefinitions)
{
    context.Entry(specificationDefinition).State = System.Data.EntityState.Unchanged;
    foreach (var specificationValue in specificationDefinition.Values)
    {
        context.Entry(specificationValue).State = System.Data.EntityState.Unchanged;
    }
}

このコードは次のようになります。私の追加または更新メソッドのいくつかは、約30行のコードです。私は何か間違ったことをしていると思います。エンティティを追加または更新することはそれほど苦痛ではないはずですが、オブジェクトの状態を設定しないと、データベースに例外または重複エントリが発生します。データベースにマップする各プロパティの状態を本当に設定する必要がありますか?

9
Furkan

コードは次のように置き換えることができます。

context.Products.Attach(entity);
context.Entry(entity).State = System.Data.EntityState.Modified;

これが同じである理由は(関連するエンティティが以前にUnchanged以外の状態でコンテキストにすでにアタッチされていない限り)、Attachentity含む状態Unchangedのコンテキストへのオブジェクトグラフ内のすべての関連エンティティ。後でModifiedの状態をentityに設定すると、製品のみ(関連エンティティではない)の状態がUnchangedからModifiedに変更されるだけです。

11
Slauma

さて、あなたはその時何か間違ったことをしているだけです。私のコメントに加えて、EFがデフォルトで重複を作成しないことを示すサンプルを作成しました。

私には2つのクラスがあります:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ProductCategory Category { get; set; }
    public decimal Price { get; set; }
}

public class ProductCategory
{
    public int Id { get; set; }
    public string Name { get; set; }
}

1つのコンテキスト:

public class MyContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<ProductCategory> ProductCategories { get; set; }

    public MyContext()
        : base("name=MyContext")
    {
    }

    public MyContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer<MyContext>(null);

        // Table mappings
        modelBuilder.Entity<Product>().ToTable("Product");
        modelBuilder.Entity<ProductCategory>().ToTable("ProductCategory");

        base.OnModelCreating(modelBuilder);
    }
}

次に、1つの初期化クラス(必要に応じて他の戦略から継承できます):

public class InitDb<TContext> : DropCreateDatabaseAlways<TContext>
    where TContext : DbContext
{
}

メインプログラム:

    static void Main(string[] args)
    {
        var prodCat = new ProductCategory()
        {
            Name = "Category 1"
        };

        var prod = new Product()
        {
            Name = "Product 1",
            Category = prodCat,
            Price = 19.95M
        };

        using (var context = new MyContext())
        {
            var initializer = new InitDb<MyContext>();
            initializer.InitializeDatabase(context);

            Console.WriteLine("Adding products and categories to context.");
            context.ProductCategories.Add(prodCat);
            context.Products.Add(prod);

            Console.WriteLine();
            Console.WriteLine("Saving initial context.");
            context.SaveChanges();
            Console.WriteLine("Context saved.");

            Console.WriteLine();
            Console.WriteLine("Changing product details.");
            var initProd = context.Products.Include(x => x.Category).SingleOrDefault(x => x.Id == 1);
            PrintProduct(initProd);
            initProd.Name = "Product 1 modified";
            initProd.Price = 29.95M;
            initProd.Category.Name = "Category 1 modified";
            PrintProduct(initProd);

            Console.WriteLine();
            Console.WriteLine("Saving modified context.");
            context.SaveChanges();
            Console.WriteLine("Context saved.");

            Console.WriteLine();
            Console.WriteLine("Getting modified product from database.");
            var modProd = context.Products.Include(x => x.Category).SingleOrDefault(x => x.Id == 1);
            PrintProduct(modProd);

            Console.WriteLine();
            Console.WriteLine("Finished!");
            Console.ReadKey();
        }


    }

    static void PrintProduct(Product prod)
    {
        Console.WriteLine(new string('-', 10));
        Console.WriteLine("Id      : {0}", prod.Id);
        Console.WriteLine("Name    : {0}", prod.Name);
        Console.WriteLine("Price   : {0}", prod.Price);
        Console.WriteLine("CatId   : {0}", prod.Category.Id);
        Console.WriteLine("CatName : {0}", prod.Category.Name);
        Console.WriteLine(new string('-', 10));
    }

これにより、次のコンソール出力が生成されます。

Adding products and categories to context.

Saving initial context.
Context saved.

Changing product details.
----------
Id      : 1
Name    : Product 1
Price   : 19,95
CatId   : 1
CatName : Category 1
----------
----------
Id      : 1
Name    : Product 1 modified
Price   : 29,95
CatId   : 1
CatName : Category 1 modified
----------

Saving modified context.
Context saved.

Getting modified product from database.
----------
Id      : 1
Name    : Product 1 modified
Price   : 29,95
CatId   : 1
CatName : Category 1 modified
----------

Finished!

また、SQL Server Management Studioを見ると、このソリューションは1つの製品と1つのカテゴリのみを作成(および更新)しています。

もちろん、リポジトリを使用して、データと作業単位を取得、更新、および削除する必要があります。これらは例から除外されています。

したがって、コードを投稿しない場合、それ以上のサポートはできません:-)

3
Nullius