Типы сущностей с конструкторами

Можно определить конструктор с параметрами и вызвать этот конструктор EF Core при создании экземпляра сущности. Параметры конструктора можно привязать к сопоставленным свойствам или к различным типам служб, чтобы упростить поведение, например ленивую загрузку.

Примечание

В настоящее время все привязки конструктора по соглашению. Настройка конкретных конструкторов для использования планируется в будущем выпуске.

Привязка к сопоставленным свойствам

Рассмотрим типичную модель блога или записи:

public class Blog
{
    public int Id { get; set; }

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

Когда EF Core создает экземпляры этих типов, например для результатов запроса, сначала вызывает конструктор без параметров по умолчанию, а затем присваивает каждому свойству значение из базы данных. Однако если EF Core находит параметризованный конструктор с именами параметров и типами, которые соответствуют сопоставленным свойствам, вместо этого вызывает параметризованный конструктор со значениями для этих свойств и не будет явно задавать каждое свойство. Пример:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; set; }

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

Обратите внимание на следующие моменты.

  • Не все свойства должны иметь параметры конструктора. Например, свойство Post.Content не задается параметром конструктора, поэтому EF Core установит его после вызова конструктора обычным образом.
  • Типы и имена параметров должны соответствовать типам свойств и именам, за исключением того, что свойства могут быть заданы в регистре Pascal, а параметры являются верблюдьими.
  • EF Core не может задать свойства навигации (например, блог или записи выше) с помощью конструктора.
  • Конструктор может быть общедоступным, частным или иметь любые другие специальные возможности. Однако для отложенной загрузки прокси-серверов требуется, чтобы конструктор был доступен из наследующего прокси-класса. Обычно это означает, что это делает его общедоступным или защищенным.

Свойства только для чтения

После задания свойств с помощью конструктора имеет смысл сделать некоторые из них доступными только для чтения. EF Core поддерживает это, но есть некоторые аспекты для поиска:

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

Простой способ избежать этих вещей — использовать частные методы задания. Пример:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; private set; }

    public string Name { get; private set; }
    public string Author { get; private set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; private set; }

    public string Title { get; private set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; private set; }

    public Blog Blog { get; set; }
}

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

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

public class Blog
{
    private int _id;

    public Blog(string name, string author)
    {
        Name = name;
        Author = author;
    }

    public string Name { get; }
    public string Author { get; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    private int _id;

    public Post(string title, DateTime postedOn)
    {
        Title = title;
        PostedOn = postedOn;
    }

    public string Title { get; }
    public string Content { get; set; }
    public DateTime PostedOn { get; }

    public Blog Blog { get; set; }
}

И эта конфигурация в OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Author);
            b.Property(e => e.Name);
        });

    modelBuilder.Entity<Post>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Title);
            b.Property(e => e.PostedOn);
        });
}

Примечания.

  • Ключ "свойство" теперь является полем. Это не readonly поле, чтобы можно было использовать созданные в хранилище ключи.
  • Другие свойства — это свойства только для чтения, заданные только в конструкторе.
  • Если значение первичного ключа всегда задается EF или считывается из базы данных, его не нужно включать в конструктор. Это оставляет ключ "свойство" как простое поле и дает понять, что он не должен быть задан явным образом при создании новых блогов или записей.

Примечание

Этот код приведет к предупреждению компилятора "169", указывающее, что поле никогда не используется. Это можно игнорировать, так как в действительности EF Core использует поле экстралингвистическим образом.

Внедрение служб

EF Core также может внедрять "службы" в конструктор типа сущности. Например, можно внедрить следующее:

  • DbContext — текущий экземпляр контекста, который также может быть введен в качестве производного типа DbContext.
  • ILazyLoader — служба отложенной загрузки. Дополнительные сведения см. в документации по отложенной загрузке .
  • Action<object, string> — отложенный делегат загрузки. Дополнительные сведения см. в документации по отложенной загрузке .
  • IEntityType — метаданные EF Core, связанные с данным типом сущности.

Примечание

В настоящее время можно внедрить только службы, известные EF Core. Поддержка внедрения служб приложений рассматривается в будущем выпуске.

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

public class Blog
{
    public Blog()
    {
    }

    private Blog(BloggingContext context)
    {
        Context = context;
    }

    private BloggingContext Context { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; set; }

    public int PostsCount
        => Posts?.Count
           ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId"))
           ?? 0;
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

Некоторые моменты, которые следует заметить об этом:

  • Конструктор является частным, так как он вызывается только EF Core, и существует другой открытый конструктор для общего использования.
  • Код, использующий внедренную службу (то есть контекст), защищает его от того, чтобы null обрабатывать случаи, когда EF Core не создает экземпляр.
  • Так как служба хранится в свойстве чтения и записи, она сбрасывается при присоединении сущности к новому экземпляру контекста.

Предупреждение

Внедрение DbContext, как это, часто считается анти-шаблоном, так как он выполняет пары типов сущностей непосредственно в EF Core. Тщательно рассмотрите все варианты перед использованием внедрения службы, как показано ниже.