Diskuze: Nejvhodnější návrh komunikace s databází
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Tvůrce
Zobrazeno 12 zpráv z 12.
//= Settings::TRACKING_CODE_B ?> //= Settings::TRACKING_CODE ?>
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Podobné problémy jsem řešil v ASP.NET Core aplikaci. Velkou výhodou byla
přítomnost IServiceCollection (pro konfiguraci) a DI (pro běh aplikace).
Ale něco podobného se dá udělat i v .NET FW pomocí UnityContainer.
Takže na každou drobnou (atomickou) functionalitu jsem udělal interface -
na repositories, mappery, connectory na další služby, ...
Př.:
services.AddScoped<IAuthorizationService,AuthorizationService>();
services.AddSingleton<IUserAccessCache,MemoryUserAccessCache>();
services.AddMapperSingleton<IStockItemMapperFullOut,
StockItemMapperFullOut>();
services.AddMapperGenerate<IUserAccountRegisterMapperIn>();
services.AddScoped(sp => new
XXXDbContext(Configuration["data:XXXDbConnectionString"]));
services.AddScoped(sp => new
YYYDbContext(Configuration["data:YYYDbConnectionString"]));
services.AddScoped<IStockItemRepository,
SpecialStockItemRepository>();
Všechny implementace těchto interfaců (dále jen služeb) vlastně tvoří
"tělo" aplikace.
Každý, kdo potřebuje zajistit nějakou prácičku si z DI vyžádá službu,
která tu prácičku zajistí.
Př.:
Controller potřebuje načíst data, z DI containeru získá repository a ten
zajistí načtení dat; controller nezajímá, jak to zajistí.
Repository potřebuje DbContext, tak si o něj požádá DI.
Přechod dat mezi dvěmi vrstvami musí být zajištěny pomocí kontraktů -
tříd, je mají takovou strukturu, aby pojmuly všechna data ve formě, která
vyhovuje oběma vrstvám.
Př.:
Repository má poskytnout data. Pokud ta data získá přímo z DB, bod pro něj
prostě to rovnou vrátí. Pokud ten repozitory získá data z DB v jiné
strukruře, musí to mapovat.
Př.:
interface IUserRepository
{
IUserInfo GetUserInfo(int idUser);
}
interface IUserInfo
{
string Name {get;}
IEnumerable<string> PhoneNumbers {get;}
}
public class RepoSample1:IUserRepository
{
public IUserInfo GetUserInfo(int idUser)
{
//celé to psát nebudu, ale přestav si jednoduchou tabulku (Id (int),Name
(nvarchar),PhoneNumbers (nvarchar))
//PhoneNumbers se samozřejmě musí splitovat - nejspíš na staně aplikace po
načtení z EF
}
}
public class RepoSample2:IUserRepository
{
public IUserInfo GetUserInfo(int idUser)
{
//celé to psát nebudu, ale přestav si jednoduché tabulky (Id (int),Name
(nvarchar)) a další (Id (int),IdParent (int),PhoneNumber (nvarchar))
//PhoneNumbers se samozřejmě nebudou splitovat
}
}
Výhodou tohoto je jistá imunita vůči struktuře DB.
public class RepoSample3:IUserRepository
{
public IUserInfo GetUserInfo(int idUser)
{
//získá data třeba z WCF
}
}
Jesliže má controller poslat data dále (na UI) nejspíš musí data mappovat. Na to si z DI containeru vyžádá mapper.
Mappery: ručo nebo auto
Přiznám se bez mučení, že nevím. Já jsem zvolil obé. Většinu jsem se
snažil hodit na automat a zbytek ručně. Tím automatem mám na mysli vlastí
runtime kompiler - takže mám pod kontrolou, jak pracuje.
př.:
services.AddMapperSingleton<IStockItemMapperFullOut,
StockItemMapperFullOut>(); //registrace mapperu s ručně napsanou
implementací
services.AddMapperGenerate<IUserAccountRegisterMapperIn>();
//registrace mapperu se strojově vygenerovanou implementací (za běhu
aplikace)
Přemýšlel jsem, že bych to nejdříve napsal bodově.. ale asi nejdříve něco napíšu jako shrnutí.. Zvolil jste anemický model, u něj je problém ten, že by jste u něj měl postupovat neobjektově = vysr.. vykašlat se na zbytečné abstrakce, které stejně NEFUNGUJÍ ... proč nefungují?
Povím vám už teď příběh o naivním programátorovi, který chtěl udělat dobrou věc. Vadilo mu, že by měl mít v Core vrstvě reference na Entity Framework, tak si řekl, že "o-abstrahuje DAL" a vytvori si BaseRepository (Presto, ze DBSet je repository), vytvori si UOW (DbContext je UOW také) .. a začne nad tím vším vyvíjet software.. po krátkém čase však zjistí, že je to pro něj absolutně nepoužitelné z několika důvodů¨:
A je to jeho chyba? Byl pouze naivní,.. myslel si, že jak ho OOP učí, lze zcela odstínit od reality (v tomto případě, že je pod tím nějaká databáze, že má určité technické omezení atp.)
Vaše implementace UOW je také dosti omezující.. Absolutně vám
zlikviduje například nějakou kompozitni akci..
Co když budete mít v Servise MetoduA a MetoduB - kazda bude mit uvnitr tento
UOW, ale tyto dve metody budete chtit volat z metody MetodaC a budete chtit s
temi metodami sdilet 1 UOW?
Jste nahraný... a proč potřebujete sdílet UOW? protože UOW u vás =
dbcontext. DbContext má v sobě IdentityMap.. Zkuste si schválně vytvořit
dvě instance toho DbContextu, ziskat si entitu z 1. a zkusit ji dat jako update
do 2... Entity Framework vam povi, ze tu entitu nema v identity mapě..
Samozřejmě.. je možné ji ručně attachnout .. a co se může stát? Že budete chtít udělat UPDATE,
ale ono to udělá INSERT a spadne to na duplicate key.
::::::::::NEVER EVER ::::::::::::: nevracejte do vyšší vrstvy IQueryable. Prostě to nedělejte. Nebo se zblázníte -- pokud vám to není jasné, můžu vysvětlit proč..
Za mě tedy máte vesměs tři možnosti:
NecoRepository() {
List<NejakyModelBlaBlaBla> GetAllcustomersOlderThan(int age) {
return context.Customers.Where(....x.age>age).Select(x => ...).ToList();
}
}
Jinak.. IQueryable pouzit muzes.. idealnipouziti jsou extensionmetody v DALunad IQueryable.. lze sipakvelmi efektivne interfacem oanotovat jednotlive entity a usetri ti to spoustu prace... ..
EDIT: a koukam, ze mi asi odchazi mezernik.
Díky za odpověď. Sám jsem měl pocit, že to zbytečně zesložiťuju. Scénář, kdy si nad contextem postavím "servisy", tj obalím query nad db do metod a budu z nich vracet DTO pro vyšší vrstvy (WCF -> klient -> ..) je ok?
TO NEDOPORUČUJI. ZA mě osobně si klidně vytvořte v DALu repository, nevymyslejte ale nad ním žádné zbytečnosti =nedejte ho genericky atp. Prostě class kde budete mít všechny metody co budou upravovat a vracet data.. S tim že je budou hned vracet jako PDO pro vyšší vrstvu. Zabalit tuto Dal logiku do něčeho je důležité právě proto, aby jste při problémech s výkonem mohl vnitřek změnit třeba z EF na SQL.
A WCF bude volat přímo repozitory nebo ještě nějaká mezi-vrstva? Pokud nějaká mezi-vrstva, tak mi přijde, že v ní budu dělat jen to samé. Jen vnitřně zavolám akorát Repository. Nebo opět něco vynechávám a nevidím do toho díky moc
Přesně jste to řekl. Pokud máte anemicky model, tak je většinou ve vrstvě service pouze to samé co v Repository + třeba v ní kontrolujete práva, řešíte modifikace atp.
Supr díky
Zadejte si na Yt moje jméno, najdete tam přednášku z jedné googlacke akce o programovacích paradigmatech, bohužel to Muselo být hodne povrchně, ale pointu pochytite.
Na tu už jsem koukal. A pokud nevadí, můžeme si tykat, tady na itn to je běžný.
Zobrazeno 12 zpráv z 12.