Diskuze: Nejvhodnější návrh komunikace s databází
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)
Marian Benčat:15.9.2017 20:45
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í?
- už v IRepository máte několik chyb , například to, že máte primary key na tvrdku jako INT, už todle je leaky abstrakce, dále máte v 1 jediném interfacu narvané všechny metody, co když to nebude podporovat?
- GetAll() ?? to snad ne. GetAll si muzete dovolit u domenoveho modelu, kde vite co tam prijde za data, urcite ale ne u Interfacu dalu,.. nebo opravdu chcete tahat desetitisíce řádků z databáze a poté je filtrovat in memory?
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ů¨:
- chybí mu v IRepository funkcionalita
- ztrácí ohromné množství výkonu a je to pomalé
- není schopný to paralelizovat
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:
- Pokračovat v anemickém modelu, sledovat místní seriál o eshopu (nebo o čem to bylo) a nařezat si tam tunu průšvihů
- Pokračovat v anemnickém modelu a vykašlat se na abstrakci abstrakce, která stejně nebude fungovat, protože jsou tam příliš siliné závislosti na "low levlu", vykašlete se naprosto na nějaký omyly typu GenericRepository a proste si do DALu napiste normalne dotazy:
NecoRepository() {
List<NejakyModelBlaBlaBla> GetAllcustomersOlderThan(int age) {
return context.Customers.Where(....x.age>age).Select(x => ...).ToList();
}
}
- Udělejte to skutečně OOP, coz ve sve podstate znamena.
Marian Benčat:16.9.2017 10:12
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.
Petr Nymsa:16.9.2017 11:42
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?
Marian Benčat:16.9.2017 13:29
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.
Petr Nymsa:16.9.2017 13:40
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
Marian Benčat:16.9.2017 13:47
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.
+20 Zkušeností
+2,50 Kč
Supr díky
Marian Benčat:16.9.2017 13:56
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.
Petr Nymsa:16.9.2017 13:59
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.