Подтвердить что ты не робот

Связи Entity Framework между различными DbContext и различными схемами

Итак, у меня есть два основных объекта: член и гильдия. Один член может владеть Гильдией, и одна Гильдия может иметь несколько Членов.

У меня есть класс Members в отдельном DbContext и отдельной библиотеке классов. Я планирую повторно использовать эту библиотеку классов в нескольких проектах и ​​помочь дифференцировать, я установил схему базы данных "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 и добавил сопоставление с классом Members в DbContext, частью которого является Гильдия. 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"}

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

UPDATE

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

ОБНОВЛЕНИЕ 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.\r\nParameter name: identity"}

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

System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C

Сущность Framework, похоже, не очень хорошо справляется с этим. Зачем? Я не уверен, но я думаю, что это потому, что ContextM создает прокси-член Member и клонирует член в новый объект-член, ContextM больше не имеет ассоциации. Это, я думаю, позволяет ContextG свободно использовать новый объект Member. Я попытался установить ProxyCreationEnabled = false в своих DbContexts, но объект 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();
4b9b3361

Ответ 1

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

В сборке "М":

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, поэтому он должен иметь всю необходимую информацию о сопоставлении.

Вы можете зарегистрировать один и тот же тип в двух разных контекстах, хотя и с разных сборок. Таким образом, вы можете сопоставить Member в контексте сборки Guild, позвоните ему contextG, но только если

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

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

это решит вашу ошибку