Tipi di entità con costruttori

È possibile definire un costruttore con parametri e chiamare questo costruttore ef Core quando si crea un'istanza dell'entità. I parametri del costruttore possono essere associati alle proprietà mappate o a vari tipi di servizi per facilitare comportamenti come il caricamento differita.

Nota

Attualmente, l'associazione di tutti i costruttori è per convenzione. La configurazione di costruttori specifici da usare è pianificata per una versione futura.

Associazione a proprietà mappate

Si consideri un tipico modello di blog/post:

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

Quando EF Core crea istanze di questi tipi, ad esempio per i risultati di una query, chiama prima il costruttore senza parametri predefinito e quindi imposta ogni proprietà sul valore del database. Tuttavia, se EF Core trova un costruttore con parametri con nomi di parametri e tipi che corrispondono a quelli delle proprietà mappate, chiamerà invece il costruttore con parametri con valori per tali proprietà e non imposta ogni proprietà in modo esplicito. Ad esempio:

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

Ci sono alcuni aspetti da considerare:

  • Non tutte le proprietà devono avere parametri del costruttore. Ad esempio, la proprietà Post.Content non è impostata da alcun parametro del costruttore, quindi EF Core lo imposta dopo aver chiamato il costruttore nel modo normale.
  • I tipi di parametro e i nomi devono corrispondere ai tipi di proprietà e ai nomi, ad eccezione del fatto che le proprietà possono essere maiuscole/minuscole pascale mentre i parametri sono in maiuscolo.
  • EF Core non può impostare le proprietà di navigazione (ad esempio blog o post precedenti) usando un costruttore.
  • Il costruttore può essere pubblico, privato o avere qualsiasi altra accessibilità. Tuttavia, i proxy di caricamento differita richiedono che il costruttore sia accessibile dalla classe proxy che eredita. In genere questo significa renderlo pubblico o protetto.

Proprietà di sola lettura

Una volta impostate le proprietà tramite il costruttore, può essere utile renderle di sola lettura. EF Core supporta questa funzionalità, ma esistono alcuni aspetti da cercare:

  • Le proprietà senza setter non vengono mappate per convenzione. In questo modo si tende a eseguire il mapping delle proprietà che non devono essere mappate, ad esempio le proprietà calcolate.
  • L'uso di valori di chiave generati automaticamente richiede una proprietà chiave di lettura/scrittura, poiché il valore della chiave deve essere impostato dal generatore di chiavi durante l'inserimento di nuove entità.

Un modo semplice per evitare queste cose consiste nell'usare setter privati. Ad esempio:

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 vede una proprietà con un setter privato come lettura/scrittura, il che significa che tutte le proprietà vengono mappate come prima e la chiave può comunque essere generata dall'archivio.

Un'alternativa all'uso di setter privati consiste nel rendere le proprietà di sola lettura e aggiungere un mapping più esplicito in OnModelCreating. Analogamente, alcune proprietà possono essere rimosse completamente e sostituite solo con campi. Si considerino ad esempio questi tipi di entità:

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

Questa configurazione 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);
        });
}

Informazioni da considerare:

  • La chiave "property" è ora un campo. Non è un readonly campo in modo che le chiavi generate dall'archivio possano essere usate.
  • Le altre proprietà sono proprietà di sola lettura impostate solo nel costruttore.
  • Se il valore della chiave primaria viene impostato solo da EF o letto dal database, non è necessario includerlo nel costruttore. Ciò lascia la chiave "proprietà" come un campo semplice e rende chiaro che non deve essere impostato in modo esplicito durante la creazione di nuovi blog o post.

Nota

Questo codice genererà l'avviso del compilatore '169' che indica che il campo non viene mai usato. Questo può essere ignorato perché in realtà EF Core usa il campo in modo extralingustico.

Inserimento di servizi

EF Core può anche inserire "servizi" nel costruttore di un tipo di entità. Ad esempio, è possibile inserire quanto segue:

  • DbContext - l'istanza di contesto corrente, che può anche essere digitata come tipo DbContext derivato
  • ILazyLoader - il servizio lazy-loading--vedere la documentazione di lazy-loading per altri dettagli
  • Action<object, string> - un delegato lazy-loading--vedere la documentazione di lazy-loading per altri dettagli
  • IEntityType - Metadati di EF Core associati a questo tipo di entità

Nota

Attualmente, solo i servizi noti da EF Core possono essere inseriti. Il supporto per l'inserimento dei servizi dell'applicazione viene considerato per una versione futura.

Ad esempio, un dbContext inserito può essere usato per accedere in modo selettivo al database per ottenere informazioni sulle entità correlate senza caricarle tutte. Nell'esempio seguente viene usato per ottenere il numero di post in un blog senza caricare i post:

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

Ecco alcuni aspetti da notare:

  • Il costruttore è privato, poiché viene chiamato solo da EF Core e c'è un altro costruttore pubblico per l'uso generale.
  • Il codice che usa il servizio inserito (ovvero il contesto) è difensivo rispetto al fatto di null gestire i casi in cui EF Core non crea l'istanza.
  • Poiché il servizio viene archiviato in una proprietà di lettura/scrittura, verrà reimpostato quando l'entità è collegata a una nuova istanza del contesto.

Avviso

L'inserimento di DbContext come questo è spesso considerato un anti-pattern perché associa i tipi di entità direttamente a EF Core. Considerare attentamente tutte le opzioni prima di usare l'inserimento del servizio in questo modo.