web-dev-qa-db-ja.com

Entity Frameworkコードファースト-複雑なプロパティをこの方法で更新できないのはなぜですか?

私はEntityFramework 4.1(コードファースト)を使用して小さなサンプルプロジェクトに取り組んでいます。私のクラスは次のようになります。

public class Context : DbContext
{
    public IDbSet<Person> People { get; set; }
    public IDbSet<EmployeeType> EmployeeTypes { get; set; }
}

public class Person
{
    [Key]
    public int Key { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    virtual public EmployeeType EmployeeType { get; set; }
}

public class EmployeeType
{
    [Key]
    public int Key { get; set; }
    public string Text { get; set; }

    virtual public ICollection<Person> People { get; set; }
}

いくつかのEmployeeTypes( "first"、 "second")をデータベースに保存し、最初のタイプのPersonを保存しました。次に、Personを変更します。 Personをロードし、プロパティを変更してから保存することで、これを実行できることを知っています。しかし、代わりにやりたいことは、うまくいくはずだと私には思えますが、これは次のとおりです。

var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person { 
            Key = originalKey,       // same key
            FirstName = "NewFirst",  // new first name
            LastName = "NewLast",    // new last name
            EmployeeType = e };      // new employee type
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();

奇妙なことに、これはFirstNameとLastNameを変更しますが、EmployeeTypeは変更しません。新しいコンテキストを取得してこのPersonをリクエストすると、このコードが実行される前と同じように、EmployeeTypeは「first」に設定されたままになります。

スカラープロパティだけでなく、ナビゲーションプロパティを更新するにはどうすればよいですか?(EmployeeTypeの場合、実際に変更する必要があるのは外部キーだけなので、これは特に不可解です。 Personテーブルであり、そのキーはスカラープロパティです。)

(ちなみに、最初にPersonを取得してから、プロパティを1つずつ変更することでこれを実行できることはわかっていますが、ASP.NET MVCでモデルバインディングを使用しているため、この方法の方が簡単なようです。更新されたpersonオブジェクトはすでにmy POSTメソッドにあります。)

28
Ryan Lundy

別の方法で試すことができます。

var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person { 
            Key = originalKey,       // same key
            FirstName = "NewFirst",  // new first name
            LastName = "NewLast"};   // new last name
c.People.Attach(p); // Attach person first so that changes are tracked 
c.Entry(p).Reference(e => e.EmployeeType).Load();               
p.EmployeeType = e; // Now context should know about the change
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();

他のアプローチは、次のようにPersonエンティティの外部キーを公開することです。

public class Person
{
    [Key]
    public int Key { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [ForeignKey("EmployeeType")]
    public int EmployeeTypeKey { get; set; }
    public virtual EmployeeType EmployeeType { get; set; }
}

これにより、PersonEmployeeTypeの間の関係のタイプが独立アソシエーションから外部キーアソシエーションに変更されます。ナビゲーションプロパティを割り当てる代わりに、外部キープロパティを割り当てます。これにより、現在のコードでリレーションを変更できます。

問題は、独立した関連付け(外部キープロパティを使用しない関連付け)が、状態マネージャー/変更トラッカーで個別のオブジェクトとして処理されることです。したがって、人の変更は既存の関係の状態に影響を与えず、新しい関係も設定しませんでした。 MSDNで質問しました DbContext APIでそれを行う方法ですが、DbContextObjectContextにキャストし、ObjectStateManagerChangeRelationshipStateを使用した場合にのみ可能です。

19
Ladislav Mrnka

EFの方法でそれを行うための12の異なる方法を試した後、私がやろうとしていることを行うための合理的なEFコードファーストの方法はないと結論付けました。そこで、リフレクションを使用しました。 DbContextから継承するクラス用にこのメソッドを作成しました。

public void UpdateFrom<T>(T updatedItem) where T : KeyedItem
{
    var originalItem = Set<T>().Find(updatedItem.Key);
    var props = updatedItem.GetType().GetProperties(
        BindingFlags.Public | BindingFlags.Instance);
    foreach (var prop in props)
    {
        var value = prop.GetValue(updatedItem, null);
        prop.SetValue(originalItem, value, null);
    }
}

私のすべてのオブジェクトは抽象クラスを継承し、共通の主キープロパティを持っているため、渡されたものと同じキーを持つ既存のオブジェクトを検索し、新しいオブジェクトから既存のオブジェクトを更新します。 SaveChangesは後で呼び出す必要があります。

2
Ryan Lundy

これはコレクションで機能しますが、もっと良い方法が必要だと思います。


var properties = typeof(TEntity).GetProperties();
foreach (var property in properties)
{
    if (property.GetCustomAttributes(typeof(OneToManyAttribute), false).Length > 0)
    {
        dynamic collection = db.Entry(e).Collection(property.Name).CurrentValue;
        foreach (var item in collection)
        {
            if(item.GetType().IsSubclassOf(typeof(Entity))) 
            {
                if (item.Id == 0)
                {
                    db.Entry(item).State = EntityState.Added;
                }
                else
                {
                    db.Entry(item).State = EntityState.Modified;
                }
             }
        }
    }
}
db.Entry(e).State = EntityState.Modified;            
db.SaveChanges();

1
David Wick