Как заполнить данные отношениями «многие к майу» в Entity Framework Migrations - PullRequest
18 голосов
/ 18 декабря 2011

Я использую миграцию структуры сущностей (в режиме автоматической миграции).Все в порядке, но у меня есть один вопрос: как я должен заполнять данные, когда у меня есть отношение ко многим.Например, у меня есть два класса моделей:

public class Parcel
{
  public int Id { get; set; }
  public string Description { get; set; }
  public double Weight { get; set; }
  public virtual ICollection<BuyingItem> Items { get; set; }
}

public class BuyingItem
{
    public int Id { get; set; }
    public decimal Price { get; set; }
    public virtual ICollection<Parcel> Parcels { get; set; }
}

Я понимаю, как заполнять простые данные (для класса PaymentSystem) и отношения «один ко многим», но какой код я должен написать в методе Seed для генерации некоторых экземпляровпосылки и покупки?Я имею в виду использование DbContext.AddOrUpdate (), потому что я не хочу дублировать данные каждый раз, когда я запускаю Update-Database.

protected override void Seed(ParcelDbContext context)
{
        context.AddOrUpdate(ps => ps.Id,
                            new PaymentSystem {Id = 1, Name = "Visa"},
                            new PaymentSystem {Id = 2, Name = "PayPal"},
                            new PaymentSystem {Id = 3, Name = "Cash"});
}

protected override void Seed(Context context)
{
    base.Seed(context);

    // This will create Parcel, BuyingItems and relations only once
    context.AddOrUpdate(new Parcel() 
    { 
        Id = 1, 
        Description = "Test", 
        Items = new List<BuyingItem>
        {
            new BuyingItem() { Id = 1, Price = 10M },
            new BuyingItem() { Id = 2, Price = 20M }
        }
    });

    context.SaveChanges();
}

Да.Этот код создает Parcel, BuyingItems и отношение, но если мне нужен тот же BuyingItem в другой Parcel (они имеют отношение многие ко многим), и если я повторю этот код для второй посылки - он дублирует BuyingItems в базе данных (хотя я установилтот же идентификатор).Пример:

protected override void Seed(Context context)
{
    base.Seed(context);

    context.AddOrUpdate(new Parcel() 
    { 
        Id = 1, 
        Description = "Test", 
        Items = new List<BuyingItem>
        {
            new BuyingItem() { Id = 1, Price = 10M },
            new BuyingItem() { Id = 2, Price = 20M }
        }
    });

    context.AddOrUpdate(new Parcel() 
    { 
        Id = 2, 
        Description = "Test2", 
        Items = new List<BuyingItem>
        {
            new BuyingItem() { Id = 1, Price = 10M },
            new BuyingItem() { Id = 2, Price = 20M }
        }
    });

    context.SaveChanges();
}

Как добавить одни и те же элементы BuyingItems в разные участки?

Ответы [ 3 ]

20 голосов
/ 18 декабря 2011

Вы должны заполнить отношение «многие ко многим» так же, как вы строите отношение «многие ко многим» в любом коде EF:

protected override void Seed(Context context)
{
    base.Seed(context);

    // This will create Parcel, BuyingItems and relations only once
    context.AddOrUpdate(new Parcel() 
    { 
        Id = 1, 
        Description = "Test", 
        Items = new List<BuyingItem>
        {
            new BuyingItem() { Id = 1, Price = 10M },
            new BuyingItem() { Id = 2, Price = 20M }
        }
    });

    context.SaveChanges();
}

Указание Id, которое будет использоваться в базе данных, имеет решающее значение, в противном случае каждый Update-Database будет создавать новые записи.

AddOrUpdate никоим образом не поддерживает изменение отношений, поэтому вы не можете использовать его для добавления или удаления отношений при следующей миграции. Если вам это нужно, вы должны вручную удалить отношение, загрузив Parcel с BuyingItems и вызвав Remove или Add в коллекции навигации, чтобы разорвать или добавить новое отношение.

19 голосов
/ 18 августа 2014

Обновленный ответ

Убедитесь, что вы читаете раздел «Использование AddOrUpdate должным образом» ниже для полного ответа.

Прежде всего, давайте создадим составной первичный ключ (состоящий из идентификатора посылки и предмета) для устранения дубликатов.Добавьте следующий метод в класс DbContext:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Parcel>()
            .HasMany(p => p.Items)
            .WithMany(r => r.Parcels)
            .Map(m =>
            {
                m.ToTable("ParcelItems");
                m.MapLeftKey("ParcelId");
                m.MapRightKey("BuyingItemId");
            });
    }

Затем реализуйте метод Seed следующим образом:

    protected override void Seed(Context context)
    {
        context.Parcels.AddOrUpdate(
          p => p.Id,
          new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 },
          new Parcel { Id = 2, Description = "Parcel 2", Weight = 2.0 },
          new Parcel { Id = 3, Description = "Parcel 3", Weight = 3.0 }
        );

        context.BuyingItems.AddOrUpdate(
          b => b.Id,
          new BuyingItem { Id = 1, Price = 10m },
          new BuyingItem { Id = 2, Price = 20m }
        );

        // Make sure that the above entities are created in the database
        context.SaveChanges();

        var p1 = context.Parcels.Find(1);
        // Uncomment the following line if you are not using lazy loading.
        //context.Entry(p1).Collection(p => p.Items).Load();

        var p2 = context.Parcels.Find(2);
        // Uncomment the following line if you are not using lazy loading.
        //context.Entry(p2).Collection(p => p.Items).Load();

        var i1 = context.BuyingItems.Find(1);
        var i2 = context.BuyingItems.Find(2);

        p1.Items.Add(i1);
        p1.Items.Add(i2);

        // Uncomment to test whether this fails or not, it will work, and guess what, no duplicates!!!
        //p1.Items.Add(i1);
        //p1.Items.Add(i1);
        //p1.Items.Add(i1);
        //p1.Items.Add(i1);
        //p1.Items.Add(i1);

        p2.Items.Add(i1);
        p2.Items.Add(i2);

        // The following WON'T work, since we're assigning a new collection, it'll try to insert duplicate values only to fail.
        //p1.Items = new[] { i1, i2 };
        //p2.Items = new[] { i2 };
    }

Здесь мы гарантируем, что сущности создаются \ обновляются в базе данных с помощьювызов context.SaveChanges() в методе Seed.После этого мы получаем необходимую посылку и покупаем предметы, используя context.После этого мы используем свойство Items (которое является коллекцией) для Parcel объектов, чтобы добавить BuyingItem, как нам угодно.

Обратите внимание, независимо от того, сколько раз мы вызываем метод Add, используятот же объект элемента, мы не заканчиваем с нарушением первичного ключа.Это связано с тем, что EF внутренне использует HashSet<T> для управления Parcel.Items коллекцией.HashSet<Item> по своей природе не позволит вам добавлять дубликаты.

Более того, если вам как-то удастся обойти это поведение EF, как я продемонстрировал в примере, наш первичный ключ не будетвпишите дубликаты.

Правильное использование AddOrUpdate

При использовании типичного поля Id (int, identity) в качестве выражения идентификатора с методом AddOrUpdate следует соблюдать осторожность.

В этом случае, если вы вручную удалите одну из строк из таблицы Parcel, вы в конечном итоге создадите дубликаты при каждом запуске метода Seed (даже с обновленным методом Seed, который я предоставил выше.).

Рассмотрим следующий код:

 context.Parcels.AddOrUpdate(
      p => p.Id,
      new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 },
      new Parcel { Id = 2, Description = "Parcel 1", Weight = 1.0 },
      new Parcel { Id = 3, Description = "Parcel 1", Weight = 1.0 }
 );

Технически (с учетом суррогатного идентификатора здесь) строки уникальны, но с точки зрения конечного пользователя они являются дубликатами.

Истинным решением здесь является использование поля Description в качестве идентификатора выражения.Добавьте этот атрибут в свойство Description класса Parcel, чтобы сделать его уникальным, [MaxLength(255), Index(IsUnique=true)].Обновите следующие фрагменты в методе Seed:

       context.Parcels.AddOrUpdate(
          p => p.Description,
          new Parcel { Description = "Parcel 1", Weight = 1.0 },
          new Parcel { Description = "Parcel 2", Weight = 2.0 },
          new Parcel { Description = "Parcel 3", Weight = 3.0 }
        );

        // Make sure that the above entities are created in the database
        context.SaveChanges();

        var p1 = context.Parcels.Single(p => p.Description == "Parcel 1");

Обратите внимание, я не использую поле Id, так как EF будет игнорировать его при вставке строк.И мы используем Description для получения правильного объекта посылки, независимо от значения Id.


Старый ответ

Я бы хотелДобавьте сюда несколько замечаний:

  1. Использование Id, вероятно, не принесет пользы, если столбец Id является полем, созданным базой данных.EF будет игнорировать его.

  2. Этот метод работает нормально, когда метод Seed запускается один раз.Он не будет создавать дубликаты, однако, если вы запустите его во второй раз (и большинство из нас должны делать это часто), он может ввести дубликаты.В моем случае это так.

Этот урок Тома Дайкстры показал мне правильный способ сделать это.Это работает, потому что мы ничего не принимаем как должное.Мы не указываем идентификаторы.Вместо этого мы запрашиваем контекст по известным уникальным ключам и добавляем к ним связанные сущности (которые снова получают путем запроса контекста).В моем случае это сработало как шарм.

3 голосов
/ 18 декабря 2011

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

protected override void Seed(Context context)
{
    base.Seed(context);
    var buyingItems = new[]
    {
        new BuyingItem
        {
             Id = 1,
             Price = 10m
        },
        new BuyingItem
        {
             Id = 2,
             Price = 20m,
        }
    }

    context.AddOrUpdate(new Parcel() 
    { 
        Id = 1, 
        Description = "Test", 
        Items = new List<BuyingItem>
        {
            buyingItems[0],
            buyingItems[1]
        }
    },
    new Parcel() 
    { 
        Id = 2, 
        Description = "Test2", 
        Items = new List<BuyingItem>
        {
            buyingItems[0],
            buyingItems[1]
        }
    });

    context.SaveChanges();
}

В базе данных нет дубликатов.

Спасибо, Ладислав, вы дали мне правильный вектор, чтобы найти решение моей задачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...