3

は、この単純なモデルとのViewModelシナリオを考えデータベースから取り出します。フォームが送信されたときに今、私が行いますエンティティフレームワーク4.1最初のコードと自動マッパーの問題

SomeModel destinationModel = someContext.SomeModel.Include("Company").Where(i => i.Id == id) // assume id is available from somewhere. 

その後、私は

Company oldCompany = destinationModel.company; // save it before mapper assigns it null 

Mapper.Map(sourceViewModel,destinationModel); 

//After this piece of line my company in destinationModel will be null because sourceViewModel's company is null. Great!! 
//so I assign old company to it 

destinationModel.company = oldCompany; 

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

context.SaveChanges(); 

、問題を行う、私はそれがSaveChangesメソッドの後にデータベースにまだnullである私の会社にoldCompanyを割り当てる場合もあります。

注:

私はこれらの行に変更した場合:これらの

destinationModel.company = oldCompany; 

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

context.SaveChanges(); 

:私は状態を2回変更

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

destinationModel.company = oldCompany; 

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

context.SaveChanges(); 

お知らせし、それが正常に動作します。何が問題なの?これはef 4.1バグですか?

この問題に対処するためのサンプルコンソールアプリケーションです:

using System; 
using System.Linq; 
using System.Data.Entity; 
using System.ComponentModel.DataAnnotations; 
using AutoMapper; 

namespace Slauma 
{ 
    public class SlaumaContext : DbContext 
    { 
     public DbSet<Company> Companies { get; set; } 
     public DbSet<MyModel> MyModels { get; set; } 

     public SlaumaContext() 
     { 
      this.Configuration.AutoDetectChangesEnabled = true; 
      this.Configuration.LazyLoadingEnabled = true; 
     } 
    } 

    public class MyModel 
    { 
     public int Id { get; set; } 
     public string Foo { get; set; } 

     [ForeignKey("CompanyId")] 
     public virtual Company Company { get; set; } 

     public int? CompanyId { get; set; } 
    } 

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


    public class MyViewModel 
    { 
     public string Foo { get; set; } 

     public Company Company { get; set; } 

     public int? CompanyId { get; set; } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 

      Database.SetInitializer<SlaumaContext>(new DropCreateDatabaseIfModelChanges<SlaumaContext>()); 

      SlaumaContext slaumaContext = new SlaumaContext(); 

      Company company = new Company { Name = "Microsoft" }; 
      MyModel myModel = new MyModel { Company = company, Foo = "Foo"}; 

      slaumaContext.Companies.Add(company); 
      slaumaContext.MyModels.Add(myModel); 
      slaumaContext.SaveChanges(); 

      Mapper.CreateMap<MyModel, MyViewModel>(); 
      Mapper.CreateMap<MyViewModel, MyModel>(); 


      //fetch the company 
      MyModel dest = slaumaContext.MyModels.Include("Company").Where(c => c.Id == 1).First(); //hardcoded for demo 

      Company oldCompany = dest.Company; 

      //creating a viewmodel 
      MyViewModel source = new MyViewModel(); 
      source.Company = null; 
      source.CompanyId = null; 
      source.Foo = "foo hoo"; 

      Mapper.Map(source, dest); // company null in dest 


      //uncomment this line then only it will work else it won't is this bug? 
      //slaumaContext.Entry(dest).State = System.Data.EntityState.Modified; 

      dest.Company = oldCompany; 

      slaumaContext.Entry(dest).State = System.Data.EntityState.Modified; 
      slaumaContext.SaveChanges(); 

      Console.ReadKey(); 

     } 
    } 
} 
+0

'CompanyModel'や' SomeViewModel'に 'CompanyI'のような外部キープロパティがあります。 ?あなたのViewModelに 'null'が入っていることが分かったら、' Include'を使って 'Company'を読み込むのはなぜですか?私の意見では、 'Include'を削除することができます。 – Slauma

+0

外部キーのプロパティはNULL可能であり、 'SomeViewModel'にもありますか? 'Mapper.Map'の後で何が起こりますか?' destinationModel'のFK値を上書きしますか? – Slauma

+0

@Slauma:はい、あなたはかなり正しいです。外部キーはNULL可能です。はい、Mapper.Mapの後にfkの値はnullですが、明示的に値を代入しますが、効果はありません。それはヌルだけを「好き」でした。 – Jaggu

答えて

3

Automapperは、デフォルトで常に先のインスタンスにソース・インスタンスからすべてのプロパティを更新します。あなたの会社のプロパティが上書きされたくないのであれば、あなたは明示的にマッパーのためにこれを設定する必要はありません:

Mapper.CreateMap<MyViewModel, MyModel>().ForMember(m => m.Company, c => c.UseDestinationValue()); 

EF関連するこれまでのところ何も。

Mapper.CreateMap<MyViewModel, MyModel>().ForMember(m => m.CompanyId, c => c.UseDestinationValue()); 

EDIT:あなたはまた、マッピング中たCompanyIdの宛先値を使用する必要があります:あなたはEFでこれを使用している場合しかし、あなたは常にあなたのナビゲーションプロパティ会社とたCompanyIdを使用する必要がしかし、問題はありませんあなたの会社はnullですが、それをリセットした後もDBではnullです。これは、 'CompanyId'のような明示的なIdプロパティを持っている場合、それを維持する必要があるという事実によって引き起こされます。だから、あなたもdestinationModel.companyId = oldCompany.Id;

を呼び出す必要がdestinationModel.company = oldCompany;を呼び出すのに十分ではありませんそして、あなたはそれが既にあなたのための変更追跡をやっている文脈からあなたのdestのエンティティを取得したので、そのためEntityState.Modified設定は必要ありません。

EDIT:あなたの修正されたサンプル:

Mapper.CreateMap<MyModel, MyViewModel>(); 
Mapper.CreateMap<MyViewModel, MyModel>();  

//fetch the company 
MyModel dest = slaumaContext.MyModels.Include("Company").Where(c => c.Id == 18).First(); //hardcoded for demo 

var oldCompany = dest.Company; 

//creating a viewmodel 
MyViewModel source = new MyViewModel(); 
source.Company = null; 
source.CompanyId = null; 
source.Foo = "fdsfdf"; 

Mapper.Map(source, dest); // company null in dest 

dest.Company = oldCompany; 
dest.CompanyId = oldCompany.Id; 

slaumaContext.SaveChanges(); 
+0

私のCreateMapsを変更したいので、あなたのソリューションは良いですが、私が望んでいたものではありません。私の質問はまだ存在します:私は会社を割り当てても、なぜそれはnullを取るのですか? – Jaggu

+0

@Jaggu私は自分の答えを更新しました。 – nemesv

+0

偉大な答えnemesv。ほんとうにありがとう!スラウマの解説とあなたの答えの後に、物事は私にもっと明確になりました。 – Jaggu

2

@のnemesvの答えやAutoMapperのfinetuning第二EDITは私の意見に移動するための方法です。あなたは彼の答えを受け入れるべきです。私はあなたのコードがなぜ機能しないのか説明を追加するだけです(ただし、あなたのコードは状態を2回設定するだけです)。まず、問題はAutoMapperとは何の関係もなく、プロパティを手動で設定したときと同じ動作になります。

知るべき重要なことは、あなたが無効にしない場合は状態(Entry(dest).State = EntityState.Modified)を設定することのみ(特にそれがDbContext.ChangeTracker.DetectChanges()を呼び出して、コンテキストしかしState呼び出し、実際にいくつかの複雑な方法のためのプロパティのセッターにいくつかの内部フラグを設定していないということですAutoDetectChangesEnabled)。

だから、何最初のケースで起こる:

// ... 
Mapper.Map(source, dest); 
dest.Company = oldCompany; 

// at this point the state of dest EF knows about is still the state 
// when you loaded the entity from the context because you are not working 
// with change tracking proxies, so the values are at this point: 
// dest.CompanyId = null <- this changed compared to original value 
// dest.Company = company <- this did NOT change compared to original value 

// The next line will call DetectChanges() internally: EF will compare the 
// current property values of dest with the snapshot of the values it had 
// when you loaded the entity 
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified; 

// So what did EF detect: 
// dest.Company didn't change, but dest.CompanyId did! 
// So, it assumes that you have set the FK property to null and want 
// to null out the relationship. As a consequence, EF also sets dest.Company 
// to null at this point and later saves null to the DB 

後者の場合には何が起こる:だから

// ... 
Mapper.Map(source, dest); 

// Again in the next line DetectChanges() is called, but now 
// dest.Company is null. So EF will detect a change of the navigation property 
// compared to the original state 
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified; 

dest.Company = oldCompany; 

// Now DetectChanges() will find that dest.Company has changed again 
// compared to the last call of DetectChanges. As a consequence it will 
// set dest.CompanyId to the correct value of dest.Company 
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified; 

// dest.Company and dest.CompanyId will have the old values now 
// and SaveChanges() doesn't null out the relationship 

すると、これは実際には、通常の変更追跡行動ではなくEFのバグです。

私が気付いていることは、ビューで使用していないプロパティを持つViewModelがあることです。あなたのViewModelにCompanyCompanyIdがないと、すべての問題が消えてしまいます。 (または、少なくともAutoMapperが@nemesvのようにこれらのプロパティをマップしないように設定してください)

+0

それは邪魔ではありません。実際に私は私の編集、削除、追加操作のための共有ViewModelを持っています。私は会社や会社が必要ですが、編集や削除はしません。追加、編集、削除の間に共有ビューモデルを作成するのは悪い習慣ですか?私は追加、編集、削除操作ごとにクラスのオーバーロードを作成したくありませんでした。そこで私はクラスを再利用しようとしました。これは悪いですか? – Jaggu

+0

おそらく私はこの質問へのリンクを持つ別の質問としてそれを作成する必要があります。 – Jaggu

+1

@Jaggu ViewModelは通常、それらが提供するビューに合わせて調整されます。私の場合、ユーザーの要件、ビジネスルール、またはユーザーフレンドリーなものを作るなどの他のニュアンスは、それぞれのビューが多少異なることを示しています。したがって、それぞれのビューにViewModelがあるかもしれません。これは、少なくとも私のMVCでの経験でした。 – AaronLS

関連する問題