Delen via


Entiteitstypen met constructors

Het is mogelijk om een constructor met parameters te definiëren en ef Core deze constructor aan te roepen bij het maken van een exemplaar van de entiteit. De constructorparameters kunnen worden gebonden aan gemapte eigenschappen of aan verschillende soorten services om gedrag zoals lazy loading mogelijk te maken.

Opmerking

Momenteel is alle constructorbinding standaard. Configuratie van specifieke constructors die moeten worden gebruikt, is gepland voor een toekomstige release.

Binding met gemapte eigenschappen

Overweeg een typisch 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; }
}

Wanneer EF Core exemplaren van deze typen maakt, zoals voor de resultaten van een query, wordt eerst de standaardconstructor zonder parameter aangeroepen en vervolgens elke eigenschap ingesteld op de waarde uit de database. Als EF Core echter een geparameteriseerde constructor met parameternamen en -typen vindt die overeenkomen met die van toegewezen eigenschappen, wordt in plaats daarvan de geparameteriseerde constructor aangeroepen met waarden voor deze eigenschappen en wordt niet elke eigenschap expliciet ingesteld. Voorbeeld:

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

Enkele dingen die u moet noteren:

  • Niet alle eigenschappen hoeven constructorparameters te hebben. De eigenschap Post.Content is bijvoorbeeld niet ingesteld door een constructorparameter, dus EF Core stelt deze in nadat de constructor op de normale manier is aangeroepen.
  • De parametertypen en -namen moeten overeenkomen met eigenschapstypen en -namen, behalve dat eigenschappen Pascal-cased kunnen zijn terwijl de parameters camel-cased zijn.
  • EF Core kan geen navigatie-eigenschappen (zoals blog of berichten hierboven) instellen met behulp van een constructor.
  • De constructor kan openbaar, privé zijn of andere toegankelijkheid hebben. Voor lazy-loading proxy's is echter vereist dat de constructor toegankelijk is vanuit de onderliggende proxyklasse. Dit betekent meestal dat het openbaar of beschermd wordt.

Alleen-lezen eigenschappen

Zodra de eigenschappen zijn ingesteld via de constructor, kan het zinvol zijn om sommige eigenschappen alleen-lezen te maken. EF Core ondersteunt dit, maar er zijn enkele dingen die u moet bekijken:

  • Eigenschappen zonder setters worden niet gemapt volgens conventie. (Hierdoor worden eigenschappen gemapt die niet gemapt moeten worden, zoals berekende eigenschappen.)
  • Voor het gebruik van automatisch gegenereerde sleutelwaarden is een sleuteleigenschap vereist die lezen/schrijven is, omdat de sleutelwaarde moet worden ingesteld door de sleutelgenerator bij het invoegen van nieuwe entiteiten.

Een eenvoudige manier om deze dingen te voorkomen, is door privé-setters te gebruiken. Voorbeeld:

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 ziet een eigenschap met een privé-setter als lezen/schrijven, wat betekent dat alle eigenschappen zijn toegewezen als voorheen en dat de sleutel nog steeds kan worden opgeslagen.

Een alternatief voor het gebruik van privé-setters is om eigenschappen werkelijk alleen-lezen te maken en meer expliciete toewijzing toe te voegen in OnModelCreating. Op dezelfde manier kunnen sommige eigenschappen volledig worden verwijderd en vervangen door alleen velden. Denk bijvoorbeeld aan deze entiteitstypen:

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

Deze configuratie 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);
        });
}

Punten die u moet in acht nemen:

  • De sleutel 'eigenschap' is nu een veld. Het is geen readonly veld, zodat opgeslagen sleutels kunnen worden gebruikt.
  • De andere eigenschappen zijn alleen-lezeneigenschappen die alleen in de constructor zijn ingesteld.
  • Als de primaire-sleutelwaarde alleen wordt ingesteld door EF of wordt gelezen uit de database, hoeft u deze niet op te nemen in de constructor. Hierdoor blijft de sleutel 'eigenschap' als een eenvoudig veld en wordt duidelijk dat het niet expliciet moet worden ingesteld bij het maken van nieuwe blogs of berichten.

Opmerking

Deze code resulteert in compilerwaarschuwing 169 die aangeeft dat het veld nooit wordt gebruikt. Dit kan worden genegeerd omdat EF Core in werkelijkheid het veld op een extralinguistische manier gebruikt.

Services injecteren

EF Core kan ook 'services' injecteren in de constructor van een entiteitstype. Het volgende kan bijvoorbeeld worden geïnjecteerd:

  • DbContext - de huidige contextinstance, die ook kan worden getypt als uw afgeleide DbContext-type
  • ILazyLoader - de service voor lazy-loading--zie de documentatie voor lazy-loading voor meer informatie
  • Action<object, string> - een lazy-loading gedelegeerde--zie de documentatie voor lazy loading voor meer informatie
  • IEntityType - de EF Core-metagegevens die zijn gekoppeld aan dit entiteitstype

Opmerking

Op dit moment kunnen alleen services die bekend zijn door EF Core, worden geïnjecteerd. Ondersteuning voor het injecteren van toepassingsservices wordt overwogen voor een toekomstige release.

Een geïnjecteerde DbContext kan bijvoorbeeld worden gebruikt om selectief toegang te krijgen tot de database om informatie over gerelateerde entiteiten te verkrijgen zonder ze allemaal te laden. In het onderstaande voorbeeld wordt dit gebruikt om het aantal berichten in een blog te verkrijgen zonder de berichten te laden:

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

Hier zijn een paar dingen die u moet opmerken:

  • De constructor is privé, omdat deze alleen wordt aangeroepen door EF Core en er een andere openbare constructor is voor algemeen gebruik.
  • De code die gebruikmaakt van de geïnjecteerde service (d.i.v. de context) is een verdediging tegen het null afhandelen van gevallen waarin EF Core het exemplaar niet maakt.
  • Omdat de service is opgeslagen in een eigenschap lezen/schrijven, wordt deze opnieuw ingesteld wanneer de entiteit is gekoppeld aan een nieuw contextexemplaar.

Waarschuwing

Het injecteren van dbContext zoals dit wordt vaak beschouwd als een antipatroon, omdat uw entiteitstypen rechtstreeks aan EF Core worden gekoppeld. Overweeg zorgvuldig alle opties voordat u service-injectie op deze manier gebruikt.