Как определить EF 6 Parent Child Relation с переменной? - PullRequest
0 голосов
/ 19 марта 2020

Я пытаюсь настроить 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.

enter image description here

Я пытался изменить это в моем свободном API без успеха.

1 Ответ

0 голосов
/ 20 марта 2020

Решением может быть удаление атрибутов ForeignKey, но вместо этого добавление отношения через построитель моделей:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Composition>().HasOne<Item>(x => x.ChildItem).WithMany().OnDelete(DeleteBehavior.Restrict);
    modelBuilder.Entity<Composition>().HasOne<Item>(x => x.ParentItem).WithMany().OnDelete(DeleteBehavior.Restrict);
}

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

Я попробовал это, добавив сущность Composition и два Item s, а затем удалив сущность Composition. Два Item остаются в базе данных. Я думаю, что так оно и должно быть.

...