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 DbContextILazyLoader
- lazy-loading service -- zobacz dokumentację ładowania leniwego, aby uzyskać więcej szczegółówAction<object, string>
- delegat ładowania leniwego - zobacz dokumentację ładowania leniwego, aby uzyskać więcej szczegółówIEntityType
— 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.