3

この単純なモデルとビューモデルのシナリオを考えてみましょう:

public class SomeModel
{
    public virtual Company company {get; set;}
    public string name {get; set;}
    public string address {get; set;}

    //some other few tens of properties
}

public class SomeViewModel
{
    public Company company {get; set;}
    public string name {get; set;}
    public string address {get; set;}
    //some other few tens of properties
}

発生する問題は次のとおりです。

会社が不要な編集ページがあるため、データベースから取得しません。フォームが送信されたら、次のことを行います。

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();

そして問題は、oldCompany を会社に割り当てても、savechanges 後にデータベースでまだ null です。

ノート:

これらの行を変更すると:

destinationModel.company = oldCompany;

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

context.SaveChanges();

これらに:

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

destinationModel.company = oldCompany;

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

context.SaveChanges();

状態を 2 回変更したことに注意してください。正常に動作します。何が問題になる可能性がありますか? これは 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();

        }
    }
}
4

2 に答える 2

3

Automapperは、デフォルトで、ソースインスタンスから宛先インスタンスまでのすべてのプロパティを常に更新します。したがって、Companyプロパティを上書きしたくない場合は、マッパーに対してこれを明示的に構成する必要があります。

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

これまでのところ、EFに関連するものはありません。ただし、これをEFで使用している場合は、ナビゲーションプロパティCompanyとCompanyIdを一貫して使用する必要があります。マッピング中にCompanyIdの宛先値も使用する必要があります。

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

編集:しかし、問題はあなたの会社がnullであるということではありませんが、リセットした後もDBではnullのままです。これは、「CompanyId」のような明示的なIdプロパティがある場合、それを維持する必要があるという事実が原因です。だから電話するだけでは十分ではありませんdestinationModel.company = oldCompany;あなたも電話する必要がありますdestinationModel.companyId = oldCompany.Id;

また、コンテキストからdestエンティティを取得したため、すでに変更の追跡が行われているため、EntityState.Modifiedを設定する必要はありません。

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

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();
于 2011-10-19T12:46:15.940 に答える
2

@nemesvの回答の2番目の編集またはAutoMapperの微調整が私の意見です。あなたは彼の答えを受け入れるべきです。コードが機能しない理由の説明を追加するだけです(ただし、状態を2回設定したコードは機能します)。まず、この問題は AutoMapper とは関係ありません。プロパティを手動で設定すると、同じ動作が得られます。

知っておくべき重要なことは、状態 ( Entry(dest).State = EntityState.Modified) を設定すると、コンテキストに内部フラグが設定されるだけでなく、State実際にはいくつかの複雑なメソッドを呼び出すためのプロパティ セッターが、特にDbContext.ChangeTracker.DetectChanges()(無効にしない場合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

2 番目のケースで何が起こるか:

// ...
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 のバグではありません。

私が気がかりなことの 1 つは、ビューで使用していないプロパティを明らかに持っている ViewModel があることです。ViewModel に問題がなくCompanyCompanyIdすべての問題が解消される場合。(または、@nemesv で示されているように、これらのプロパティをマップしないように少なくとも AutoMapper を構成します。)

于 2011-10-19T15:09:11.687 に答える