Отношения Entity Framework между различными DbContext и различными схемами

у меня есть класс Members в отдельном DbContext и отдельной библиотеке классов. Я планирую повторно использовать эту библиотеку классов в нескольких проектах и, чтобы помочь дифференцировать, я установил схему базы данных как "acc". Я тщательно протестировал эту библиотеку и могу добавлять, удалять и обновлять членов в acc.Таблица элементов.

класс Гильдии как такие:

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.Члены.

я получил ссылку на проект EF члена и добавил отображение в класс Members в DbContext, частью которого является класс Guild. modelBuilder.Configurations.Add(new MemberMapping()); (Не уверен, что это лучший способ.)

это привело к этой ошибке:

{"The member with identity 'GuildProj.Data.EF.Guild_Members' does not exist in the metadata collection.rnParameter name: identity"}

как я могу использовать внешний ключ между этими двумя таблицы пересекают DbContexts и с различными схемами базы данных?

обновление

я сузил причину ошибки. Когда я создаю новую гильдию, я устанавливаю идентификатор члена лидера гильдии в MemberID. Это прекрасно работает. Но когда я затем пытаюсь добавить объект члена этого лидера в список членов Гильдии (членов), это и вызывает ошибку.

обновление 2

вот код того, как я создаю контекст что класс Гильдии в игре. (По просьбе Хусейна Халиля)

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, Role и Permission в DbContext, который содержится Guild. Мне пришлось добавить роль и разрешение, потому что у члена было List<Role> Roles и каждая роль была List<Permission> Permissions.

это приблизило меня к решению. Я все еще получаю ошибки например:

{"The member with identity 'GuildProj.Data.EF.Member_Roles' does not exist in the metadata collection.rnParameter name: identity"}

здесь, когда вы вытаскиваете член из Session, вы получаете что-то вроде этого:

System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C

Entity Framework, похоже, не очень хорошо с этим справляется. Почему? Я не уверен, но я думаю, что это потому, что ContextM создает прокси-сервер члена и, клонируя член в новый объект-член, ContextM больше не имеет ассоциации. Это, я думаю, позволяет ContextG свободно использовать новый объект-член. Я попытался установить ProxyCreationEnabled = false в моих DbContexts, но объект-член, извлеченный из сеанса, продолжал иметь тип System.Данные.Сущность.DynamicProxies.Член.

Итак, то, что я сделал, было:

Member member = new Member((Member)Session[Constants.UserSession]);

мне пришлось клонировать каждого Role и друг Permission а также внутри их соответствующих конструкторов.

это дало мне 99% пути туда. Мне пришлось изменить свое РЕПО и как я спасал

4 ответов


это рабочий код:

в сборке "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)

это также работает при связывании существующих объектов.


оригинальный ответ на более общий случай:

вы не можете комбинировать типы в разных контекстах в графе объекта. Это означает, что вы не можете сделать что-то вроде

from a in context.As
join b in context.Bs on ...

...поскольку всегда есть один контекст, который должен создать весь SQL-запрос, поэтому он должен иметь всю необходимую информацию о сопоставлении.

вы можете зарегистрировать один и тот же тип в двух разных контекстах, даже из разных сборок. Так что вы могли бы map Member в контексте Guild's ассамблеи, давайте назовите это contextG, но только если

  1. Member не относится к другим типам, что не сопоставленные в contextG. Это может означать, что свойства навигации в Member должно игнорироваться явно.
  2. Member не может ссылаться на типы в contextG, потому что эти типы не являются частью Member's контекст.

если любое из этих условий не может быть выполнено, лучшее, что вы можете сделать, это создать новый Member класс Guild'ы сборка и регистрация ее отображения в контексте. Возможно, вы хотите использовать другое имя, чтобы предотвратить двусмысленность, но это единственная оставшаяся альтернатива.


Я обнаружил, что когда у меня проблемы с сущностью и построением отношений, это часто потому, что я иду против потока структуры или пытаюсь построить отношения или абстракции, которые технически не сформированы. Здесь я бы предложил сделать быстрый шаг назад и проанализировать несколько вещей, прежде чем углубляться в эту конкретную проблему.

во-первых, мне интересно, почему вы используете другую схему здесь для того, что, вероятно, одно приложение доступ к графу объектов. Несколько схем могут быть полезны в некотором контексте, но я думаю, что Брент Озар делает очень заметный момент в этом статьи. Учитывая эту информацию, я склонен сначала предложить объединить несколько схем в одну и перейти к использованию одного контекста БД для базы данных.

следующее, что нужно решить, это график объектов. По крайней мере, для меня самая большая борьба с моделированием данных-это когда я сначала не понимаю, какие вопросы приложение имеет базу данных. Я имею в виду, что сначала выясните, какие данные нужны приложению в его различных контекстах, а затем посмотрите, как оптимизировать эти структуры данных для производительности в реляционном контексте. Посмотрим, как это можно сделать ...

исходя из вашей модели я вижу, что у нас есть несколько ключевых терминов/объектов в домене:

  • сбор Гильдии
  • собрания участников
  • коллекция лидеров гильдий.

кроме того, у нас есть некоторые правила, которые должны быть реализованы:

  • Гильдия может иметь 1 лидера (и, возможно, более 1?)
  • лидер Гильдии должен быть членом
  • Гильдия имеет список из 0 или более членов
  • член может принадлежать к гильдии (и, возможно, более 1?)

Итак, учитывая эту информацию, давайте рассмотрим, какие вопросы может иметь ваше приложение этой модели данных. Я приложение может:

  • найдите элемент и посмотрите его свойства
  • посмотрите на члена и увидеть их свойства, и что они являются лидером гильдии
  • посмотрите гильдию и увидеть список всех ее членов
  • посмотреть в гильдию и увидеть список лидеров гильдии
  • посмотрите список всех лидеров гильдий

хорошо, теперь мы можем перейти к латунным гвоздям, как говорится...

в использование таблицы join между гильдиями и членами является оптимальным в этом случае. Это даст вам возможность иметь членов в нескольких гильдиях или без гильдий и обеспечить низкую стратегию обновления блокировки-так хорошо!

переход на лидеров гильдий есть несколько вариантов, которые могут иметь смысл. Даже при том, что, возможно, никогда не будет случая для сержантов гильдии, я думаю, что имеет смысл рассмотреть новую сущность, называемую лидером Гильдии. Этот подход допускает несколько вариантов. Вы можете кэш в приложение список лидеров гильдий идентификаторы, так что вместо того, чтобы сделать поездку в дБ разрешаю гильдии действия руководителя вы можете нажмите локального кэша приложений, который имеет только лидер списка идентификаторов и не вся руководителем объекта; и наоборот, можно попасть в список лидеров гильдии и в зависимости от запроса направление, вы можете нажать кластеризованных индексов на основных сущностей или удобной в обслуживании промежуточного индекса на совместную кооперацию.

Как я заметил в начале этого пути, чтобы долго "ответ", когда я сталкиваюсь с такими проблемами, как ваши, это обычно потому, что я иду против зерна сущности. Я призываю вас пересмотреть свою модель данных и как с более низким подходом трения - потерять множественную схему и добавить промежуточный объект guild_leader. Ура!


если вы явно не скажете, что Member организация должна быть сопоставлена с acc.Members, EF будет ожидать, что он будет в dbo - схемы Members таблица. Для этого вам нужно предоставить либо EntityTypeConfiguration для этого типа или комментировать его с System.ComponentModel.DataAnnotations.Schema.TableAttribute как [Table("acc.Members")]


я отвечаю на ваш обновленный вопрос :

попробуйте использовать эту строку перед обновлением контекста

context.Entry(Guild.Members).State = Entity.EntityState.Unchanged

это решит ошибку, которую вы