Typy entit s konstruktory
Při vytváření instance entity je možné definovat konstruktor s parametry a volat tento konstruktor EF Core. Parametry konstruktoru mohou být svázány s mapovanými vlastnostmi nebo s různými druhy služeb, které usnadňují chování, jako je opožděné načítání.
Poznámka
V současné době platí, že všechny vazby konstruktoru jsou podle konvence. Konfigurace konkrétních konstruktorů, které se mají použít, se plánuje pro budoucí vydání.
Vazba na mapované vlastnosti
Představte si typický model blogu nebo příspěvku:
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; }
}
Když EF Core vytvoří instance těchto typů, například pro výsledky dotazu, nejprve zavolá výchozí konstruktor bez parametrů a pak nastaví každou vlastnost na hodnotu z databáze. Pokud však EF Core najde parametrizovaný konstruktor s názvy a typy parametrů, které odpovídají mapovaným vlastnostem, bude místo toho volat parametrizovaný konstruktor s hodnotami pro tyto vlastnosti a nenastaví jednotlivé vlastnosti explicitně. Příklad:
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; }
}
Několik věcí, které si poznamenejte:
- Ne všechny vlastnosti musí mít parametry konstruktoru. Vlastnost Post.Content například není nastavena žádným parametrem konstruktoru, takže EF Core ji nastaví po volání konstruktoru normálním způsobem.
- Typy a názvy parametrů se musí shodovat s typy a názvy vlastností, s výjimkou toho, že vlastnosti mohou být při použití parametrů ve camel-cased.
- EF Core nemůže nastavit navigační vlastnosti (například blog nebo příspěvky výše) pomocí konstruktoru.
- Konstruktor může být veřejný, soukromý nebo může mít jakoukoli jinou přístupnost. Opožděné načítání proxy serverů však vyžaduje, aby konstruktor byl přístupný z dědění třídy proxy. Obvykle to znamená, že je buď veřejná, nebo chráněná.
Vlastnosti jen pro čtení
Jakmile jsou vlastnosti nastaveny prostřednictvím konstruktoru, může dávat smysl, aby některé z nich byly jen pro čtení. EF Core to podporuje, ale je potřeba se podívat na některé věci:
- Vlastnosti bez setter nejsou mapovány podle konvence. (Při tom se obvykle mapují vlastnosti, které by se neměly mapovat, například vypočítané vlastnosti.)
- Použití automaticky generovaných hodnot klíče vyžaduje vlastnost klíče, která je určená pro čtení i zápis, protože při vkládání nových entit je potřeba nastavit hodnotu klíče generátorem klíčů.
Snadný způsob, jak se těmto věcem vyhnout, je používat soukromé settery. Příklad:
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 vidí vlastnost s privátním setterem jako pro čtení i zápis, což znamená, že všechny vlastnosti jsou mapovány jako předtím a klíč se stále dá vygenerovat.
Alternativou k použití privátních setter je vytvoření vlastností jen pro čtení a přidání explicitního mapování v OnModelCreating. Podobně lze některé vlastnosti zcela odebrat a nahradit pouze poli. Představte si například tyto typy 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; }
}
A tato konfigurace v 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);
});
}
Poznámky:
- Klíč "vlastnost" je teď pole. Nejedná se o
readonly
pole, aby bylo možné použít klíče vygenerované v úložišti. - Ostatní vlastnosti jsou vlastnosti jen pro čtení nastavené pouze v konstruktoru.
- Pokud je hodnota primárního klíče nastavena pouze ef nebo číst z databáze, není nutné ji zahrnout do konstruktoru. Tím ponecháte klíč "vlastnost" jako jednoduché pole a je jasné, že by se nemělo explicitně nastavovat při vytváření nových blogů nebo příspěvků.
Poznámka
Výsledkem tohoto kódu bude upozornění kompilátoru 169 označující, že pole se nikdy nepoužívá. To je možné ignorovat, protože EF Core ve skutečnosti používá pole extralinguistickým způsobem.
Vkládání služeb
EF Core může také vložit "služby" do konstruktoru typu entity. Můžete například zadat následující:
DbContext
– aktuální kontextová instance, kterou lze také zadat jako odvozený typ DbContext.ILazyLoader
- lazy-loading service---see the lazy-loading documentation for more detailsAction<object, string>
- lazy-loading delegate---see the lazy-loading documentation for more detailsIEntityType
– metadata EF Core přidružená k tomuto typu entity
Poznámka
V současné době je možné vloženého do nástroje EF Core pouze služby známé. Podpora vkládání aplikačních služeb se považuje za budoucí verzi.
Vložený dbContext lze například použít k selektivnímu přístupu k databázi a získat informace o souvisejících entitách bez jejich načtení. V následujícím příkladu se používá k získání počtu příspěvků v blogu bez načtení příspěvků:
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; }
}
Několik věcí, které byste si o tom mohli všimnout:
- Konstruktor je soukromý, protože je volána pouze ef Core a existuje další veřejný konstruktor pro obecné použití.
- Kód používající vloženou službu (tj. kontext) je defenzivní proti tomu
null
, aby zpracovával případy, kdy EF Core nevytvořuje instanci. - Protože služba je uložena ve vlastnosti pro čtení a zápis, bude resetována, když je entita připojena k nové instanci kontextu.
Upozorňující
Vkládání podobného dbContextu se často považuje za anti-vzor, protože spojuje typy entit přímo s EF Core. Před použitím injektáže služby pečlivě zvažte všechny možnosti.