Я пытаюсь настроить Entity Framework, чтобы определить родительское дочернее отношение с переменной для отношения. Предмет состоит из соотношения других предметов. Как я могу создать это с помощью Entity Framework 6.
Допустим, мой элемент «Родитель» - это фунт, а «Мой ребенок» - яйцо, мука, сахар и масло. Для фунт-торта соотношение каждого ребенка равно 1. Давайте сделаем это просто.
public class Item
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Composition
{
public Guid Id { get; set; }
public Guid ParentId { get; set; } // Id of Parent Item
public Guid ChildId { get; set; } // Id of Child Item
public int Ratio { get; set; } // Number of Child that compose the parent.
}
Мой начальный DbContext
public class TestContext : DbContext
{
public DbSet<Item> Items { get; set; }
}
При создании миграции с Add-Migration Initial
. Система сгенерирует этот код миграции:
public override void Up()
{
CreateTable(
"dbo.Compositions",
c => new
{
Id = c.Guid(nullable: false),
ParentId = c.Guid(nullable: false),
ChildId = c.Guid(nullable: false),
Ratio = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Items",
c => new
{
Id = c.Guid(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
}
Миграция создала 2 моих объекта, но они не имеют никакого отношения. Поэтому я не могу легко переходить от таблицы предметов ко всем своим композициям. Я использую Linpad и LinqPad не создавать свойства навигации. Это нормально, я никогда не говорю своему POCO, что ParentId и ChildId должны быть Id, существующими в моей таблице Item.
Чтобы создать ограничение в моей базе данных и иметь возможность навигации, я думаю, что я должен добавить свойство навигации и свяжите это с внешним ключом. Я попробую несколько решений здесь и прокомментирую.
Решение 1 - Просто добавьте свойства навигации
public class Composition
{
public Guid Id { get; set; }
public Guid ParentId { get; set; } // Id of Parent Item
[ForeignKey(nameof(ParentId))]
public Item Parent { get; set; }
public Guid ChildId { get; set; } // Id of Child Item
[ForeignKey(nameof(ChildId))]
public Item Child { get; set; }
public int Ratio { get; set; } // Number of Child that compose the parent.
}
При создании миграции с Add-Migration Initial. Система генерирует этот код миграции:
public override void Up()
{
CreateTable(
"dbo.Compositions",
c => new
{
Id = c.Guid(nullable: false),
ParentId = c.Guid(nullable: false),
ChildId = c.Guid(nullable: false),
Ratio = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Items", t => t.ChildId, cascadeDelete: true)
.ForeignKey("dbo.Items", t => t.ParentId, cascadeDelete: true)
.Index(t => t.ParentId)
.Index(t => t.ChildId);
CreateTable(
"dbo.Items",
c => new
{
Id = c.Guid(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
}
Но почему cascadeDelete для true? Я не хочу удалять все свое дерево при каждом удалении элемента. Я знаю, что могу определить Nullable Child или ChildId и Nullable Parent или ParentId, но я не могу принять это. В моей таблице Составов родитель и ребенок не могут быть нулевыми. Это не логика c Я создаю ссылку со значением на одной стороне и нулем на другой стороне. Я могу удалить композиционную ссылку, но если я ее создаю, то обе стороны должны существовать.
Кроме того, я не могу Udpate-Database
эту версию, потому что
Введение ограничения FOREIGN KEY 'FK_dbo.Compositions_dbo .Items_ParentId 'в таблице' Compositions 'может вызывать циклы или несколько каскадных путей. Укажите «УДАЛИТЬ НЕТ ДЕЙСТВИЙ» или «ОБНОВИТЬ НЕТ ДЕЙСТВИЙ» или измените другие ограничения «ИНОСТРАННЫЙ КЛЮЧ».
Решение 2 - Использование Fluent API
Я принимаю решение 1 и я пытаюсь добавить недостающую информацию, чтобы помочь системе в моем переопределении OnModelCreating ()
public class TestContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Composition> Compositions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Composition>()
.HasRequired(c => c.Parent)
// And I have no idea how to do here
}
}
Решение 3 - Сбор композиции по элементу
public class Item
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Composition> Compositions { get; set; }
}
public class Composition
{
public Guid Id { get; set; }
public Guid ParentId { get; set; } // Id of Parent Item
//[ForeignKey(nameof(ParentId))]
//public Item Parent { get; set; }
public Guid ChildId { get; set; } // Id of Child Item
[ForeignKey(nameof(ChildId))]
public Item Child { get; set; }
public int Ratio { get; set; } // Number of Child that compose the parent.
}
При миграции создание с помощью Add-Migration Initial. Система сгенерирует этот код миграции:
public override void Up()
{
CreateTable(
"dbo.Compositions",
c => new
{
Id = c.Guid(nullable: false),
ParentId = c.Guid(nullable: false),
ChildId = c.Guid(nullable: false),
Ratio = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Items", t => t.ChildId, cascadeDelete: true)
.Index(t => t.ChildId);
CreateTable(
"dbo.Items",
c => new
{
Id = c.Guid(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
}
Кажется лучше, но все же у меня есть каскадное удаление в true.
Решение 4 - перенести родительские и дочерние композиции на элемент
public class Item
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Composition> ChildCompositions { get; set; }
public ICollection<Composition> ParentCompositions { get; set; }
}
public class Composition
{
public Guid Id { get; set; }
public Guid ParentId { get; set; } // Id of Parent Item
[ForeignKey(nameof(ParentId))]
public Item Parent { get; set; }
public Guid ChildId { get; set; } // Id of Child Item
[ForeignKey(nameof(ChildId))]
public Item Child { get; set; }
public int Ratio { get; set; } // Number of Child that compose the parent.
}
Тогда сценарий миграции будет
public override void Up()
{
CreateTable(
"dbo.Compositions",
c => new
{
Id = c.Guid(nullable: false),
ParentId = c.Guid(nullable: false),
ChildId = c.Guid(nullable: false),
Ratio = c.Int(nullable: false),
Item_Id = c.Guid(),
Item_Id1 = c.Guid(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Items", t => t.Item_Id)
.ForeignKey("dbo.Items", t => t.Item_Id1)
.ForeignKey("dbo.Items", t => t.ChildId, cascadeDelete: true)
.ForeignKey("dbo.Items", t => t.ParentId, cascadeDelete: true)
.Index(t => t.ParentId)
.Index(t => t.ChildId)
.Index(t => t.Item_Id)
.Index(t => t.Item_Id1);
CreateTable(
"dbo.Items",
c => new
{
Id = c.Guid(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
}
Logi c. Система не может знать, что моя именованная коллекция ParentComposition должна быть связана с внешним ключом ParentId, а коллекция ChildComposition должна быть связана с внешним ключом ChildId? Таким образом, система создает новые внешние ключи.
Вкл. Update-Database
Я получаю ошибку
Введение ограничения FOREIGN KEY 'FK_dbo.Compositions_dbo.Items_ParentId' в таблице 'Compositions' может вызвать циклы или несколько каскадных путей. Укажите ON DELETE NO ACTION или ON UPDATE NO ACTION, или измените другие ограничения FOREIGN KEY.
Решение 5 - Вернитесь к Fluent API
Но я могу теперь просмотрите свойства, так что, возможно, я найду то, что ищу:
public class Item
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Composition> ParentCompositions { get; set; }
public ICollection<Composition> ChildCompositions { get; set; }
}
public class Composition
{
public Guid Id { get; set; }
public Guid ParentId { get; set; } // Id of Parent Item
[ForeignKey(nameof(ParentId))]
public Item Parent { get; set; }
public Guid ChildId { get; set; } // Id of Child Item
[ForeignKey(nameof(ChildId))]
public Item Child { get; set; }
public int Ratio { get; set; } // Number of Child that compose the parent.
}
public class TestContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Composition> Compositions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Item>()
.HasMany(c => c.ChildCompositions)
.WithRequired(c => c.Parent)
//.HasForeignKey(c => c.ParentId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Item>()
.HasMany(c => c.ParentCompositions)
.WithRequired(c => c.Child)
//.HasForeignKey(c => c.ChildId)
.WillCascadeOnDelete(false);
}
}
Дает мне скрипт миграции:
public override void Up()
{
CreateTable(
"dbo.Compositions",
c => new
{
Id = c.Guid(nullable: false),
ParentId = c.Guid(nullable: false),
ChildId = c.Guid(nullable: false),
Ratio = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Items", t => t.ParentId)
.ForeignKey("dbo.Items", t => t.ChildId)
.Index(t => t.ParentId)
.Index(t => t.ChildId);
CreateTable(
"dbo.Items",
c => new
{
Id = c.Guid(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
}
И я могу обновить свою базу данных с помощью этого. Но, странно, когда я проверяю его результат в LinqPad, кажется, что существует инверсия между списком ChildCompositions и списком ParentCompositions.
Я пытался изменить это в моем свободном API без успеха.