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 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 de constructor se pueden enlazar a propiedades asignadas o a varios tipos de servicios para facilitar 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, todos los enlaces de constructor son por Convención.As of EF Core 2.1, all constructor binding is by convention. La configuración de constructores específicos que se va a usar está planeada 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 un modelo típico de blog o post: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, primero llamará al constructor sin parámetros predeterminado y, a continuación, establecerá 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 encuentra un constructor con parámetros con nombres de parámetros y tipos que coinciden con los de propiedades asignadas, se llamará en su lugar al constructor con parámetros con valores para esas propiedades y no establecerá cada propiedad explícitamente.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; }
}

Cosas que tener en cuenta:Some things to note:

  • No todas las propiedades deben tener parámetros de constructor.Not all properties need to have constructor parameters. Por ejemplo, la propiedad post. Content no está establecida por ningún parámetro de constructor, por lo que EF Core la 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 nombres y tipos de parámetros deben coincidir con los nombres y tipos de propiedad, con la excepción de que las propiedades pueden tener mayúsculas y minúsculas Pascal, mientras que los parámetros tienen 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 pueden establecer las propiedades de navegación (como 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 tener cualquier otra accesibilidad.The constructor can be public, private, or have any other accessibility. Sin embargo, los proxies de carga diferida requieren que el constructor sea 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 las propiedades se establecen mediante el constructor, puede tener sentido que algunas de ellas sean de solo 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 que debe buscar:EF Core supports this, but there are some things to look out for:

  • Las propiedades sin establecedores no se asignan por Convención.Properties without setters are not mapped by convention. (Esto tiende a asignar propiedades que no deben asignarse, como las propiedades calculadas).(Doing so tends to map properties that should not be mapped, such as computed properties.)
  • El uso de valores de clave generados automáticamente requiere una propiedad de clave que sea de lectura y escritura, ya que el valor de clave debe establecerse mediante el generador de claves al insertar 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 sencilla de evitar estos aspectos 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 como de lectura y escritura, lo que significa que todas las propiedades se asignan como antes y la clave todavía se puede generar en 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 hacer que las propiedades sean 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 por completo y reemplazar solo por campos.Likewise, some properties can be removed completely and replaced with only fields. Por ejemplo, considere 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 que hay que tener en cuenta:Things to note:

  • La clave "propiedad" es ahora un campo.The key "property" is now a field. No se trata de un campo de readonly para que se puedan usar las 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 establecidas solo en el constructor.The other properties are read-only properties set only in the constructor.
  • Si el valor de la clave principal solo se establece en EF o se lee de la base de datos, no es necesario 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 simple y deja claro que no se debe establecer explícitamente al crear nuevos blogs o publicaciones.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 producirá una advertencia del compilador ' 169 ' que indica que el campo nunca se utiliza.This code will result in compiler warning '169' indicating that the field is never used. Esto puede pasarse por alto, ya que en realidad EF Core está utilizando el campo de forma extralingüística.This can be ignored since in reality EF Core is using the field in an extralinguistic manner.

Insertar 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, se puede insertar lo siguiente:For example, the following can be injected:

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

Nota

A partir de EF Core 2,1, solo se pueden insertar los servicios conocidos por EF Core.As of EF Core 2.1, only services known by EF Core can be injected. Se está considerando la compatibilidad con la inserción de servicios de aplicación en una versión futura.Support for injecting application services is being considered for a future release.

Por ejemplo, un DbContext insertado se puede usar para tener acceso de forma selectiva a la base de datos para obtener información sobre las entidades relacionadas sin cargarlas todas.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 usa para obtener el número de publicaciones en un blog sin cargar las entradas: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; }
}

Algunos aspectos que se deben tener en cuenta:A few things to notice about this:

  • El constructor es privado, ya que nunca lo llama EF Core, y 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 inyectado (es decir, el contexto) está defensivo con respecto a que se null controlar los casos en los que EF Core no crea 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.
  • Dado que el servicio se almacena en una propiedad de lectura y escritura, se restablecerá cuando la entidad se adjunte 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

Inyectar DbContext como esto se suele considerar un anti-patrón, ya que une 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. Tenga en cuenta todas las opciones antes de usar la inserción de servicios como esta.Carefully consider all options before using service injection like this.