Статьи

Использование самоссылающихся таблиц в Entity Framework

С тех пор, как вышел EF, я был его фанатом. Однако время от времени я сталкиваюсь с ситуацией проектирования таблиц, с которой я не уверен, как справиться с EF. На этой неделе мне нужно было настроить таблицу со ссылками на себя, чтобы хранить некоторые иерархические данные. Самоссылающаяся таблица — это таблица, в которой первичный ключ таблицы также определяется как внешний ключ. Звучит немного запутанно, верно?

Давайте проясним решение на примере. Допустим, я создаю приложение, в котором у меня есть список категорий и подкатегорий. Одна из моих категорий высшего уровня — «Языки программирования», а в языках программирования я имею подкатегории «C #» и «Java». Для хранения этих данных я могу использовать одну таблицу со следующей структурой:

Фактические данные будут выглядеть так:

образ

Просто для пояснения, категория верхнего уровня будет иметь нулевое значение для поля ParentId. Для всех дочерних категорий поле ParentId используется для представления значения первичного ключа его родителя. Как программист, вы можете думать о поле ParentId как о указателе. Для завершения примера давайте взглянем на SQL, использованный для создания таблицы.

CREATE TABLE [dbo].[Categories] (
    [CategoryId] [int] IDENTITY(1,1) NOT NULL,    
    [Name] [nvarchar](255) NOT NULL,    
    [ParentId] [int] NULL,
PRIMARY KEY CLUSTERED 
(
    [CategoryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[Categories]  WITH CHECK ADD  CONSTRAINT [Category_Parent] FOREIGN KEY([ParentId])
REFERENCES [dbo].[Categories] ([CategoryId])
GO

ALTER TABLE [dbo].[Categories] CHECK CONSTRAINT [Category_Parent]
GO

Изучив SQL, вы должны были заметить, что CategoryId является первичным ключом таблицы, а поле ParentId является внешним ключом, который указывает на поле CategoryId. Так как у нас есть ключ, ссылающийся на другой ключ в той же таблице, мы можем классифицировать его как таблицу с самоссылкой. Теперь, когда мы полностью понимаем, что такое таблица, ссылающаяся на себя, мы можем перейти к коду Entity Framework. Для начала нам нужно создать простой объект C # для представления таблицы категорий. Конечно, имейте в виду, что если вы сначала используете EF Code, вам не нужно заранее создавать таблицу или базу данных. Сначала я показал таблицу только потому, что хотел лучше проиллюстрировать, что такое таблица со ссылками на себя.

public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }
}

Пока что класс Category очень прост. Однако мы действительно хотим добавить еще несколько свойств, чтобы сделать этот класс полезным. Например, если вы дочерняя категория, вы действительно хотите использовать точечную запись, чтобы получить имя родительской категории (например, subCategory.Parent.Name). Используя EF, мы создадим виртуальное свойство с именем Parent. Делая свойство виртуальным, мы даем EF знать, что при обращении к этому свойству мы хотим загрузить некоторые данные. На основании ваших настроек конфигурации и кода, который вы используете для извлечения ваших данных (независимо от того, использовали ли вы DbSet.Include), EF будет лениво загружать или нетерпеливо загружать эти данные.

public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }
    public virtual Category Parent { get; set; }
}

Наконец, нам также нужно свойство с именем Children, чтобы мы могли использовать точечную запись для перечисления по дочерним категориям. Еще раз, вот модифицированный класс:

public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }
    public virtual Category Parent { get; set; }
    public virtual ICollection<Category> Children { get; set; }
}

Последний шаг — дать EF знать, как эти свойства связаны друг с другом. Это можно сделать, используя свободный API EF . Если вы новичок в EF и не знаете свободно распространяемого API, вы можете сначала прочитать эту статью .

public class CommodityCategoryMap : EntityTypeConfiguration<Category> {
    public CommodityCategoryMap() {
        HasKey(x => x.CategoryId);

        Property(x => x.CategoryId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        Property(x => x.Name)
            .IsRequired()
            .HasMaxLength(255);

        HasOptional(x => x.Parent)
            .WithMany(x => x.Children)
            .HasForeignKey(x => x.ParentId)
            .WillCascadeOnDelete(false);
    }
}

Надеюсь, вы уделили особое внимание последнему разделу кода, в котором мы утверждаем, что категория имеет необязательное свойство Parent. В базе данных, это просто означает, что поле ParentID имеет значение NULL. Код также заявляет, что если у объекта Category может быть ноль или много дочерних объектов Чтобы указать, что запись является дочерней, мы используем поле ParentId для хранения значения первичного ключа родительской записи. Как я упоминал ранее, если вы программист, вам проще воспринимать поле ParentId как указатель. Наконец, я отключил каскад при удалении. Этот шаг не является обязательным и, вероятно, основан на ваших личных предпочтениях. Если вы включите каскад при удалении и удалите категорию, которая имеет 100 дочерних элементов, то вы фактически удалите 101 запись. По какой-то причине это немного пугает меня. Может быть,моя короткая карьера в качестве администратора базы данных заставила меня не доверять людям с большими объемами удаленных заявлений. Тем не менее, вы можете решить по-разному в зависимости от ваших обстоятельств.

Надеемся, что это короткое учебное пособие по EF поможет вам, если вы работаете по сценарию, в котором вам нужно собирать и манипулировать иерархическими данными. Если у вас есть какие-либо вопросы, пожалуйста, оставьте комментарий.