Поделиться через


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

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