Udostępnij za pośrednictwem


Typy jednostek z konstruktorami

Można zdefiniować konstruktor z parametrami i pozwolić EF Core wywołać ten konstruktor podczas tworzenia wystąpienia jednostki. Parametry konstruktora można powiązać z zamapowanych właściwości lub różnego rodzaju usług, aby ułatwić takie zachowania jak leniwe ładowanie.

Uwaga / Notatka

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

Wiązanie z mapowanymi właściwościami

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ą być zapisywane w stylu Pascal, podczas gdy nazwy parametrów są zapisywane w stylu camel.
  • 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 serwery proxy z leniwym ładowaniem wymagają, aby konstruktor był dostępny z klasy proxy dziedziczącej. Zazwyczaj oznacza to upublicznienie lub ochronę.

Właściwości tylko do odczytu

Gdy właściwości są ustawiane za pomocą konstruktora, można ustawić niektóre z nich jako tylko do odczytu. EF Core obsługuje to, ale są pewne kwestie, na które należy zwrócić uwagę:

  • Właściwości bez setterów 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 problemów jest użycie prywatnych setteró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 setterów prywatnych jest utworzenie właściwości rzeczywiście tylko do odczytu i dodanie bardziej jawnego mapowania w elemencie 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 elemencie 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. To nie jest pole readonly, 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 atrybut "właściwość" jako pole podstawowe i jasno wskazuje, że nie powinno być ustawiane bezpośrednio podczas tworzenia nowych blogów lub wpisów.

Uwaga / Notatka

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ż określić jako Twój pochodny typ DbContext
  • ILazyLoader - lazy-loading service – zobacz dokumentację lazy-loading, aby uzyskać więcej szczegółów
  • Action<object, string> - delegat do ładowania leniwego -- szczegóły znajdziesz w dokumentacji ładowania leniwego
  • IEntityType — metadane platformy EF Core skojarzone z tym typem jednostki

Uwaga / Notatka

Obecnie można wstrzykiwać tylko usługi znane przez EF Core. Obsługa integracji usług aplikacji jest rozważana w kontekście 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 rzeczy, na które warto zwrócić uwagę:

  • Konstruktor jest prywatny, ponieważ jest wywoływany tylko przez program EF Core i istnieje inny publiczny konstruktor do użytku ogólnego.
  • Kod, który korzysta z wstrzykniętej usługi (czyli kontekstu), jest odporny na sytuacje, gdy jest ona ustawiona na null, aby obsługiwać przypadki, w których EF Core nie tworzy wystąpienia.
  • Ponieważ usługa jest przechowywana we właściwości odczytu/zapisu, zostanie zresetowana po dołączeniu obiektu 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.