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

Отношения "многие ко многим" слева и справа перевернули после обновления Entity Framework 5

У меня есть код, который сохраняет многие и многие отношения в коде. Он отлично работает с Entity Framework 4.1, но после обновления до Entity Framework 5 он не работает.

Я получаю следующую ошибку:

Оператор INSERT противоречил ограничению FOREIGN KEY "FK_WebUserFavouriteEvent_Event". Конфликт произошел в базе данных "MainEvents", в таблице "dbo.Event", в столбце "Id".

Я использую объекты POCO с пользовательскими сопоставлениями. Стандартное поле и сопоставления отношения "один к одному", похоже, работают нормально.

UPDATE

Хорошо, поэтому у меня установлен SQL Profiler и сюжет утолщен...

exec sp_executesql N'insert [dbo].[WebUserFavouriteEvent]([WebUserId], [EventId])
values (@0, @1)
',N'@0 int,@1 int',@0=1820,@1=14

Это означает:

WebUserId = @0 = 1820
EventId = @1 = 14

Интересно, что EF5, похоже, перевернул внешние ключи вокруг... WebUserId должен быть 14, а EventId должен быть 1820, а не наоборот, как сейчас.

Я просмотрел код сопоставления, и я на 99%. Я все правильно настроил. Для получения дополнительной информации см. Entity Framework Fluent API - Отношения статьи MSDN.

ПРИМЕЧАНИЕ. Я также обнаружил, что это также не ограничивается сохранением, также нарушены SELECT.

Здесь весь соответствующий код:

Уровень обслуживания

public void AddFavEvent(WebUser webUser, Event @event)
{
    webUser.FavouriteEvents.Add(@event);

    _webUserRepo.Update(webUser);
}

Repository

public void Update<T>(params T[] entities)
    where T : DbTable
{
    foreach (var entity in entities)
    {
        entity.UpdatedOn = DateTime.UtcNow;
    }

    _dbContext.SaveChanges();
}

ПРИМЕЧАНИЕ.. Я использую 1 подход DataContext для запроса, поэтому webUser и @event были бы загружены из того же контекста, что и в _webUserRepo.

Объекты (не беспокойтесь о материалах DbTable)

public class Event : DbTable
{
    //BLAH
    public virtual ICollection<WebUser> FavouriteOf { get; set; }
    //BLAH
}

public class WebUser : DbTable
{
    //BLAH
    public virtual ICollection<Event> FavouriteEvents { get; set; }
    //BLAH
}

<сильного > Отображение

public class EventMapping : DbTableMapping<Event>
{
    public EventMapping()
    {
        ToTable("Event");
        //BLAH
        HasMany(x => x.FavouriteOf)
            .WithMany(x => x.FavouriteEvents)
            .Map(x =>
                     {
                         x.MapLeftKey("EventId");
                         x.MapRightKey("WebUserId");
                         x.ToTable("WebUserFavouriteEvent");
                     });
    }
}

public class WebUserMapping : DbTableMapping<WebUser>
{
    public WebUserMapping ()
    {
        HasMany(x => x.FavouriteEvents)
            .WithMany(x => x.FavouriteOf)
            .Map(m =>
                     {
                         m.MapLeftKey("WebUserId");
                         m.MapRightKey("EventId");
                         m.ToTable("WebUserFavouriteEvent");
                     });
    }
}
4b9b3361

Ответ 1

Глядя на это, я подозреваю, что проблема может быть вызвана тем фактом, что вы сопоставляете одно и то же отношение дважды. И вы сопоставляете его в другом порядке.

Я сделал простой тест, где я сначала сопоставил взаимосвязь один раз:

    class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
        var p = new Parent();
        var c = new Child();
        using (var db = new Context())
        {
            db.Parents.Add(new Parent());
            db.Parents.Add(p);

            db.Children.Add(c);
            db.SaveChanges();
        }

        using (var db = new Context())
        {
            var reloadedP = db.Parents.Find(p.ParentId);
            var reloadedC = db.Children.Find(c.ChildId);

            reloadedP.Children = new List<Child>();
            reloadedP.Children.Add(reloadedC);

            db.SaveChanges();
        }

        using (var db = new Context())
        {
            Console.WriteLine(db.Children.Count());
            Console.WriteLine(db.Children.Where(ch => ch.ChildId == c.ChildId).Select(ch => ch.Parents.Count).First());
            Console.WriteLine(db.Parents.Where(pa => pa.ParentId == p.ParentId).Select(pa => pa.Children.Count).First());
        }
    }
}

public class Parent
{
    public int ParentId { get; set; }
    public ICollection<Child> Children { get; set; }

}

public class Child
{
    public int ChildId { get; set; }
    public ICollection<Parent> Parents { get; set; }
}

public class Context : DbContext
{
    public Context() : base("data source=Mikael-PC;Integrated Security=SSPI;Initial Catalog=EFTest")
    {

    }

    public IDbSet<Child> Children { get; set; }
    public IDbSet<Parent> Parents { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Child>()
            .HasMany(x => x.Parents)
            .WithMany(x => x.Children)
            .Map(c =>
            {
                c.MapLeftKey("ChildId");
                c.MapRightKey("ParentId");
                c.ToTable("ChildToParentMapping"); 
            });

    }
}

И затем я изменил OnModelCreating как:

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Child>()
            .HasMany(x => x.Parents)
            .WithMany(x => x.Children)
            .Map(c =>
            {
                c.MapLeftKey("ChildId");
                c.MapRightKey("ParentId");
                c.ToTable("ChildToParentMapping"); 
            });

        modelBuilder.Entity<Parent>()
           .HasMany(x => x.Children)
           .WithMany(x => x.Parents)
           .Map(c =>
           {
               c.MapLeftKey("ParentId");
               c.MapRightKey("ChildId");
               c.ToTable("ChildToParentMapping");
           });
    }

Что я нашел и подозревал, что первый запуск генерирует этот sql:

exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ChildId], [ParentId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

В отличие от второго, который генерирует:

exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ParentId], [ChildId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

Вы видите, что значения перевернуты? Здесь он фактически учитывает столбец ChildId как ParentId. Теперь это не сбой для меня, но я позволил EF создать базу данных, что означает, что он, вероятно, просто переключит имена столбцов, и если бы я посмотрел на внешние ключи, они тоже были бы переключены. Если вы создали базу данных вручную, что, вероятно, не будет.

Итак, короче: вы не равны, и я ожидаю, что один из них будет использован, и что, вероятно, он ошибается. В предыдущих версиях я думаю, что EF выбрал их в другом порядке.

UPDATE: Мне немного любопытно узнать о внешних ключах и проверить sql.

Из первого кода:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ChildId] FOREIGN KEY ([ChildId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE

И из второго кода:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE

Теперь это не приятно. ParentId сопоставлен с детьми, конечно, не то, что мы хотим.

Итак, второе неправильное отображение? Не совсем потому, что смотрите, что случилось, когда я удалил первый:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Parents_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Parents] ([ParentId]) ON DELETE CASCADE

Как-то с двумя сопоставлениями, кажется, все испортилось. Ошибка или нет. Я не знаю.

Ответ 2

Я могу подтвердить, что это ошибка в EF5, хотя я все еще не уверен, как она работает в 4.3.1.

Проблема в том, что мы неправильно связываем вызовы LeftKey/RightKey с их соответствующими свойствами навигации.

Я напишу ошибку EF6 на нашем сайте проекта CodePlex.

Чтобы обходной путь, я думаю, вам нужно либо:

  • Настройте связь только с одной стороны. Или,
  • Переключение имен столбцов LeftKey/RightKey одинаково в обеих конфигурациях.

Извините за неудобства.

UPDATE. Вот ошибка .