Typy jednostek z konstruktorami

Można zdefiniować konstruktor z parametrami i wywołać ten konstruktor ef Core podczas tworzenia wystąpienia jednostki. Parametry konstruktora można powiązać z zamapowanych właściwościami lub do różnych rodzajów usług, aby ułatwić zachowanie, takie jak ładowanie leniwe.

Uwaga

Obecnie wszystkie powiązania konstruktora są zgodnie z konwencją. Zaplanowano konfigurację określonych konstruktorów do użycia w przyszłej wersji.

Wiązanie z zamapowanych właściwości

Rozważ typowy model bloga/wpisu:

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

Gdy program EF Core tworzy wystąpienia tych typów, na przykład dla wyników zapytania, najpierw wywoła domyślny konstruktor bez parametrów, a następnie ustawi każdą właściwość na wartość z bazy danych. Jeśli jednak program EF Core znajdzie sparametryzowany konstruktor z nazwami parametrów i typami, które pasują do właściwości mapowanych, zamiast tego wywoła sparametryzowany konstruktor z wartościami dla tych właściwości i nie ustawi jawnie każdej właściwości. Przykład:

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

Niektóre kwestie do zapamiętania:

  • Nie wszystkie właściwości muszą mieć parametry konstruktora. Na przykład właściwość Post.Content nie jest ustawiana przez żaden parametr konstruktora, dlatego program EF Core ustawi ją po wywołaniu konstruktora w normalny sposób.
  • Typy parametrów i nazwy muszą być zgodne z typami i nazwami właściwości, z tą różnicą, że właściwości mogą mieć wielkość liter Pascal, podczas gdy parametry są wielbłądowe.
  • Program EF Core nie może ustawić właściwości nawigacji (takich jak blog lub wpisy powyżej) przy użyciu konstruktora.
  • Konstruktor może być publiczny, prywatny lub ma inne ułatwienia dostępu. Jednak opóźnione serwery proxy wymagają, aby konstruktor był dostępny z dziedziczonej klasy serwera proxy. Zazwyczaj oznacza to upublicznienie lub ochronę.

Właściwości tylko do odczytu

Po ustawieniu właściwości za pomocą konstruktora warto wykonać niektóre z nich tylko do odczytu. Program EF Core obsługuje tę funkcję, ale istnieją pewne kwestie, które należy zwrócić uwagę na następujące kwestie:

  • Właściwości bez elementów ustawiających nie są mapowane zgodnie z konwencją. (W ten sposób ma tendencję do mapowania właściwości, które nie powinny być mapowane, na przykład właściwości obliczone).
  • Użycie automatycznie wygenerowanych wartości klucza wymaga właściwości klucza, która jest odczyt-zapis, ponieważ wartość klucza musi być ustawiana przez generator kluczy podczas wstawiania nowych jednostek.

Łatwym sposobem uniknięcia tych rzeczy jest użycie prywatnych zestawów. Przykład:

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

Program EF Core widzi właściwość z prywatnym ustawiaczem jako odczyt-zapis, co oznacza, że wszystkie właściwości są mapowane tak jak poprzednio, a klucz nadal może być generowany przez magazyn.

Alternatywą dla używania zestawów prywatnych jest utworzenie właściwości w trybie tylko do odczytu i dodanie bardziej jawnego mapowania w elemecie OnModelCreating. Podobnie niektóre właściwości można całkowicie usunąć i zastąpić tylko polami. Rozważmy na przykład następujące typy jednostek:

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

A ta konfiguracja w elemecie 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);
        });
}

Kwestie do zanotowania:

  • Klucz "property" jest teraz polem. Nie jest readonly to pole, aby można było używać kluczy generowanych przez magazyn.
  • Pozostałe właściwości są właściwościami tylko do odczytu ustawionymi tylko w konstruktorze.
  • Jeśli wartość klucza podstawowego jest ustawiana tylko przez program EF lub odczyt z bazy danych, nie ma potrzeby dołączania jej do konstruktora. Pozostawia to klucz "właściwość" jako proste pole i wyjaśnia, że nie należy go jawnie ustawiać podczas tworzenia nowych blogów lub wpisów.

Uwaga

Ten kod spowoduje wyświetlenie ostrzeżenia kompilatora "169" wskazującego, że pole nigdy nie jest używane. Można to zignorować, ponieważ w rzeczywistości program EF Core używa pola w sposób ekstralingwistyczny.

Wstrzykiwanie usług

Program EF Core może również wstrzyknąć "usługi" do konstruktora typu jednostki. Na przykład można wstrzyknąć następujące elementy:

  • DbContext — bieżące wystąpienie kontekstu, które można również wpisać jako pochodnego typu DbContext
  • ILazyLoader- lazy-loading service -- zobacz dokumentację ładowania leniwego, aby uzyskać więcej szczegółów
  • Action<object, string> - delegat ładowania leniwego - zobacz dokumentację ładowania leniwego, aby uzyskać więcej szczegółów
  • IEntityType — metadane platformy EF Core skojarzone z tym typem jednostki

Uwaga

Obecnie można wstrzykiwać tylko usługi znane przez program EF Core. Obsługa wstrzykiwania usług aplikacji jest brana pod uwagę w przyszłej wersji.

Na przykład wstrzyknięta metoda DbContext może służyć do selektywnego uzyskiwania dostępu do bazy danych w celu uzyskania informacji o powiązanych jednostkach bez ładowania ich wszystkich. W poniższym przykładzie użyto metody uzyskiwania liczby wpisów w blogu bez ładowania wpisów:

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

Kilka kwestii, które należy zwrócić uwagę na następujące kwestie:

  • Konstruktor jest prywatny, ponieważ jest wywoływany tylko przez program EF Core i istnieje inny publiczny konstruktor do użytku ogólnego.
  • Kod korzystający z usługi wstrzykniętej (czyli kontekstu) jest defensywny, null aby obsługiwać przypadki, w których program EF Core nie tworzy wystąpienia.
  • Ponieważ usługa jest przechowywana we właściwości odczytu/zapisu, zostanie zresetowana po dołączeniu jednostki do nowego wystąpienia kontekstu.

Ostrzeżenie

Wstrzykiwanie elementu DbContext w ten sposób jest często uznawane za antywzór, ponieważ łączy typy jednostek bezpośrednio z platformą EF Core. Przed użyciem iniekcji usługi należy dokładnie rozważyć wszystkie opcje.