Você está na página 1de 23

Magnus Montin

.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.

Você também pode gostar