つまり、メンバーとギルドという2つの主要なオブジェクトがあります。 1人のメンバーがギルドを所有でき、1人のギルドが複数のメンバーを持つことができます。
別のDbContextと別のクラスライブラリにMembersクラスがあります。このクラスライブラリを複数のプロジェクトで再利用する予定であり、区別しやすくするために、データベーススキーマを「acc」に設定します。このライブラリを広範囲にテストし、acc.Membersテーブルのメンバーを追加、削除、および更新できます。
ギルドクラスはそのようなものです:
_public class Guild
{
public Guild()
{
Members = new List<Member>();
}
public int ID { get; set; }
public int MemberID { get; set; }
public virtual Member LeaderMemberInfo { get; set; }
public string Name { get; set; }
public virtual List<Member> Members { get; set; }
}
_
のマッピング:
_internal class GuildMapping : EntityTypeConfiguration<Guild>
{
public GuildMapping()
{
this.ToTable("Guilds", "dbo");
this.HasKey(t => t.ID);
this.Property(t => t.MemberID);
this.HasRequired(t => t.LeaderMemberInfo).WithMany().HasForeignKey(t => t.MemberID);
this.Property(t => t.Name);
this.HasMany(t => t.Members).WithMany()
.Map(t =>
{
t.ToTable("GuildsMembers", "dbo");
t.MapLeftKey("GuildID");
t.MapRightKey("MemberID");
});
}
}
_
しかし、新しいギルドを作成しようとすると、dbo.Membersがないと表示されます。
メンバーのEFプロジェクトへの参照を取得し、Guildクラスが含まれているDbContextのMembersクラスへのマッピングを追加しました。 modelBuilder.Configurations.Add(new MemberMapping());
(それが最善の方法かどうかはわかりません。)
これにより、次のエラーが発生しました。
_{"The member with identity 'GuildProj.Data.EF.Guild_Members' does not exist in the metadata collection.\r\nParameter name: identity"}
_
これら2つのテーブル間でDbContext間で、異なるデータベーススキーマを使用して外部キーを利用するにはどうすればよいですか?
[〜#〜] update [〜#〜]
エラーの原因を絞り込みました。新しいギルドを作成するときは、ギルドリーダーのメンバーIDをMemberIDに設定します。これは正常に機能します。しかし、そのリーダーのメンバーオブジェクトをギルドのメンバーリスト(メンバー)に追加しようとすると、それがエラーの原因になります。
更新2
これは、Guildクラスが含まれるコンテキストを作成する方法のコードです(Hussein Khalilの要求による)
_public class FSEntities : DbContext
{
public FSEntities()
{
this.Configuration.LazyLoadingEnabled = false;
Database.SetInitializer<FSEntities>(null);
}
public FSEntities(string connectionString)
: base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new GuildMapping());
modelBuilder.Configurations.Add(new KeyValueMappings());
modelBuilder.Configurations.Add(new LocaleMappings());
modelBuilder.Configurations.Add(new MemberMapping());
}
public DbSet<Guild> Guilds { get; set; }
public DbSet<KeyValue> KeyValues { get; set; }
public DbSet<Locale> Locales { get; set; }
}
_
これは私がリポジトリに保存する方法です:
_ public async Task CreateGuildAsync(Guild guild)
{
using (var context = new FSEntities(_ConnectionString))
{
context.Entry(guild.Members).State = EntityState.Unchanged;
context.Entry(guild).State = EntityState.Added;
await context.SaveChangesAsync();
}
}
_
最終的な解決策
そのため、Member
を含むDbContextのRole
、Permission
、およびGuild
にマッピングを追加する必要がありました。メンバーには_List<Role> Roles
_があり、各ロールには_List<Permission> Permissions
_があるため、ロールと権限を追加する必要がありました。
これは私を解決策に近づけました。私はまだ次のようなエラーが発生していました:
_{"The member with identity 'GuildProj.Data.EF.Member_Roles' does not exist in the metadata collection.\r\nParameter name: identity"}
_
ここで、Session
からMemberをプルすると、次のようになります。
_System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C
_
EntityFrameworkはこれでうまく機能していないようです。どうして?よくわかりませんが、ContextMがMemberのプロキシを作成し、Memberを新しいMemberオブジェクトに複製することにより、ContextMに関連付けがなくなったためだと思います。これにより、ContextGは新しいMemberオブジェクトを自由に使用できるようになると思います。 DbContextsでProxyCreationEnabled = falseを設定しようとしましたが、SessionからプルされたMemberオブジェクトのタイプがSystem.Data.Entity.DynamicProxies.Memberのままでした。
だから、私がしたことは:
_Member member = new Member((Member)Session[Constants.UserSession]);
_
それぞれのコンストラクター内で、各Role
と各Permission
のクローンを作成する必要がありました。
これで99%の道のりができました。リポジトリとGuild
オブジェクトの保存方法を変更する必要がありました。
_ context.Entry(guild.LeaderMemberInfo).State = EntityState.Unchanged;
foreach(var member in guild.Members)
{
context.Entry(member).State = EntityState.Unchanged;
}
context.Entry(guild).State = EntityState.Added;
await context.SaveChangesAsync();
_
これは機能するコードです:
アセンブリ「M」の場合:
_public class Member
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MemberMapping : EntityTypeConfiguration<Member>
{
public MemberMapping()
{
this.HasKey(m => m.Id);
this.Property(m => m.Name).IsRequired();
}
}
_
アセンブリ「G」の場合:
Guild
クラスGuild
マッピングにはWillCascadeOnDelete(false)
が含まれていますが、LeaderMemberInfo
マッピング。modelBuilder.Configurations.Add(new GuildMapping());
およびmodelBuilder.Configurations.Add(new MemberMapping());
コード:
_var m = new Member { Name = "m1" };
var lm = new Member { Name = "leader" };
var g = new Guild { Name = "g1" };
g.LeaderMemberInfo = lm;
g.Members.Add(lm);
g.Members.Add(m);
c.Set<Guild>().Add(g);
c.SaveChanges();
_
実行されたSQL:
_INSERT [dbo].[Members]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[Members]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'leader' (Type = String, Size = -1)
INSERT [dbo].[Guilds]([MemberID], [Name])
VALUES (@0, @1)
SELECT [ID]
FROM [dbo].[Guilds]
WHERE @@ROWCOUNT > 0 AND [ID] = scope_identity()
-- @0: '1' (Type = Int32)
-- @1: 'g1' (Type = String, Size = -1)
INSERT [dbo].[GuildsMembers]([GuildID], [MemberID])
VALUES (@0, @1)
-- @0: '1' (Type = Int32)
-- @1: '1' (Type = Int32)
INSERT [dbo].[Members]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[Members]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'm1' (Type = String, Size = -1)
INSERT [dbo].[GuildsMembers]([GuildID], [MemberID])
VALUES (@0, @1)
-- @0: '1' (Type = Int32)
-- @1: '2' (Type = Int32)
_
これは、既存のオブジェクトを関連付けるときにも機能します。
より一般的なケースの元の回答:
異なるコンテキストのタイプを1つのオブジェクトグラフに結合することはできません。つまり、あなたは次のようなことをすることはできません
_from a in context.As
join b in context.Bs on ...
_
... SQLクエリ全体を作成する必要があるコンテキストは常に1つあるため、必要なすべてのマッピング情報が含まれている必要があります。
ただし、異なるアセンブリからでも、同じタイプを2つの異なるコンテキストに登録できます。したがって、Member
のアセンブリのコンテキストでGuild
をマップできます。これを、contextG
と呼びましょう。ただし、次の場合に限ります。
Member
は、ないcontextG
にマップされている他のタイプを参照しません。これは、Member
のナビゲーションプロパティを明示的に無視する必要があることを意味する場合があります。Member
はcontextG
の型を参照できません。これは、これらの型がMember
のコンテキストの一部ではないためです。これらの条件のいずれかを満たせない場合は、Member
のアセンブリに新しいGuild
クラスを作成し、そのマッピングをコンテキストに登録するのが最善の方法です。あいまいさを防ぐために別の名前を使用したい場合もありますが、これが唯一の選択肢です。
Member
エンティティを_acc.Members
_にマップする必要があることを明示的に指定しない限り、EFはそれがdbo
スキーマMembers
テーブルにあることを期待します。このためには、このタイプにEntityTypeConfiguration
を指定するか、[Table("acc.Members")]
のように_System.ComponentModel.DataAnnotations.Schema.TableAttribute
_で注釈を付ける必要があります。
エンティティと関係の構築に問題がある場合、フレームワークの流れに逆らったり、技術的に整形式ではない関係や抽象化を構築しようとしていることが原因であることがよくあります。ここで提案するのは、この特定の問題を詳しく調べる前に、少し前に戻っていくつかのことを分析することです。
まず、オブジェクトグラフにアクセスする単一のアプリケーションである可能性が高いものに対して、ここで異なるスキーマを使用している理由に興味があります。状況によっては複数のスキーマが役立つ場合がありますが、Brent Ozarはこの点で非常に重要な点を示していると思います 記事 。その情報を踏まえて、最初に、複数のスキーマを1つにマージし、データベースに単一のDBコンテキストを使用するようにプッシュすることをお勧めします。
次に取り組むべきことは、オブジェクトグラフです。少なくとも私にとって、データモデリングで私が抱えていた最大の苦労は、アプリケーションがデータベースに対してどのような質問をしているのかを最初に理解していないときです。これが意味するのは、最初にアプリケーションがさまざまなコンテキストでどのデータを必要としているかを把握し、次にリレーショナルコンテキストでのパフォーマンスのためにそれらのデータ構造を最適化する方法を検討することです。それがどのように行われるか見てみましょう...
上記のモデルに基づいて、ドメインにいくつかの重要な用語/オブジェクトがあることがわかります。
さらに、実装する必要のあるビジネスルールがいくつかあります。
したがって、この情報を前提として、アプリケーションがこのデータモデルに対してどのような質問をする可能性があるかを調べてみましょう。私はアプリケーションができます:
さて、彼らが言うように、今、私たちは真鍮の鋲に取り掛かることができます...
この場合、ギルドとメンバー間の結合テーブルの使用が最適です。それはあなたに複数のギルドにメンバーを置くか、ギルドを持たない能力を提供し、低ロックの更新戦略を提供します-とても良い電話です!
ギルドリーダーに移ると、理にかなっているかもしれないいくつかの選択肢があります。ギルド軍曹というケースは決してないかもしれませんが、ギルドリーダーと呼ばれる新しい組織を検討することは理にかなっていると思います。このアプローチで可能なことはいくつかあります。ギルドリーダーIDのリストをアプリにキャッシュできるため、リーダーが実行するギルドアクションを承認するためにデータベースにアクセスするのではなく、リーダーIDリストのみを持ち、リーダーオブジェクト全体を持たないローカルアプリケーションキャッシュをヒットできます。逆に、ギルドのリーダーのリストを取得でき、クエリの方向に関係なく、コアエンティティのクラスター化インデックスまたは結合エンティティの保守が容易な中間インデックスをヒットできます。
この方法の冒頭で長い「答え」について述べたように、私があなたのような問題に遭遇したとき、それは通常、私がエンティティの粒に反対しているためです。データモデルと、摩擦の少ないアプローチでどのように考えるかを再考することをお勧めします。複数のスキーマを緩め、中間のguild_leaderオブジェクトを追加します。乾杯!
私はあなたの更新された質問に答えています:
コンテキストを更新する前に、この行を使用してみてください
context.Entry(Guild.Members).State = Entity.EntityState.Unchanged
これはあなたが持っているエラーを解決します