2011-11-13 7 views
3

EF 4.1コードファーストを使用して、単一のロールを持つユーザーの単純な関係をモデル化しようとしています。EF 4.1で1対多の関係に分離されたエンティティを追加するコードファースト

System.Data:私は(クライアント・サーバー・ラウンドトリップをシミュレートするために、異なるコンテキストを使用して)別のコンテキストに新しい役割を持つ既存のユーザーを保存しようとすると、私は次の例外を取得します。 Entity.Infrastructure.DbUpdateException:関係の外部キープロパティを公開していないエンティティを保存中にエラーが発生しました。 EntityEntriesプロパティは、単一のエンティティが例外のソースとして識別できないため、nullを返します。エンティティタイプに外部キーのプロパティを公開することで、保存時の例外処理を簡単に行うことができます。詳細については、InnerExceptionを参照してください。 --->System.Data.UpdateException: 'User_CurrentRole' AssociationSetからの関係が 'Added'状態にあります。多重度の制約が与えられると、対応する 'User_CurrentRole_Source'も 'Added'状態でなければなりません。

私が期待しているのは、新しい役割が作成され、既存のユーザーと関連付けられていることです。
私は間違って何をしていますか?これはEF 4.1のコードで最初に達成できますか?エラーメッセージは、ユーザーとロールの両方が追加された状態になる必要があることを示唆していますが、私は既存のユーザーを変更しています。

重要事項:エンティティの構造を変更しないようにしたい(エンティティに表示される外部キーのプロパティを導入するなど)。データベースには、外部キーを指すようにしたい役割(それ以外の方法ではありません)。私はまた、(別の方法がない限り)セルフトラッキングエンティティに移動する準備ができていません。ここで

は実体である:

public class User 
{ 
    public int UserId { get; set; } 
    public string Name { get; set; } 
    public Role CurrentRole { get; set; } 
} 

public class Role 
{ 
    public int RoleId { get; set; } 
    public string Description { get; set; } 
} 

そして、ここでは、私はマッピングを使用します:

public class UserInitializer : DropCreateDatabaseAlways<UserRolesContext> 
{ 
     protected override void Seed(UserRolesContext context) 
     { 
      context.Users.Add(new User() {Name = "Bob", 
              CurrentRole = new Role() {Description = "Builder"}}); 
      context.SaveChanges(); 
     } 
} 

そして最後に:

public class UserRolesContext : DbContext 
{ 
    public DbSet<User> Users { get; set; } 
    public DbSet<Role> Roles { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<User>().HasKey(u => u.UserId); 
     modelBuilder.Entity<Role>().HasKey(r => r.RoleId); 
     modelBuilder.Entity<User>().HasRequired(u => u.CurrentRole); 
    } 
} 

を私はこれでデータベースに事前ここで失敗したテストがあります:

[TestMethod] 
    public void CanModifyDetachedUserWithRoleAndReattach() 
    { 
     Database.SetInitializer<UserRolesContext>(new UserInitializer()); 
     var context = new UserRolesContext(); 

     // get the existing user 
     var user = context.Users.AsNoTracking().Include(c => c.CurrentRole).First(u => u.UserId == 1); 

     //modify user, and attach to a new role 
     user.Name = "MODIFIED_USERNAME"; 
     user.CurrentRole = new Role() {Description = "NEW_ROLE"}; 

     var newContext = new UserRolesContext(); 
     newContext.Users.Attach(user); 
     // attachment doesn't mark it as modified, so mark it as modified manually 
     newContext.Entry(user).State = EntityState.Modified; 
     newContext.Entry(user.CurrentRole).State = EntityState.Added; 

     newContext.SaveChanges(); 

     var verificationContext = new UserRolesContext(); 
     var afterSaveUser = verificationContext.Users.Include(c => c.CurrentRole).First(u => u.UserId == 1); 
     Assert.AreEqual("MODIFIED_USERNAME", afterSaveUser.Name, "User should have been modified"); 
     Assert.IsTrue(afterSaveUser.CurrentRole != null, "User used to have a role, and should have retained it"); 
     Assert.AreEqual("NEW_ROLE", afterSaveUser.CurrentRole.Description, "User's role's description should have changed."); 
    } 
} 
} 

確かにこれは、私がモデルマッピングを定義した方法で紛失していると思われるシナリオです。

答えて

6

EF状態モデルが壊れています。あなたのエンティティはCurrentRoleとマッピングされていますので、UserRoleなしで使用することはできません。 independent associationsも使用しました(あなたのエンティティに公開されているFKプロパティはありません)。つまり、役割とユーザーの関係は、その状態を持つ別の追跡対象のエントリです。既存のユーザーにロールを割り当てると、関係エントリの状態はに設定されますが、Deletedとしてマークしないかぎり、既存のUser(これは既に役割が割り当てられている必要があるため)ではできません。ユーザー)。これを分離シナリオで解決すると、very hardとなり、ラウンドトリップ中に古いロールに関する情報を渡し、手動で状態マネージャーまたはエンティティグラフ自体で再生する必要があるコードにつながります。何かのように:

Role newRole = user.CurrentRole; // Store the new role to temp variable 
user.CurrentRole = new Role { Id = oldRoleId }; // Simulate old role from passed Id 

newContext.Users.Attach(user); 
newCotnext.Entry(user).State = EntityState.Modified; 
newContext.Roles.Add(newRole); 

user.CurrentRole = newRole; // Reestablish the role so that context correctly set the state of the relation with the old role 

newContext.SaveChanges(); 

最も簡単な解決策は、データベースから古い状態をロードし、ロードされた(添付)1に新しい状態から変更をマージです。これは、FKプロパティを公開することによっても回避できます。

Btw。あなたのモデルは1対1ではなく1対1であり、複数のユーザーにロールを割り当てることができます.1対1の場合、新しいロールを作成する前に古いロールを削除する必要があるため、さらに複雑になります。

+0

ありがとうございますLadislavさん、私が記述したオブジェクトモデル(つまり、STEなし、FKプロパティなし)で、このシナリオをEFコードファーストで簡単にサポートしているとは言いがたいです。プロパティとしてFKを導入することは、変更を追跡して手動でマージするのと同様に、厄介な回避策のように思えます。幸いにも、これはプロジェクトの初期段階であるため、NHibernateを評価してより適切かどうかを確認することができます。 –

関連する問題