Beruflich Dokumente
Kultur Dokumente
.NET
Myblogaboutapplicationdevelopmentonthe.NETplatform.
Implementingagenericdataaccesslayerusing
EntityFramework
Posted:May30,2013|Author:MagnusMontin|Filedunder:EntityFramework,Ntier|Tags:
.NET,C#,EntityFramework,Ntier|116Comments
Thispostisabouthowyoucandevelopagenericdataaccesslayer(DAL)withfullCRUD(Create,
Read,UpdateandDelete)supportusingEntityFramework5withplainoldCLRobjects(POCOs)
andshortlivedcontextsinadisconnectedandstatelessNtierapplication.
EntityFramework(EF)isMicrosoftsrecommendeddataaccesstechnologywhenbuildingnew
.NETapplications.Itisanobjectrelationalmappingframework(ORM)thatenablesdevelopersto
workwithrelationaldatausingdomainspecificobjectswithouthavingtowritecodetoaccess
datafromadatabase.
EFprovidesthreeoptionsforcreatingtheconceptualmodelofyourdomainentities,alsoknown
astheentitydatamodel(EDM);databasefirst,modelfirstandcodefirst.Withboththemodel
firstandcodefirstapproachesthepresumptionisthatyoudonthaveanexistingdatabasewhen
youstartdevelopingtheapplicationandadatabaseschemaiscreatedbasedonthemodel.As
databaseswithinenterpriseenvironmentsaregenerallydesignedandmaintainedbydatabase
administrators(DBAs)ratherthandevelopers,thispostwillusethedatabasefirstoptionwhere
theEDMbecomesavirtualreflectionofadatabaseorasubsetofit.
TypicallywhenyouaredoingdatabasefirstdevelopmentusingEFyouaretargetinganalready
existingdatabasebutfortestinganddemopurposesyoumayofcoursegenerateanewonefrom
scratch.ThereisawalkthroughonhowyoucancreatealocalservicebaseddatabaseinVisual
Studio2012availableonMSDNhere.
Thedatabaseusedinthisexampleisaverysimpleonecontainingonlythetwotableslisted
Thedatabaseusedinthisexampleisaverysimpleonecontainingonlythetwotableslisted
below.ThereisaonetomanyrelationshipbetweentheDepartmentandEmployeetablesmeaning
anemployeebelongstoasingledepartmentandadepartmentcanhaveseveralemployees.
CREATETABLE[dbo].[Department](
[DepartmentId]INTIDENTITY(1,1)NOTNULL,
[Name]VARCHAR(50)NULL,
PRIMARYKEYCLUSTERED([DepartmentId]ASC)
);
CREATETABLE[dbo].[Employee](
[EmployeeId]INTIDENTITY(1,1)NOTNULL,
[DepartmentId]INTNOTNULL,
[FirstName]VARCHAR(20)NOTNULL,
[LastName]VARCHAR(20)NOTNULL,
[Email]VARCHAR(50)NULL,
PRIMARYKEYCLUSTERED([EmployeeId]ASC),
CONSTRAINT[FK_Employee_Department]FOREIGNKEY([DepartmentId])
REFERENCES[dbo].[Department]([DepartmentId])
);
Ntierarchitecture
Alargeenterpriseapplicationwilltypicallyhaveoneormoredatabasestostoredataandontop
ofthisadataaccesslayer(DAL)toaccessthedatabase(s).Ontopofthistheremaybesome
repositoriestocommunicatewiththeDAL,abusinesslayercontaininglogicandclasses
representingthebusinessdomain,aservicelayertoexposethebusinesslayertoclientsand
finallysomeuserinterfaceapplicationsuchasaWPFdesktopapplicationoranASP.NETweb
application.
Userinterfacelayer
WPF/ASP.NET/ConsoleApp/WinRT/
Servicelayer
WCF/ASMX/
Businesslogiclayer
Dataaccesslayer
EF/ADO.NET/
Database
SQLServer/Oracle/MySql/
Dataaccesslayer(DAL)
TheDALissimplyaC#classlibraryprojectwhereyoudefinethemodelgeneratedfromthe
existingdatabasealongwiththegenericimplementationforreadingandmodifyingthedatain
thedatabase.Itistheonlylayerintheapplicationthatwillactuallyknowanythingaboutand
haveanydependenciesonEF.Anyuserinterfacecodeshouldonlycommunicatewiththeservice
orbusinesslayeranddonthaveanyreferencestotheDAL.
1.Startbycreatinganewclasslibraryproject(Mm.DataAccessLayer)andaddanewADO.NET
EntityDataModeltoit.ChoosetheGeneratefromdatabaseoptionintheEntityDataModel
wizard.ThewizardletsyouconnecttothedatabaseandselecttheDepartmentandEmployeetables
tobeincludedinthemodel.
Oncethewizardhascompletedthemodelisaddedtoyourprojectandyouareabletoviewitin
theEFDesigner.BydefaultallgeneratedcodeincludingthemodelclassesfortheDepartmentand
Employeeentitiessitsinthesameproject.
SeparatingentityclassesfromEDMX
Again,inanenterpriselevelapplicationwhereseparationofconcernsisofgreatimportanceyou
certainlywanttohaveyourdomainlogicandyourdataaccesslogicinseparateprojects.Inother
wordsyouwanttomovethegeneratedmodel(Model.tt)toanotherproject.Thiscaneasilybe
accomplishedbyfollowingthesesteps:
2.Addanewclasslibraryproject(Mm.DomainModel)tothesolutioninVisualStudio.
3.OpenFileExplorer(rightclickonthesolutioninVisualStudioandchoosetheOpenFolderin
FileExploreroption)andmovetheModel.ttfiletothenewprojectfolder.
4.BackinVisualStudio,includetheModel.ttfileinthenewprojectbyclickingontheShowAll
FilesiconatthetopoftheSolutionExplorerandthenrightclickontheModel.ttfileandchoose
theIncludeInProjectoption.
5.DeletetheModel.ttfilefromtheDALproject.
6.Forthetemplateinthenewdomainmodelprojecttobeabletofindthemodelyouthenneedto
modifyittopointtothecorrectEDMXpath.YoudothisbysettingtheinputFilevariableinthe
Model.tttemplatefiletopointtoanexplicitpathwheretofindthemodel:
conststringinputFile=@"../Mm.DataAccessLayer/Model.edmx";
Onceyousavethefiletheentityclassesshouldbegeneratedinthedomainmodelproject.Note
thatifyoumakeanychangestothemodelintheDALprojectlateronyouarerequiredto
explicitlyupdateyourmodelclasses.ByrightclickontheModel.tttemplatefileandchooseRun
CustomTooltheentityclasseswillberegeneratedtoreflectthelatestchangestothemodel.
7.Asthecontextbydefaultexpectstheentityclassestobeinthesamenamespace,addausing
statementfortheirnewnamespacetotheModel.Context.tttemplatefileintheDALproject:
usingSystem;
usingSystem.Data.Entity;
usingSystem.Data.Entity.Infrastructure;
usingMm.DomainModel;<!Added>
<#
if(container.FunctionImports.Any())
{
#>
usingSystem.Data.Objects;
usingSystem.Data.Objects.DataClasses;
usingSystem.Linq;
<#
}
#>
8.Finally,youneedtoaddareferencefromtheDALprojecttothedomainmodelprojectinorder
forittocompile.
DbContext
InanEFbasedapplicationacontextisresponsiblefortrackingchangesthataremadetothe
entitiesaftertheyhavebeenloadedfromthedatabase.YouthenusetheSaveChangesmethodon
thecontexttopersistthechangesbacktothedatabase.
BydefaultEDMscreatedinVisualStudio2012generatessimplePOCOentityclassesanda
contextthatderivesfromDbContextandthisistherecommendedtemplateunlessyouhavea
reasontouseoneoftheotherslistedonMSDNhere.
TheDbContextclasswasintroducedinEF4.1andprovidesasimplerandmorelightweightAPI
comparedtotheEF4.0ObjectContext.Howeveritsimplyactslikeawrapperaroundthe
ObjectContextandifyouforsomereasonneedthegranularcontrolofthelatteryoucan
implementanextensionmethodextensionmethodsenableyoutoaddmethodstoexisting
typeswithoutcreatinganewderivedtypetobeabletoconverttheDbContexttoan
ObjectContextthroughanadapter:
usingSystem.Data.Entity;
usingSystem.Data.Entity.Infrastructure;
usingSystem.Data.Objects;
namespaceMm.DataAccessLayer
{
publicstaticclassDbContextExtensions
{
publicstaticObjectContextToObjectContext(thisDbContextdbContext)
{
return(dbContextasIObjectContextAdapter).ObjectContext;
}
}
}
Encapsulatingdataaccessintorepositories
Arepositoryisresponsibleforencapsulatingthedataaccesscode.ItsitsbetweentheDALandthe
businesslayeroftheapplicationtoquerythedatasourcefordataandmapthisdatatoanentity
class,anditalsopersistschangesintheentityclassesbacktothedatasourceusingthecontext.
Arepositorytypicallyimplementsaninterfacethatprovidesasimplesetofmethodsforthe
developerusingtherepositorytocodeagainst.Usinganinterfacetheconsumerdoesntneedto
knowanythingaboutwhathappensbehindthescenes,i.e.whethertheDALusesEF,another
ORMormanuallycreatingconnectionsandcommandstoexecutequeriesagainstadatasource.
Besidestheabstractionitbringsitsalsogreatifyouareusingdependencyinjectioninyour
application.
Byusingagenericrepositoryforqueryingandpersistingchangesforyourentityclassesyoucan
maximizecodereuse.Belowisasamplegenericinterfacewhichprovidesmethodstoqueryforall
entities,specificentitiesmatchingagivenwherepredicateandasingleentityaswellasmethods
forinserting,updatingandremovinganarbitrarynumberofentities.
9.AddthebelowinterfacenamedIGenericDataRepositorytotheMm.DataAccessLayerproject.
usingMm.DomainModel;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq.Expressions;
namespaceMm.DataAccessLayer
{
publicinterfaceIGenericDataRepository<T>whereT:class
{
IList<T>GetAll(paramsExpression<Func<T,object>>[]navigationProperties);
IList<T>GetList(Func<T,bool>where,paramsExpression<Func<T,object>>[]n
TGetSingle(Func<T,bool>where,paramsExpression<Func<T,object>>[]naviga
voidAdd(paramsT[]items);
voidUpdate(paramsT[]items);
voidRemove(paramsT[]items);
}
}
IListvsIQueryable
NotethatthereturntypeofthetwoGet*methodsisIList<T>ratherthanIQueryable<T>.This
meansthatthemethodswillbereturningtheactualalreadyexecutedresultsfromthequeries
ratherthanexecutablequeriesthemselves.Creatingqueriesandreturnthesebacktothecalling
codewouldmakethecallerresponsibleforexecutingtheLINQtoEntitiesqueriesand
consequentlyuseEFlogic.Besides,whenusingEFinanNtierapplicationtherepositorytypically
createsanewcontextanddisposeitoneveryrequestmeaningthecallingcodewonthaveaccess
toitandthereforetheabilitytocausethequerytobeexecuted.Thusyoushouldalwayskeep
yourLINQqueriesinsideoftherepositorywhenusingEFinadisconnectedscenariosuchasin
anNtierapplication.
Loadingrelatedentities
EFofferstwocategoriesforloadingentitiesthatarerelatedtoyourtargetentity,e.g.getting
employeesassociatedwithadepartmentinthiscase.EagerloadingusestheIncludemethodon
theDbSettoloadchildentitiesandwillissueasinglequerythatfetchesthedataforallthe
includedentitiesinasinglecall.Eachofthemethodsforreadingdatafromthedatabaseinthe
concretesampleimplementationoftheIGenericDataRepository<T>interfacebelowsupportseager
loadingbyacceptingavariablenumberofnavigationpropertiestobeincludedinthequeryas
arguments.
10.AddanewclassnamedGenericDataRepositorytotheMM.DataAccessLayerprojectand
implementtheIGenericDataRepository<T>interface.
usingMm.DomainModel;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Data.Entity;
usingSystem.Data.Entity.Infrastructure;
usingSystem.Linq;
usingSystem.Linq.Expressions;
namespaceMm.DataAccessLayer
{
publicclassGenericDataRepository<T>:IGenericDataRepository<T>whereT:
{
publicvirtualIList<T>GetAll(paramsExpression<Func<T,object>>[]navigati
{
List<T>list;
using(varcontext=newEntities())
{
IQueryable<T>dbQuery=context.Set<T>();
//Applyeagerloading
foreach(Expression<Func<T,object>>navigationPropertyinnavigatio
dbQuery=dbQuery.Include<T,object>(navigationProperty);
list=dbQuery
.AsNoTracking()
.ToList<T>();
}
returnlist;
}
publicvirtualIList<T>GetList(Func<T,bool>where,
paramsExpression<Func<T,object>>[]navigationProperties)
{
List<T>list;
using(varcontext=newEntities())
{
IQueryable<T>dbQuery=context.Set<T>();
//Applyeagerloading
foreach(Expression<Func<T,object>>navigationPropertyinnavigatio
dbQuery=dbQuery.Include<T,object>(navigationProperty);
list=dbQuery
.AsNoTracking()
.Where(where)
.ToList<T>();
}
returnlist;
}
publicvirtualTGetSingle(Func<T,bool>where,
paramsExpression<Func<T,object>>[]navigationProperties)
{
Titem=null;
using(varcontext=newEntities())
{
IQueryable<T>dbQuery=context.Set<T>();
//Applyeagerloading
foreach(Expression<Func<T,object>>navigationPropertyinnavigatio
dbQuery=dbQuery.Include<T,object>(navigationProperty);
item=dbQuery
.AsNoTracking()//Don'ttrackanychangesfortheselecteditem
.FirstOrDefault(where);//Applywhereclause
}
returnitem;
}
/*restofcodeomitted*/
}
}
Forexample,hereshowyouwouldcalltheGetAllmethodtogetalldepartmentswithits
employeesincluded:
IGenericDataRepository<Department>repository=newGenericDataRepository<Department
IList<Department>departments=repository.GetAll(d=>d.Employees);
WithlazyloadingrelatedentitiesareloadedfromthedatasourcebyEFissuingaseparatequery
firstwhenthegetaccessorofanavigationpropertyisaccessedprogrammatically.
Dynamicproxies
ForEFtoenablefeaturessuchaslazyloadingandautomaticchangetrackingforPOCOentities,it
cancreateawrapperclassaroundthePOCOentityatruntime.Thereisacertainsetofrulesthat
yourentityclassesneedtofollowtogetthisproxybehavior.Togettheinstantchangetracking
behavioreverypropertymustbemarkedasvirtual.Forthelazyloadingtowork,thoserelated
propertiesthatyouwanttobelazilyloadedmustbemarkedasvirtualandthosewhopointtoa
setofrelatedchildobjectshavetobeoftypeICollection.Thereisacompletelistofthe
requirementsforPOCOproxiestobecreatedavailableonMSDNhereifyouwantmore
information.
Disconnectedentities
However,inanNtierapplicationentityobjectsareusuallydisconnectedmeaningtheyarenot
beingtrackedbyacontextasthedataisfetchedusingonecontext,returnedtotheclientwhere
thereisnocontexttotrackchangesandthensentbacktotheserverandpersistedbacktothe
databaseusinganotherinstanceofthecontext.Lookingatthecodeabove,anewinstanceofthe
contextwillbecreatedanddisposedforeachmethodcallandtheAsNoTrackingextension
methodalsoaddedinEF4.1isusedtotellthecontextnottotrackanychangeswhichmay
resultinbetterperformancewhenqueryingforalargenumberofentities.Whenusingshortlived
contextslikethis,youshoulddisablelazyloading.Ifyoudontanexceptionsayingthecontext
hasbeendisposedwillbethrownwheneveranoninitializednavigationpropertyisaccessed
fromanywhereoutsidethecontextsscope.
11.Lazyloadinganddynamicproxycreationisturnedoffforallentitiesinacontextbysetting
twoflagsontheConfigurationpropertyontheDbContextasshownbelow.Boththeseproperties
aresettotruebydefault.
namespaceMm.DataAccessLayer
{
usingSystem.Data.Entity;
usingSystem.Data.Entity.Infrastructure;
usingMm.DomainModel;
publicpartialclassEntities:DbContext
{
publicEntities()
:base("name=Entities")
{
Configuration.LazyLoadingEnabled=false;
Configuration.ProxyCreationEnabled=false;
}
protectedoverridevoidOnModelCreating(DbModelBuildermodelBuilder)
{
thrownewUnintentionalCodeFirstException();
}
publicDbSet<Department>Departments{get;set;}
publicDbSet<Employee>Employees{get;set;}
}
}
RootvsGraphs
WhenitcomestopersistingchangestothedatabaseyouneedtodecidewhetheryourCUD
methodsshouldacceptanentiregraphofentitiesoronlyasinglerootentitytobepassedin.A
graphofentitiesisanumberofentitiesthatreferenceeachother.Forexample,whenyouwantto
insertanewDepartmententitytothedatabasebypassingittotherepositorysAddmethodit
mighthaverelatedEmployeeobjects.InthiscasetheEmployeeobjectsbelongtothegraphandthe
Departmentobjectistherootentity.
EntityState
Ontheserverside,thingswillgeteasierifyoudecidetonotsupportgraphs.Inthiscaseyou
couldexposeanAddmethodandanUpdatemethodforeachentitytypeandthesemethods
wouldonlyoperateonastandaloneinstanceratherthanagraphofentities.EFmakesitsimpleto
implementthesemethods.Itisallaboutsettingthestateofthepassedinentityobject.Anentity
canbeinoneoffivestatesasdefinedbytheSystem.Data.EntityStateenumeration:
Added:theentityisbeingtrackedbythecontextbuthasntbeenaddedtothedatabaseyet.
Unchanged:theentityisbeingtrackedbythecontext,itexistsinthedatabasebutitsproperty
valueshavenotbeenchangedsinceitwasfetchedfromthedatabase.
Modified:theentityisbeingtrackedbythecontext,itexistsinthedatabaseandsomeorallofits
propertyvalueshavebeenmodifiedsinceitwasfetchedfromthedatabase
Deleted:theentityisbeingtrackedbythecontext,itexistsinthedatabasebutwillbedeletedon
thenextcalltotheSaveChangesmethod.
Detached:theentityisnotbeingtrackedbythecontextatall.
WhenthecontextsSaveChangesmethodiscalleditdecideswhattodobasedontheentitys
currentstate.Unchangedanddetachedentitiesareignoredwhileaddedentitiesareinsertedinto
thedatabaseandthenbecomeUnchangedwhenthemethodreturns,modifiedentitiesareupdated
inthedatabaseandthenbecomeUnchangedanddeletedentitiesaredeletedfromthedatabase
andthendetachedfromthecontext.
DbSet.Entry
YoucanexplicitlychangethestateofanentitybyusingtheDbSet.Entrymethod.Thereisnoneed
toattachtheentitytothecontextbeforeusingthismethodasitwillautomaticallydothe
attachmentifneeded.BelowistheimplementationofthegenericrepositorysAddmethod.It
explicitlysetsthestateoftheentitytobeinsertedintothedatabasetoAddedbeforecalling
SaveChangestoexecuteandcommittheinsertstatement.NotethatusingtheEntrymethodto
changethestateofanentitywillonlyaffecttheactualentitythatyoupassintothemethod.It
wontcascadethroughagraphandsetthestateofallrelatedobjects,unliketheDbSet.Add
method.
publicvirtualvoidAdd(paramsT[]items)
{
using(varcontext=newEntities())
{
foreach(Titeminitems)
{
context.Entry(item).State=System.Data.EntityState.Added;
}
context.SaveChanges();
}
}
TheimplementationfortheUpdateandRemovemethodsareverysimilartotheAddmethodas
shownbelow.Notethatallexceptionhandlinghasbeenomittedforbrevityinthesamplecode.
publicvirtualvoidUpdate(paramsT[]items)
{
using(varcontext=newEntities())
{
foreach(Titeminitems)
{
context.Entry(item).State=System.Data.EntityState.Modified;
}
context.SaveChanges();
}
}
publicvirtualvoidRemove(paramsT[]items)
{
using(varcontext=newEntities())
{
foreach(Titeminitems)
{
context.Entry(item).State=System.Data.EntityState.Deleted;
}
context.SaveChanges();
}
}
Alsonotethatallmethodshavebeenmarkedasvirtual.Thisallowsyoutooverrideanymethod
inthegenericrepositorybyaddingaderivedclassincaseswhereyouneedsomespecificlogicto
applyonlytoacertaintypeofentity.Tobeabletoextendthegenericimplementationwith
methodsthatarespecificonlytoacertaintypeofentity,whetheritsaninitialrequirementora
possiblefutureone,itsconsideredagoodpracticetodefinearepositoryperentitytypefromthe
beginning.Youcansimplyinherittheserepositoriesfromthegenericoneasshownbelowand
addmethodstoextendthecommonfunctionalitybasedonyourneeds.
12.AddinterfacesandclassestorepresentspecificrepositoriesfortheDepartmentandEmployee
entitiestotheDALproject.
usingMm.DomainModel;
namespaceMm.DataAccessLayer
{
publicinterfaceIDepartmentRepository:IGenericDataRepository<Department>
{
}
publicinterfaceIEmployeeRepository:IGenericDataRepository<Employee>
{
}
publicclassDepartmentRepository:GenericDataRepository<Department>,IDepartme
{
}
publicclassEmployeeRepository:GenericDataRepository<Employee>,IEmployeeRepo
{
}
}
Businesslayer
Asmentionedbefore,therepositoryislocatedsomewherebetweentheDALandthebusiness
layerinatypicalNtierarchitecture.Thebusinesslayerwilluseittocommunicatewiththe
databasethroughtheEDMintheDAL.Anyclientapplicationwillbehappilyunawareofany
detailsregardinghowdataisfetchedorpersistedontheserverside.Itstheresponsibilityofthe
businesslayertoprovidemethodsfortheclienttousetocommunicatewiththeserver.
13.Addanewproject(Mm.BusinessLayer)tothesolutionwithreferencestotheDALproject
(Mm.DataAccessLayer)andtheprojectwiththedomainclasses(Mm.DomainModel).Thenadda
newinterfaceandaclassimplementingthisinterfacetoittoexposemethodsforcreating,reading,
updatinganddeletingentitiestoanyclientapplication.
Belowisasampleimplementation.Inarealworldapplicationthemethodsinthebusinesslayer
Belowisasampleimplementation.Inarealworldapplicationthemethodsinthebusinesslayer
wouldprobablycontaincodetovalidatetheentitiesbeforeprocessingthemanditwouldalsobe
catchingandloggingexceptionsandmaybedosomecachingoffrequentlyuseddataaswell.
usingMm.DomainModel;
usingSystem.Collections.Generic;
usingMm.DataAccessLayer;
namespaceMm.BusinessLayer
{
publicinterfaceIBusinessLayer
{
IList<Department>GetAllDepartments();
DepartmentGetDepartmentByName(stringdepartmentName);
voidAddDepartment(paramsDepartment[]departments);
voidUpdateDepartment(paramsDepartment[]departments);
voidRemoveDepartment(paramsDepartment[]departments);
IList<Employee>GetEmployeesByDepartmentName(stringdepartmentName);
voidAddEmployee(Employeeemployee);
voidUpdateEmploee(Employeeemployee);
voidRemoveEmployee(Employeeemployee);
}
publicclassBuinessLayer:IBusinessLayer
{
privatereadonlyIDepartmentRepository_deptRepository;
privatereadonlyIEmployeeRepository_employeeRepository;
publicBuinessLayer()
{
_deptRepository=newDepartmentRepository();
_employeeRepository=newEmployeeRepository();
}
publicBuinessLayer(IDepartmentRepositorydeptRepository,
IEmployeeRepositoryemployeeRepository)
{
_deptRepository=deptRepository;
_employeeRepository=employeeRepository;
}
publicIList<Department>GetAllDepartments()
{
return_deptRepository.GetAll();
}
publicDepartmentGetDepartmentByName(stringdepartmentName)
{
return_deptRepository.GetSingle(
d=>d.Name.Equals(departmentName),
d=>d.Employees);//includerelatedemployees
}
publicvoidAddDepartment(paramsDepartment[]departments)
{
/*Validationanderrorhandlingomitted*/
_deptRepository.Add(departments);
}
publicvoidUpdateDepartment(paramsDepartment[]departments)
{
/*Validationanderrorhandlingomitted*/
_deptRepository.Update(departments);
}
publicvoidRemoveDepartment(paramsDepartment[]departments)
{
/*Validationanderrorhandlingomitted*/
_deptRepository.Remove(departments);
}
publicIList<Employee>GetEmployeesByDepartmentName(stringdepartmentName)
{
return_employeeRepository.GetList(e=>e.Department.Name.Equals(departm
}
publicvoidAddEmployee(Employeeemployee)
{
/*Validationanderrorhandlingomitted*/
_employeeRepository.Add(employee);
}
publicvoidUpdateEmploee(Employeeemployee)
{
/*Validationanderrorhandlingomitted*/
_employeeRepository.Update(employee);
}
publicvoidRemoveEmployee(Employeeemployee)
{
/*Validationanderrorhandlingomitted*/
_employeeRepository.Remove(employee);
}
}
}
Client
Aclientapplicationconsumingtheseversidecodewillonlyneedreferencestothebusinesslayer
andtheentityclassesdefinedintheMm.DomainModelproject.BelowisasimpleC#console
applicationtotestthefunctionalityprovidedbythebusinesslayer.Itsimportanttonotethat
therearenoreferencesordependenciestoEFinthisapplication.InfactyoucouldreplacetheEF
basedDALwithanotheroneusingrawTSQLcommandstocommunicatewiththedatabase
withoutaffectingtheclientsidecode.TheonlythingintheconsoleapplicationthathintsthatEF
maybeinvolvedistheconnectionstringthatwasgeneratedintheDALprojectwhentheEDM
wascreatedandhastobeaddedtotheapplicationsconfigurationfile(App.config).Connection
stringsusedbyEFcontaininformationabouttherequiredmodel,themappingfilesbetweenthe
modelandthedatabaseandhowtoconnecttothedatabaseusingtheunderlyingdataprovider.
14.TobeabletotestthefunctionalityofthebusinesslayerandtheDAL,createanewconsole
applicationandaddreferencestotheMm.BusinessLayerprojectandtheMm.DomainModelproject.
usingMm.BusinessLayer;
usingMm.DomainModel;
usingSystem;
usingSystem.Collections.Generic;
namespaceMm.ConsoleClientApplication
{
classProgram
{
staticvoidMain(string[]args)
{
IBusinessLayerbusinessLayer=newBuinessLayer();
/*Createsomedepartmentsandinsertthemtothedatabasethroughtheb
Departmentit=newDepartment(){Name="IT"};
Departmentsales=newDepartment(){Name="Sales"};
Departmentmarketing=newDepartment(){Name="Marketing"};
businessLayer.AddDepartment(it,sales,marketing);
/*Getalistofdepartmentsfromthedatabasethroughthebusinesslaye
Console.WriteLine("Existingdepartments:");
IList<Department>departments=businessLayer.GetAllDepartments();
foreach(Departmentdepartmentindepartments)
Console.WriteLine(string.Format("{0}{1}",department.DepartmentId
/*Addanewemployeeandassignittoadepartment*/
Employeeemployee=newEmployee()
{
FirstName="Magnus",
LastName="Montin",
DepartmentId=it.DepartmentId
};
businessLayer.AddEmployee(employee);
/*Getasingledepartmentbyname*/
it=businessLayer.GetDepartmentByName("IT");
if(it!=null)
{
Console.WriteLine(string.Format("Employeesatthe{0}department:"
foreach(Employeeeinit.Employees)
Console.WriteLine(string.Format("{0},{1}",e.LastName,e.FirstN
};
/*Updateanexistingdepartment*/
it.Name="ITDepartment";
businessLayer.UpdateDepartment(it);
/*Removeemployee*/
it.Employees.Clear();
businessLayer.RemoveEmployee(employee);
/*Removedepartments*/
businessLayer.RemoveDepartment(it,sales,marketing);
Console.ReadLine();
}
}
}
Persistingdisconnectedgraphs
Whileavoidingthecomplexityofacceptinggraphsofobjectstobepersistedatoncemakeslife
easierforserversidedevelopers,itpotentiallymakestheclientcomponentmorecomplex.Asyou
mayhavenoticedbylookingatthecodeforthebusinesslayerabove,youarealsolikelytoendup
withalargenumberofoperationsexposedfromtheserver.Ifyoudowantyourbusinesslayerto
beabletohandlegraphsofobjectstopassedinandbepersistedcorrectly,youneedawayof
determiningwhatchangesweremadetothepassedinentityobjectsinorderforyoutosettheir
statescorrectly.
Forexample,considerascenariowhenyougetaDepartmentobjectrepresentingagraphwith
relatedEmployeeobjects.Ifallentitiesinthegrapharenew,i.e.arenotyetinthedatabase,youcan
simplycalltheDbSet.AddmethodtosetthestateofallentitiesinthegraphtoAddedandcallthe
SaveChangestopersistthechanges.Iftherootentity,theDepartmentinthiscase,isnewandall
relatedEmployeeobjectsareunchangedandalreadyexistinginthedatabaseyoucanusethe
DbSet.Entrymethodtochangethestateoftherootonly.Iftherootentityismodifiedandsome
relateditemshavealsobeenchanged,youwouldfirstusetheDbSet.Entrymethodtosetthestate
oftherootentitytoModified.Thiswillattachtheentiregraphtothecontextandsetthestateofthe
relatedobjectstoUnchanged.Youwillthenneedtoidentifytherelatedentitiesthathavebeen
changedandsetthestateofthesetoModifiedtoo.Finally,youmayhaveagraphwithentitiesof
varyingstatesincludingaddedones.ThebestthinghereistousetheDbSet.Addmethodtoset
thestatesoftherelatedentitiesthatweretrulyaddedtoAddedandthenusetheDbSet.Entry
methodtosetthecorrectstateoftheotherones.
Sohowdoyouknowthestateofanentitywhenitcomesfromadisconnectedsourceandhowdo
youmakeyourbusinesslayerabletopersistagraphwithavarietyofobjectswithavarietyof
states?Thekeyhereistohavetheentityobjectstracktheirownstatebyexplicitlysettingthestate
ontheclientsidebeforepassingthemtothebusinesslayer.Thiscanbeaccomplishedbyletting
allentityclassesimplementaninterfacewithastateproperty.Belowisasampleinterfaceandan
enumdefiningthepossiblestates.
namespaceMm.DomainModel
{
publicinterfaceIEntity
{
EntityStateEntityState{get;set;}
}
publicenumEntityState
{
Unchanged,
Added,
Modified,
Deleted
}
}
/*EntityclassesimplementingIEntity*/
publicpartialclassDepartment:IEntity
{
publicDepartment()
{
this.Employees=newHashSet<Employee>();
}
publicintDepartmentId{get;set;}
publicstringName{get;set;}
publicvirtualICollection<Employee>Employees{get;set;}
publicEntityStateEntityState{get;set;}
}
publicpartialclassEmployee:IEntity
{
publicintEmployeeId{get;set;}
publicintDepartmentId{get;set;}
publicstringFirstName{get;set;}
publicstringLastName{get;set;}
publicstringEmail{get;set;}
publicvirtualDepartmentDepartment{get;set;}
publicEntityStateEntityState{get;set;}
}
Withthissolution,thebusinesslayerwillknowthestateofeachentityinapassedingraph
Withthissolution,thebusinesslayerwillknowthestateofeachentityinapassedingraph
assumingthestateshavebeensetcorrectlyintheclientapplication.Therepositorywillneeda
helpermethodtoconvertthecustomEntityStatevaluetoaSystem.Data.EntityStateenumeration
value.ThebelowstaticmethodcanbeaddedtotheGenericDataRepository<T>classintheDALto
takescareofthis.
protectedstaticSystem.Data.EntityStateGetEntityState(Mm.DomainModel.EntityStatee
{
switch(entityState)
{
caseDomainModel.EntityState.Unchanged:
returnSystem.Data.EntityState.Unchanged;
caseDomainModel.EntityState.Added:
returnSystem.Data.EntityState.Added;
caseDomainModel.EntityState.Modified:
returnSystem.Data.EntityState.Modified;
caseDomainModel.EntityState.Deleted:
returnSystem.Data.EntityState.Deleted;
default:
returnSystem.Data.EntityState.Detached;
}
}
Next,youneedtospecifyaconstraintontheIGenericDataRepository<T>interfaceandthe
GenericDataRepository<T>classtoensurethatthetypeparameterTimplementstheIEntity
interfaceandthenmakesomemodificationstotheCUDmethodsintherepositoryasperbelow.
NotethattheUpdatemethodwillactuallybeabletodoalltheworknowasitbasicallyonlysets
theSystem.Data.EntityStateofanentitybasedonthevalueofthecustomenumproperty.
publicinterfaceIGenericDataRepository<T>whereT:class,IEntity{...}
publicvirtualvoidAdd(paramsT[]items)
{
Update(items);
}
publicvirtualvoidUpdate(paramsT[]items)
{
using(varcontext=newEntities())
{
DbSet<T>dbSet=context.Set<T>();
foreach(Titeminitems)
{
dbSet.Add(item);
foreach(DbEntityEntry<IEntity>entryincontext.ChangeTracker.Entries<I
{
IEntityentity=entry.Entity;
entry.State=GetEntityState(entity.EntityState);
}
}
context.SaveChanges();
}
}
publicvirtualvoidRemove(paramsT[]items)
{
Update(items);
}
Alsonotetheykeytoallthisworkingisthattheclientapplicationmustsetthecorrectstateofan
entityastherepositorywillbetotallydependentonthis.Finally,belowissomeclientsidecode
thatshowshowtosetthestateofentitiesandpassingagraphofobjectstothebusinesslayer.
usingMm.BusinessLayer;
usingMm.DomainModel;
usingSystem;
usingSystem.Collections.Generic;
namespaceMm.ConsoleClientApplication
{
classProgram
{
staticvoidMain(string[]args)
{
IBusinessLayerbusinessLayer=newBuinessLayer();
/*Createadepartmentgraphwithtworelatedemployeeobjects*/
Departmentit=newDepartment(){Name="IT"};
it.Employees=newList<Employee>
{
newEmployee{FirstName="Donald",LastName="Duck",EntityState=Enti
newEmployee{FirstName="Mickey",LastName="Mouse",EntityState=Ent
};
it.EntityState=EntityState.Added;
businessLayer.AddDepartment(it);
/*AddanotheremployeetotheITdepartment*/
Employeeemployee=newEmployee()
{
FirstName="Robin",
LastName="Hood",
DepartmentId=it.DepartmentId,
EntityState=EntityState.Added
};
/*andchangethenameofthedepartment*/
it.Name="InformationTechnologyDepartment";
it.EntityState=EntityState.Modified;
foreach(Employeeempinit.Employees)
emp.EntityState=EntityState.Unchanged;
it.Employees.Add(employee);
businessLayer.UpdateDepartment(it);
/*Verifychangesbyqueringfortheupdateddepartment*/
it=businessLayer.GetDepartmentByName("InformationTechnologyDepartmen
if(it!=null)
{
Console.WriteLine(string.Format("Employeesatthe{0}department:"
foreach(Employeeeinit.Employees)
Console.WriteLine(string.Format("{0},{1}",e.LastName,e.FirstN
};
/*Deleteallentities*/
it.EntityState=EntityState.Deleted;
foreach(Employeeeinit.Employees)
e.EntityState=EntityState.Deleted;
businessLayer.RemoveDepartment(it);
Console.ReadLine();
}
}
}
116CommentsonImplementingagenericdataaccesslayer
usingEntityFramework
1. RicardoAranibarsays:
October17,2014at15:35
IfyouloseIEntityinyourmodeleverytimeitisregenerated.
youshouldchangeinpropertiesModel.Contex.ttthenamespacetoentities.
2. Aliensays:
October29,2014at11:34
Superbarticle:)Thoughittookmesometimetogetitfullyworkingforme.
OnethingImwonderingaboutishowtogetjustsomecolumnsfromaanentityorajoinof
entities?
ThisissoWCFwonthavetogetallthatdataandthenfilteritontheclientside.
Somequerieswouldgetverybigwithlotsofdatanotneeded.
Whatwouldbethebestpracticewhenusingyoursample.
3. MagnusMontin says:
October29,2014at18:02
HiAlien,
OnlyloadtherelatedentitiesofanentitythatarenecassaryoryoucouldreturnDataTransfer
Objects(DTOs)thatcontainsonlyasubsetofthepropertiesofanactualentityclassfromthe
businesslayerinsteadofreturningtheactualentityobject(s).DTOsareoutofthescopeofthis
articlethough.
4. Aliensays:
October30,2014at19:08
thxforthereply
IwastryingtoskipusingDTOstonotcreatemoremaintenanceofextraobjectsthanIneed.
IgotbigproblemsloadingjustthoseentitiesthatIneed.
Inevergetthatlamdaexpressionworking,eitheritssomesyntaxerrorsinitortheresult
aintwhatIwant.
Thinkyoucouldgiveanexampleofatleast2columnsandIshouldbeabletofigureoutthe
rest.
5. Aliensays:
November4,2014at17:59
Hiagain.
JustwonderifyouhaveasolutionfortheIntelliSenceforaVB.NETprojectusingyour
implementation?
IjuststartedwithoneandthefunctionstryingtogowithIntelliSensetoitsparents/children
donsntwork.
ItworksifyouspellitcorrectlybutifitseasytofixIdratherusethat:)
6. TPhelpssays:
November6,2014at03:36
Myorganizationsstandardsrequireacalltoastoredproceduretoinsertarow;thestored
procedurereturnstheinsertedrowid.Iamunsurewhatchangeswouldneedtobemadeto
thecodetoreturnalistofrowidscorrespondingtotheitemsparameterthatwereadded.Can
youprovide?
Thanksinadvance
7. VladimirVenegas says:
November18,2014at17:49
Hi,thisverygoodtutorial,Ihaveusedasthebasisfordoingsomethingsimilar.Ihavea
problemthatmaybeyoucanhelpme.
Thefollowinglinesofcodeworkfine:
IQueryablequery=_dataContext.Set();
IQueryablequery=_dataContext.Set();
query.Where(_where);
returnquery.AsNoTracking().Include(_include[0]).ToList();
Butthefollowinglinesofcodedonotbringtheinclude:
IQueryablequery=_dataContext.Set();
query.Where(_where);
foreach(varincludein_include)
{
query.Include(include);
}
returnquery.AsNoTracking().ToList();
Thisistheclassconstructor:
//Classconstructor
publicRepository(DbContextdataContext)
{
_dataContext=dataContext;
_dataContext.Configuration.LazyLoadingEnabled=false;
_dataContext.Configuration.ProxyCreationEnabled=false;
}
8. Alexandresays:
November20,2014at17:31
HiMagnus,
Ilikeyourexample.TheonlyproblemIhadwithitwastheFuncwhereinsteadofExpression,
whichmakesthefilteringandtheselecttop1occurafterretrievingeverything.Iseeyou
talkedaboutitinthecommentsalreadybutunlesssomeonechecksthecommentsorthesql
profiler,theymightneverknowwhatreallyhappens.
Thanksanyways,ithelpedmegreatly!
9. Aliensays:
November24,2014at11:17
AnotherthingthatInoticediswhentryingtoupdateManytoManyEntitiesusingthiscode.
NomatterwhatEntityStateIuseitwontwork.Itseemstoalwayswanttoinsertrowsinthe
relatedtablesinsteadofonlythelinktablecreatingaDuplicateError.
HaveImissedsomethingorcantthisbedoneusingthisapproach?
Anyonegotasolutionforthis?
10. Juliensays:
November25,2014at12:05
@Alien
Iusethispackagetoupdatemanytomanyentities:
https://github.com/refactorthis/GraphDiff
IfanyonehasabettersolutionTellusaboutit!
11. Aliensays:
December5,2014at16:57
@Julien,thxIlltakealookatthatlater
AnotherthingIjustcameacross:AcceptChangescannotcontinuebecausetheobjectskey
valuesconflictwithanotherobjectintheObjectStateManager
IvereadalotaboutthisandmanyseemstoleaveORMsjustbecauseofthis.
TheissueisthatImassigningthesameSegmentkeytwiceinthecontext,whichmucksupthe
ObjectStateManager.
FormethisisamajorbuginEF,Whatentitygraphsdonthavesameobjectreferencedto
differententities?
eg.aPersonentityhasareferencetoAddress.
Youloadallpersonswithrelateddataforsomeeventualchangesandthentrytosaveit.
ThatgraphwillcontainSAMESegmentkeyforpersonslivingatthesameaddressthusgiving
thiserrorwhentryingto:
entry.State=GetEntityState(entity.EntityState);asshownintheUpdatemethodinthecode
above.
Anyonehadthisexceptionandgotasmartsolutionforit?
IfthisisnotovercomeIthinkweneedtoleaveEForanyORMthathasthisflawinit.
Someofthegraphscanbeverybigwithreferencestoupto10maybeeven20otherentities
andthosewill
mostdefinitelyincludesameSegementkeys.
12. hamidadldoostsays:
December14,2014at10:20
Thereisaproblemwiththiscode..ifyourentityhasaforeignkeyandthereferencedentity
existsindatabase,itwilladdnewcopyofthattodbbecauseitsnotattachedtothecontext!
13. Aliensays:
December19,2014at12:04
IgotAttachworkingbyaddingthisintoGenericDataRepository
publicvoidAttach(Tentity)
{
DbSetdbSet;
varentry=newMyEntities();
dbSet=entry.Set();
dbSet.Attach(entity);
dbSet.Attach(entity);
}
AmIcorrecttoassumethatthiswillonlyattachoneentitytothecontextChangeTracker?
andnotarelationtoanyotherentity?
Letssaywegotascenariolikethis;
Entiy1=Parent
Entity2=Child
Entity1hasaonetomanyreferencetoEntity2andthusEntity2manytooneEntity1,andlets
callthisdownwardrelationchildren
WhatifIwanttoattachoneormoreEntity2stooneEntity1,eg,
Parent.children.Attach(Tchildren)thuscreatingthe
relationbetweenthese?
Formetheabovecodecantbeuseforthat.Howcanthisbedone?
14. AdamHancocksays:
May17,2015at01:12
Excellentarticle!Ivereallylearntalotfromthis,thankyou.
15. sudippurkayasthasays:
June5,2015at01:39
Hi,Canyoupleasesharethesamplecode?
16. Aliensays:
June5,2015at23:01
@sudippurkayastha
Wellhedid,thereare2pagesinthisthread!andyoujustpostedonpage3.
Checkthisabove:Previous12
BlogatWordPress.com.TheCleanHomeTheme.