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

Можно определить конструктор с параметрами и вызвать этот конструктор 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 установит его после вызова конструктора обычным образом.
  • Типы параметров и имена должны соответствовать типам свойств и именам, за исключением того, что свойства можно регистрировать, пока параметры регистрируются верблюдю.
  • 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);
        });
}

Следует учесть:

  • Ключ "property" теперь является полем. Это не 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. Тщательно рассмотрите все варианты перед использованием внедрения служб, как это.