Tipos de entidad con constructoresEntity types with constructors

Nota

Esta característica es nueva en EF Core 2.1.This feature is new in EF Core 2.1.

A partir de EF Core 2.1, ahora es posible definir un constructor con parámetros y que EF Core llamar a este constructor al crear una instancia de la entidad.Starting with EF Core 2.1, it is now possible to define a constructor with parameters and have EF Core call this constructor when creating an instance of the entity. Los parámetros del constructor se pueden enlazar a propiedades asignadas o para distintos tipos de servicios que facilitan los comportamientos como la carga diferida.The constructor parameters can be bound to mapped properties, or to various kinds of services to facilitate behaviors like lazy-loading.

Nota

A partir de EF Core 2.1, todo el enlace de constructor es por convención.As of EF Core 2.1, all constructor binding is by convention. Configuración de constructores específicos para usar está prevista para una versión futura.Configuration of specific constructors to use is planned for a future release.

Enlazar a propiedades asignadasBinding to mapped properties

Considere la posibilidad de un modelo de entrada de Blog/típico:Consider a typical Blog/Post model:

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; }
}

Cuando EF Core crea instancias de estos tipos, como los resultados de una consulta, se llamar primero al constructor sin parámetros predeterminado y, a continuación, establece cada propiedad en el valor de la base de datos.When EF Core creates instances of these types, such as for the results of a query, it will first call the default parameterless constructor and then set each property to the value from the database. Sin embargo, si EF Core busca un constructor con parámetros con los nombres de parámetro y tipos que coinciden con los de asignan propiedades y después en su lugar, llamará al constructor con parámetros con valores de esas propiedades y no establece explícitamente cada propiedad.However, if EF Core finds a parameterized constructor with parameter names and types that match those of mapped properties, then it will instead call the parameterized constructor with values for those properties and will not set each property explicitly. Por ejemplo:For example:

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; }
}

Algunos aspectos a tener en cuenta:Some things to note:

  • No todas las propiedades deben tener los parámetros del constructor.Not all properties need to have constructor parameters. Por ejemplo, no se establece la propiedad Post.Content por cualquier parámetro de constructor, por lo que EF Core establecerá después de llamar al constructor de la manera normal.For example, the Post.Content property is not set by any constructor parameter, so EF Core will set it after calling the constructor in the normal way.
  • Los tipos de parámetro y nombres deben coincidir con los tipos de propiedad y los nombres, excepto en que las propiedades pueden ser con grafía Pascal mientras que los parámetros son mayúsculas y minúsculas camel.The parameter types and names must match property types and names, except that properties can be Pascal-cased while the parameters are camel-cased.
  • EF Core no puede establecer las propiedades de navegación (por ejemplo, Blog o publicaciones anteriores) mediante un constructor.EF Core cannot set navigation properties (such as Blog or Posts above) using a constructor.
  • El constructor puede ser público, privado, o tiene cualquier otro tipo de accesibilidad.The constructor can be public, private, or have any other accessibility. Sin embargo, los proxies de carga diferida requieren que el constructor es accesible desde la clase de proxy heredada.However, lazy-loading proxies require that the constructor is accessible from the inheriting proxy class. Normalmente, esto significa que se hace público o protegido.Usually this means making it either public or protected.

Propiedades de solo lecturaRead-only properties

Una vez que se establecen las propiedades mediante el constructor puede sentido hacer algunos de ellos de sólo lectura.Once properties are being set via the constructor it can make sense to make some of them read-only. EF Core es compatible con esto, pero hay algunas cosas a tener en cuenta:EF Core supports this, but there are some things to look out for:

  • No se asignan las propiedades sin establecedores por convención.Properties without setters are not mapped by convention. (Si lo hace, suele asignar propiedades que no deberían asignarse, por ejemplo, las propiedades calculadas).(Doing so tends to map properties that should not be mapped, such as computed properties.)
  • Utilizando los valores de clave generados automáticamente, requiere una propiedad de clave es de lectura y escritura, ya que el valor de clave debe establecerse mediante el generador de claves cuando se insertan nuevas entidades.Using automatically generated key values requires a key property that is read-write, since the key value needs to be set by the key generator when inserting new entities.

Una manera fácil de evitar estas situaciones es usar establecedores privados.An easy way to avoid these things is to use private setters. Por ejemplo:For example:

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, ve una propiedad con un establecedor privado de lectura y escritura, lo que significa que todas las propiedades se asignan como antes y la clave puede ser generado por el almacén.EF Core sees a property with a private setter as read-write, which means that all properties are mapped as before and the key can still be store-generated.

Una alternativa al uso de establecedores privados es poner propiedades realmente de solo lectura y agregar una asignación más explícita en OnModelCreating.An alternative to using private setters is to make properties really read-only and add more explicit mapping in OnModelCreating. Del mismo modo, algunas propiedades se pueden quitar completamente y reemplaza con solo los campos.Likewise, some properties can be removed completely and replaced with only fields. Por ejemplo, considere la posibilidad de estos tipos de entidad:For example, consider these entity types:

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; }
}

Y esta configuración en OnModelCreating:And this configuration in 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);
        });
}

Cosas a tener en cuenta:Things to note:

  • La clave "property" es ahora un campo.The key "property" is now a field. No es un readonly campo para que se pueden usar claves generadas por el almacén.It is not a readonly field so that store-generated keys can be used.
  • Las demás propiedades son propiedades de solo lectura que se establece solo en el constructor.The other properties are read-only properties set only in the constructor.
  • Si el valor de clave principal siempre se establece por EF o leer desde la base de datos, no hay ninguna necesidad de incluirlo en el constructor.If the primary key value is only ever set by EF or read from the database, then there is no need to include it in the constructor. Esto deja la clave "property" como un campo sencillo y deja claro que no se debe establecer explícitamente al crear nuevos blogs o entradas.This leaves the key "property" as a simple field and makes it clear that it should not be set explicitly when creating new blogs or posts.

Nota

Este código dará como resultado '169' indicando que nunca se usa el campo de advertencia del compilador.This code will result in compiler warning '169' indicating that the field is never used. Puede omitirse dado que en realidad, EF Core está usando el campo de una manera extralinguistic.This can be ignored since in reality EF Core is using the field in an extralinguistic manner.

Inyección de serviciosInjecting services

EF Core también puede insertar "servicios" en el constructor de un tipo de entidad.EF Core can also inject "services" into an entity type's constructor. Por ejemplo, el siguiente se puede insertar:For example, the following can be injected:

  • DbContext -la instancia de contexto actual, que también se puede escribir como su tipo derivado de DbContextDbContext - the current context instance, which can also be typed as your derived DbContext type
  • ILazyLoader -el servicio de la carga diferida, vea el documentación de la carga diferida para obtener más detallesILazyLoader - the lazy-loading service--see the lazy-loading documentation for more details
  • Action<object, string> -un delegado de la carga diferida, vea el documentación de la carga diferida para obtener más detallesAction<object, string> - a lazy-loading delegate--see the lazy-loading documentation for more details
  • IEntityType -los metadatos de EF Core asociados con este tipo de entidadIEntityType - the EF Core metadata associated with this entity type

Nota

A partir de EF Core 2.1, solo los servicios conocidos con EF Core se pueden insertar.As of EF Core 2.1, only services known by EF Core can be injected. Soporte técnico para insertar los servicios de aplicación se está considerando para una versión futura.Support for injecting application services is being considered for a future release.

Por ejemplo, puede utilizarse un DbContext insertado selectivamente acceder a la base de datos para obtener información sobre las entidades relacionadas sin tener que cargar todos ellos.For example, an injected DbContext can be used to selectively access the database to obtain information about related entities without loading them all. En el ejemplo siguiente se utiliza para obtener el número de publicaciones en un blog sin tener que cargar las entradas de esto:In the example below this is used to obtain the number of posts in a blog without loading the posts:

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; }
}

Algunas cosas a tener en cuenta sobre esto:A few things to notice about this:

  • El constructor es privado, ya que solo se llama mediante EF Core y no hay otro constructor público para uso general.The constructor is private, since it is only ever called by EF Core, and there is another public constructor for general use.
  • El código que usa el servicio insertado (es decir, el contexto) es estable en el mismo que se va a null para controlar los casos donde EF Core no está creando la instancia.The code using the injected service (that is, the context) is defensive against it being null to handle cases where EF Core is not creating the instance.
  • Porque el servicio se almacena en una propiedad de lectura/escritura se restablecerá cuando la entidad está asociada a una nueva instancia de contexto.Because service is stored in a read/write property it will be reset when the entity is attached to a new context instance.

Advertencia

Inyección de DbContext como esta, a menudo se considera un antipatrón puesto que asocia los tipos de entidad directamente a EF Core.Injecting the DbContext like this is often considered an anti-pattern since it couples your entity types directly to EF Core. Considere detenidamente todas las opciones antes de usar la inserción de un servicio similar al siguiente.Carefully consider all options before using service injection like this.