5

私はEFCodeFirstを使用しており、次のように2つのクラスが定義されています。

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
}

[Table("Visitors")]
public class Visitor : User
{
    public Visitor()
    {
        Favourites = new List<Building>();
    }
    public virtual IList<Building> Favourites { get; set; }
}

これは、Table-Per-Type継承を使用し、DBスキーマを次のように定義します。

Users Table
    Id int PK
    Username nvarchar(max)
    Email nvarchar(max)
Visitors Table
    Id int PK (FK to Users table)

これはまさに私がそれを構造化するために望んでいた方法です。ここで私の質問は、Userオブジェクトを作成してDBに保存した場合、後でそれを訪問者に拡張するにはどうすればよいですか(必要な場合)。ユーザーを削除して新しい訪問者を作成する必要がありますか?または、ユーザーをビジターオブジェクトにキャストすると、ユーザーテーブルのエントリがそのまま残り、ユーザーを参照する新しいエントリがビジターテーブルに追加されますか?以下のコードのようなものですか?

Context.Set<User>().Add(new User(){Id=1, Username="Bob", Email="bob@mail.bob"});
Context.SaveChanges();

//and elsewhere in the project I want to do this sort of thing:
Context.Set<Visitor>().Where(v=>v.Id == 1).FirstOrDefault().Favourites.Add(someFavouriteBuilding); //This obviously doesn't work, because the FirstOrDefault call returns null, so it will throw an exception
Context.SaveChanges();

//or maybe this can be modified slightly to work?:
var visitor = Context.Set<Visitor>().Where(v=>v.Id == 1).FirstOrDefault();
if (visitor==null)
{
    visitor = new Visitor(Context.Set<User>().Where(u=>u.Id == 1).FirstOrDefault()); // this contructor copies all the property values accross and returns a new object
}
visitor.Favourites.Add(someFavouriteBuilding); //This obviously doesn't work either
var entry = Context.Entry(visitor);
entry.State = EntityState.Modified;//here it throws this error: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Context.SaveChanges();

上記のコードの2番目のアプローチは、コンテキストに正しくアタッチすることしかできない場合に機能すると思います。とにかく、上記のコードは、私が達成しようとしていることを示すためだけのものです。私はそれが機能しないことを知っています。誰かがもっとエレガントなアプローチを提案できますか?

ありがとうございました

4

2 に答える 2

11

もうすぐそこにました...重要なのは、既存のエンティティを切り離してから、新しいエンティティアタッチすることです。

次に例を示します。

using System.Data;
using System.Data.Entity;
using System.Diagnostics;

public class Animal
{
    public long Id { get; set; }
}

public class Dog : Animal
{
}

public class AnimalsContext : DbContext
{
    public DbSet<Animal> Animals { get; set; }
}


public class Tester
{
    public void Test()
    {
        var context = new AnimalsContext();


        var genericAnimal = new Animal();
        context.Animals.Add(genericAnimal);
        context.SaveChanges();


        // Make a new clean entity, but copy the ID (important!)
        var dog = new Dog { Id = genericAnimal.Id, };

        // Do the old switch-a-roo -- detach the existing one and attach the new one
        // NOTE: the order is important!  Detach existing FIRST, then attach the new one
        context.Entry(genericAnimal).State = EntityState.Detached;
        context.Entry(dog).State = EntityState.Modified;
        context.SaveChanges();


        var thisShouldBeADog = context.Animals.Find(genericAnimal.Id);

        // thisShouldBeADog is indeed a Dog!
        Debug.Assert(thisShouldBeADog is Dog);

        // And, of course, all the IDs match because it's the same entity
        Debug.Assert((genericAnimal.Id == dog.Id) && (dog.Id == thisShouldBeADog.Id));
    }
}
于 2013-08-30T19:11:35.047 に答える
0

新しいレコードを作成し、古いレコードを削除します。既存のオブジェクトのクラスを効果的に変更したり、データベースキーを再利用しようとするレコードを削除して作成したりするべきではないと思います。

データベースの主キーは無意味である必要があります。レコードに意味のあるIDを割り当てる必要がある場合は、そのための新しいフィールドを追加します。あなたのスタックオーバーフローIDについて考えてみてください、私はそれが彼らのデータベースの主キーではないに違いありません。

于 2014-09-26T19:45:42.653 に答える