2015-06-02 6 views
13

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

У меня есть класс членов в отдельном 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-члена и добавил сопоставление классу-члену в 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 лидера к списку членов Гильдии (членов), это и вызывает ошибку.

UPDATE 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(); 
     } 
    } 

FINAL Решение

Итак, мне пришлось добавить сопоставления в 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 

Entity 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(); 

ответ

8

Это работает код:

В сборочном «М»:

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(); 
    } 
} 

В assemby «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 «s, давайте назовем его contextG, но только если

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

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

+1

Нельзя ли использовать тот же класс 'Member'? Отображение будет различным между контекстами, и объект 'Member' не сможет быть перенесен между контекстами, не будучи отделенным от первого, но это кажется возможным. – jjj

+0

@jjj Да, но я предполагаю, что 'Member' будет иметь другие ассоциации, которые не должны отображаться. Если 'Member' является POCO, а отображения - свободным отображением, то это должно быть возможно, явно оставив свойства навигации без изменений. Но я думаю, что запутать объекты 'Member' в одном приложении, которые могут быть реализованы в разных контекстах. –

+0

Не имеет значения, что свойство GuildLeader, которое из типа Member правильно сохраняет базу данных, но свойство List не работает. И только сохранение идентификатора. – ScubaSteve

1

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

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

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

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

  • Коллекция Гильдий
  • Коллекция членов
  • Коллекция лидеров гильдии.

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

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

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

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

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

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

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

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

1

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

+0

Внутри моего члена класса отображения, я сказать ему для использования схемы acc. Все в исходном коде участника работает, например: добавление, удаление, обновление членов. Когда я пытаюсь втянуть его в другой DbContext, у меня возникают проблемы. – ScubaSteve

+0

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

0

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

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

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

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

+0

вы можете обновить свой вопрос с помощью своего кода, и я дам вам знать, что именно нужно было разместить эту строку ocde –

+0

Я пробовал это, но я получил эту ошибку: {«Список типов объектов'1 не является частью модели для текущий контекст. "} – ScubaSteve

+0

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

Смежные вопросы