Sunteți pe pagina 1din 24

3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

BrettHargreaves

SoftwareArchitect&Developer

AzureTableStoragewithRepositoryPattern

IloveWindowsAzure.Ilovetheexibilityitgives.Youcanstartsmallatalowcost,and
graduallyincreasestorage,CPUandmemoryasrequired.

Thisisespeciallygreatforstartupsorifyouarepilotingapotentialnewapp/service.

WiththisinmindIwasrecentlyengagedtobuildsuchapilotapplication.Thebriefwasthatto
beginwiththecostshadtobeaslowaspossible.Inparticularweneededcheapdatastoragebut
withthepotentialthattherequirementcouldbebutterabytesworthofstorageinthefuture.

ThesetworequirementspreymuchruledoutAzureSQLforabackend.AlthoughAzureSQLis
great,itisntthecheapestoption,andtherearenitelimitsonthesizesofdatabasesyoucan
have.Soifwedidgodownthatroutewedneedtolookatpartitioning,shardingorsomeother
managedwayofspliingthedataacrossdatabases.

Finally,becausethemajorityofthedatawouldberowsoftransactionsAzureTableStorage
seemedtotthebillnicely.

Andsowestartedtodesign/buildanewWebAPIwithanAzureTableStoragebackend.

ThereisalsosomethingelseIloveSOLIDprincipals.Requirementschange.AndIliketobe
abletochangeonebitofmycodewithoutitcausingheadachesthroughouttheapplication.

IFIweregoingtouseSQLIwouldemployaUnitOfWork/RepositorypaernandEntity
FrameworkforthedatapersistencelayerandsoIdecidedIwantedtoemploythesame
approachtothisproject.

IwantTableStorageattheback,withaWebAPIinthefront,howeverIwantedtheWebAPItobe
totallyignorantaboutstoragemechanismusedafterall,itmayturnoutthatIneedtochangeto
somethingelseatsomepointinthefuture.

SowhatIneedtodoiscreateaTableStorageproviderthatIcanpassanyEntitytooandhaveit
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 1/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

SowhatIneedtodoiscreateaTableStorageproviderthatIcanpassanyEntitytooandhaveit
performtherelevantCRUDoperations.

IthenwanttostickarepositoryandUnitOfWorkinfrontofthatprovider.ThatwayifIchange
toSQLinthefutureIjusthavetoswapoutmyrepositoryimplementationforonethatusesEntity
Frameworkinstead.

Finally,IcouldalsojustimplementtheAzurestudirectlywithinarepositorybutbypuingit
inaproviderandreferencingthatprovideritallowsmemoreexibilityifIwantedtomix
providerse.g.halfSQLhalfnoSQLetc.

OneotherpointbeforeIbeginAzurecanstoredierentmodelsallinthesametable.Sowe
couldstorecustomersANDinvoiceLinesinthesametable!ThereforewehaveaParitionKeythat
allowsyoutoseparatethedierentmodelsinsomeway.YoudontHAVEtodoitlikethisyou
couldstillhaveatableperentity,butmyappisgoingtobemultitenantandsoIwantonetable
pertenantthediscussionsastowhyandwhatisbestarequiteindepth,andinfactthereisa
verygooddiscussiononitinthePaernsandPracticesLibrary.

ForthepurposesofthisarticlewearegoingtohaveaTenantIdandthiswillbeusedtodene
ourtablename,andeachEntitiesnamewillbetheParitionKey.Inthiswayifthiswerea
traditionalSQLapptheParitionKeywouldineectbeourtablenames.Ihopethatmakessense!

BuildingtheRepository

Thiswontbeacompleteblowbyblowwalkthrough.Iamgoingtoassumeyouknowsome
basicshowtocreateaproject,asolutionetc.

OK,sorstIneedmybackend.AndasstatedIwanttouseWindowsAzureTableStoragerather
thanSQLasIwantsomethingveryexible,butcheap.Imalsonottoofussedaboutrelationships
(atleastfornow!).

SorstofallweneedanAzureAccountandinparticularaStorageAccount.Therearelotsof
tutorialsonhowtodothisbutinanutshell,logintoWindowsAzureclickNew>Data
Services>Storage.

Oncecreatedyoullneedyouraccesskeys.WiththeStorageAccountselectedjustclickManage
AccessKeys.

NextletscreateourWebAPIinVisualStudiocreateanewProject.Asthiswillbesplitintoa
numberofprojectsrstcreateanEmptySolution.

Nowaddanewprojecttothesolution.SelectWindowsfromthelistoftemplatesandClass
Libraryfromtheoptionspresented.CallitModelandclickOK.

CreateaclasscalledCustomerandletscreatesomebasicstuinit.

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 2/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

namespaceModel
{
publicclassCustomer
{
publicGuidCustomerId{get;set;}
publicstringName{get;set;}
publicstringCompany{get;set;}
publicstringEmail{get;set;}
publicstringTelephone{get;set;}
publicstringVATNumber{get;set;}
}
}

OK,sonowwehavethemodelweneedawayofpersistingit.

UsingAzureTablesisfairlystraightforward.YoucreateaCloudStorageAccountobject,passtoit
aconnectionstring,thencreateaCloudTableClientandaCloudtable.Theconnectionstringinfo
comesfromtheManageAccessKeysyoudidintheAzurePortal.

Soforexample:

stringconnectionString="DefaultEndpointsProtocol=http;AccountName=
<yourstorageaccount>;AccountKey=<youraccountkey>";
CloudStorageAccountstorageAccount=
CloudStorageAccount.Parse(connectionString);
CloudTableClienttableClient=
storageAccount.CreateCloudTableClient();
CloudTabletable=
tableClient.GetTableReference("MyTable");
table.CreateIfNotExists();

Youthenusevariousmethodstopersistandreadthedata.

HoweverwhatIdontwanttotohavetodothismanuallyforeverymodel.WhatIreallywantto
doisusetherepositorypaerntocreateabasethatIcanpassANYmodeltoandhavegureout
whattosowithit.

SowhatweneedisanAzureTableStorageProviderthatwecanwrapallourfunctionalityin,and3/24
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

SowhatweneedisanAzureTableStorageProviderthatwecanwrapallourfunctionalityin,and
thenexposeitviaaRepositoryclass.Sotherstjobistocreateourprovider.

CreateanewClassLibraryProjectcalledAzureTSProvider,andthencreateanewclasscalled
TableSet.

YoualsoneedtoaddtheAzureNuGetpackagessorightclickthenewproject,selectManage
NuGetPackagesandsearchforWindowsAzureStorage.ClickInstall,accepttheLicensesand
youregoodtogo.

ThisisgoingtobeaGenericClassthisallowsustopassinANYclassweconstruct,andactonit
regardless.Wedothisbyseing<TEntity>intheclassdeclarationandstatingwhereTEntity:
class.Weneedanew()keywordinthedeclarationaswelltotellourgenerictocreateanew
objectittheonepassedtoitisnull.

NOTEitdoesnthavetobeTEntitybythewayyoucancallitwhateveryoulike!

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 4/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingMicrosoft.WindowsAzure.Storage;
usingMicrosoft.WindowsAzure.Storage.Table;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Threading.Tasks;

namespaceAzureTSProvider
{
publicclassTableSet<TEntity>
whereTEntity:class,
new()
{
privateList<dynamic>internalList;
privatestringpartitionKey;
privatestringtableName;
privatestringconnectionString;

internalCloudTableClienttableClient;
internalCloudTabletable;

publicTableSet(stringconnectionString,stringtableName)
{
this.partitionKey=typeof(TEntity).Name;
this.tableName=tableName;
this.connectionString=connectionString;

//pluralisethepartitionkey(becausebasicallyitisthe
'table'name).
if(partitionKey.Substring(partitionKey.Length1,
1).ToLower()=="y")
partitionKey=partitionKey.Substring(0,
partitionKey.Length1)+"ies";

if(partitionKey.Substring(partitionKey.Length1,
1).ToLower()!="s")
partitionKey=partitionKey+"s";

CloudStorageAccountstorageAccount=
CloudStorageAccount.Parse(connectionString);
tableClient=storageAccount.CreateCloudTableClient();
table=tableClient.GetTableReference(tableName);
table.CreateIfNotExists();
}
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 5/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

publicvirtualTEntityGetByID(objectid)
{
varquery=new
TableQuery().Where(TableQuery.GenerateFilterCondition("RowKey",
QueryComparisons.Equal,id.ToString()));
varresult=table.ExecuteQuery(query).First();

returnresult;
}

publicvirtualList<TEntity>GetAll()
{
varquery=new
TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey",
QueryComparisons.Equal,partitionKey));//getallcustomersbecause
Customerisourpartitionkey
varresult=table.ExecuteQuery(query).ToList();
}

publicvirtualvoidInsert(TEntityentity)
{
TableOperationinsertOperation=
TableOperation.Insert(entity);
table.Execute(insertOperation);
}
}
}

ToDTOornottoDTO?

ImgoingtofastforwardabitheretoexplainwhyIdidwhatImgoingtodonext.ImagineIve
hookedthisintobyrepositoryandUnitOfWork,andthenaWebAPIControllerthatcreatesa
customerobjectandtriestopersistitthroughmyframework.

Iimmediatelyranintoanissue.Youseeinorderfortablestoragetoworkwithourmodelthe
entitieswecreatehavetoimplementinheritfromTableEntity.Idontwanttodothis.Why?
BecauseIwantmyAPItohavenoreferencetoAzurewhatsoever.OrevenmyRepository.
Everythingandanythingtodowiththepersistenceoftheactualdataneedstobeneatly
encapsulatedinmyproviderclasses.

OK,sowhatweneedisaDataTransformationObject(DTO).TheDTOwillinheritfrom
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 6/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

OK,sowhatweneedisaDataTransformationObject(DTO).TheDTOwillinheritfrom
TableEntity,andIcouldeithermanuallycopypropereiesbetweenmyEntityandtheDTOoruse
somethinglikeAutomapper.

ButnowmyissueisthatIhavetopassinaDTOalongwithmyEntity.Wecouldjustsaythisisa
requirementbutapartfromnotbeingveryneatImbasicallyhavingtoduplicateeveryentityI
createwithaDTOversionallthepropertieswouldbeidentical.

SoIdecidedthatthebestwayforwardwouldbetojustcreateaDTOontheyusingthe
propertiesofmypassedinentity.

ThankgoodnessforDynamicObjectsastheseallowmetodojustthis!

FirstIcreateanemptybaseDTOwhichisaclassthatinheritsfromDynamicObjectand
ITableEntity.ItcreatesusanobjectthatimplementseverythingAzurewantsbutbecauseit
inhertitsfromDynamicObjectitisexpandableandsoallowsustoaddmorepropertiestoitThis
iscreatedinmyproviderlibrarysocreateanewclasscalledTableEntityDTOandpasteinthe
following;

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 7/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingMicrosoft.WindowsAzure.Storage;
usingMicrosoft.WindowsAzure.Storage.Table;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Dynamic;

namespaceAzureTSProvider
{
publicclassTableEntityDTO:DynamicObject,ITableEntity
{
#regionITableEntityproperties
//Summary:
//Getsorsetstheentity'scurrentETag.Setthisvalue
to'*'inorderto
//blindlyoverwriteanentityaspartofanupdate
operation.
publicstringETag{get;set;}
//
//Summary:
//Getsorsetstheentity'spartitionkey.
publicstringPartitionKey{get;set;}
//
//Summary:
//Getsorsetstheentity'srowkey.
publicstringRowKey{get;set;}
//
//Summary:
//Getsorsetstheentity'stimestamp.
publicDateTimeOffsetTimestamp{get;set;}
#endregion

//UsethisDictionarystoretable'sproperties.
publicIDictionary<string,EntityProperty>properties{get;
privateset;}

publicTableEntityDTO()
{
properties=newDictionary<string,EntityProperty>();
}

publicTableEntityDTO(stringPartitionKey,stringRowKey)
{
this.PartitionKey=PartitionKey;
this.RowKey=RowKey;
properties=newDictionary<string,EntityProperty>();
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 8/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

properties=newDictionary<string,EntityProperty>();
}

#regionoverrideDynamicObject'smehtods
publicoverrideboolTryGetMember(GetMemberBinderbinder,out
objectresult)
{
if(!properties.ContainsKey(binder.Name))
properties.Add(binder.Name,
ConvertToEntityProperty(binder.Name,null));
result=properties[binder.Name];
returntrue;
}

publicoverrideboolTrySetMember(SetMemberBinderbinder,
objectvalue)
{
EntityPropertyproperty=
ConvertToEntityProperty(binder.Name,value);

if(properties.ContainsKey(binder.Name))
properties[binder.Name]=property;
else
properties.Add(binder.Name,property);

returntrue;
}

publicboolTrySetMember(stringbinder,objectvalue)
{
EntityPropertyproperty=ConvertToEntityProperty(binder,
value);

if(properties.ContainsKey(binder))
properties[binder]=property;
else
properties.Add(binder,property);

returntrue;
}

#endregion

#regionITableEntityimplementation

publicvoidReadEntity(IDictionary<string,EntityProperty>
properties,OperationContextoperationContext)
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 9/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

properties,OperationContextoperationContext)
{
this.properties=properties;
}

publicIDictionary<string,EntityProperty>
WriteEntity(OperationContextoperationContext)
{
returnthis.properties;
}

#endregion

///<summary>
///ConvertobjectvaluetoEntityProperty.
///</summary>
privateEntityPropertyConvertToEntityProperty(stringkey,
objectvalue)
{
if(value==null)returnnewEntityProperty((string)null);
if(value.GetType()==typeof(byte[]))
returnnewEntityProperty((byte[])value);
if(value.GetType()==typeof(bool))
returnnewEntityProperty((bool)value);
if(value.GetType()==typeof(DateTimeOffset))
returnnewEntityProperty((DateTimeOffset)value);
if(value.GetType()==typeof(DateTime))
returnnewEntityProperty((DateTime)value);
if(value.GetType()==typeof(double))
returnnewEntityProperty((double)value);
if(value.GetType()==typeof(Guid))
returnnewEntityProperty((Guid)value);
if(value.GetType()==typeof(int))
returnnewEntityProperty((int)value);
if(value.GetType()==typeof(long))
returnnewEntityProperty((long)value);
if(value.GetType()==typeof(string))
returnnewEntityProperty((string)value);
thrownewException("Thisvaluetype"+value.GetType()+"
for"+key);
thrownewException(string.Format("Thisvaluetype{0}is
notsupportedfor{1}",key));
}

///<summary>
///Gettheedmtype,ifthetypeisnotaedmtypethrowa
exception.
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 10/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

exception.
///</summary>
privateTypeGetType(EdmTypeedmType)
{
switch(edmType)
{
caseEdmType.Binary:
returntypeof(byte[]);
caseEdmType.Boolean:
returntypeof(bool);
caseEdmType.DateTime:
returntypeof(DateTime);
caseEdmType.Double:
returntypeof(double);
caseEdmType.Guid:
returntypeof(Guid);
caseEdmType.Int32:
returntypeof(int);
caseEdmType.Int64:
returntypeof(long);
caseEdmType.String:
returntypeof(string);
default:thrownewTypeLoadException(string.Format("not
supportededmType:{0}",edmType));
}
}
}
}

NowinmyTableSetclassIcreatetwomethods.TherstisCreateDTOwhichtakesmysource
entity,createsadynamicentity,copiesallthepropertiesfrommysourceentityandthencopiesall
thepropertiesfrommyDTOentity.

IalsohaveaGetIdmethodthatIusetoscaneachsourceentitypropertytoseeifitsanIDwell
needthisfortheRowKeythatAzureneedstocreateaprimarykey.Itsfairlysimplisticbut
suitsmyneeds.

FinallyIhaveaStripDTOmethodthatessentialmapsourDTObacktothebaseEntityforreturn
queries.

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 11/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

#regionobjectmapping
dynamicCreateDTO(objecta)
{
TableEntityDTOdto=newTableEntityDTO();
objectrowKey=null;

Typet1=a.GetType();
Typet2=dto.GetType();

//nowsetalltheentityproperties
foreach(System.Reflection.PropertyInfopin
t1.GetProperties())
{
dto.TrySetMember(p.Name,p.GetValue(a,null)==null?
"":p.GetValue(a,null));
if(IsId(p.Name))
rowKey=p.GetValue(a,null);
}

if(rowKey==null)
rowKey=Guid.NewGuid();

dto.RowKey=rowKey.ToString();
dto.PartitionKey=partitionKey;

returndto;
}

TEntity
StripDTO(Microsoft.WindowsAzure.Storage.Table.DynamicTableEntitya)
{
TEntityresult=newTEntity();

Typet1=result.GetType();
vardictionary=(IDictionary<string,
EntityProperty>)a.Properties;

foreach(PropertyInfop1int1.GetProperties())//foreach
propertyintheentity,
{
foreach(varvalueindictionary)//seeifwehavea
correspindingpropertyintheDTO
{
if(p1.Name==value.Key)
{
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 12/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

{
p1.SetValue(result,GetValue(value.Value));
}
}

returnresult;
}

privateobjectGetValue(EntityPropertysource)
{
switch(source.PropertyType)
{
caseEdmType.Binary:
return(object)source.BinaryValue;
caseEdmType.Boolean:
return(object)source.BooleanValue;
caseEdmType.DateTime:
return(object)source.DateTimeOffsetValue;
caseEdmType.Double:
return(object)source.DoubleValue;
caseEdmType.Guid:
return(object)source.GuidValue;
caseEdmType.Int32:
return(object)source.Int32Value;
caseEdmType.Int64:
return(object)source.Int64Value;
caseEdmType.String:
return(object)source.StringValue;
default:thrownewTypeLoadException(string.Format("not
supportededmType:{0}",source.PropertyType));
}
}

privateboolIsId(stringcandidate)
{
boolresult=false;

if(candidate.ToLower()=="id")
result=true;

if(candidate.ToLower().Substring(candidate.Length2,2)
=="id")
result=true;

returnresult;
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 13/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

returnresult;
}

#endregion

NowweupdateourCRUDmethodstousetheDTOsthus;

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 14/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

publicvirtualTEntityGetByID(objectid)
{
varquery=new
TableQuery().Where(TableQuery.GenerateFilterCondition("RowKey",
QueryComparisons.Equal,id.ToString()));
vardto=table.ExecuteQuery(query).First();
TEntitymapped=StripDTO(dto);

returnmapped;
}

publicvirtualList<TEntity>GetAll()
{
List<TEntity>mappedList=newList<TEntity>();
varquery=new
TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey",
QueryComparisons.Equal,partitionKey));//getallcustomersbecause
Customerisourpartitionkey
varresult=table.ExecuteQuery(query).ToList();

foreach(variteminresult)
{
mappedList.Add(StripDTO(item));
}
returnmappedList;
}

publicvirtualvoidInsert(TEntityentity)
{
dynamicmapped=CreateDTO(entity);
TableOperationinsertOperation=
TableOperation.Insert(mapped);
table.Execute(insertOperation);
}

OK,thenalstepfortheprovidersideistocreateaContextclassthatourRepositorywilluse.
CreateanewClasscalledTSContextandenterthefollowing;

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 15/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingSystem;

namespaceAzureTSProvider
{
publicabstractclassTSContext
{
privatestringtableName{get;set;}
privatestringconnectionString{get;set;}

publicTSContext(stringconnectionString,stringtableName)
{
this.tableName=tableName;
this.connectionString=connectionString;
}

publicvirtualTableSet<TEntity>Set<TEntity>()
whereTEntity:class,new()
{
varset=newTableSet<TEntity>(connectionString,
tableName);

returnset;
}
}
}

ThenalsteponourjourneyiscreateourabstractedRepositorybase,ouractual
CustomerRepositoryandourUnitOfWorkImnotgoingtogointotoomuchdetailwiththeseas
therearelotsofarticlesthatreallygointotheniygriyofthem.

CreateanewProjectcallDAL.AddinreferencestoourModelandAzureProviderprojects.
CreateaclasscalledAzureContextthatinheritsfromourTSContextandpastein:

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 16/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingAzureTSProvider;
usingModel;
usingSystem;

namespaceDAL
{
publicclassAzureContext:TSContext
{
privatestaticstringtableName;
privatestaticstringconnection;

publicAzureContext(stringconnectionString,stringtable)
:base(connectionString,table)
{
tableName=table;
connection=connectionString;
}

publicTableSet<Customer>Customers{get;set;}
}
}

CreateanewclasscalledRepositoryBase.

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 17/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingAzureTSProvider;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;

namespaceDAL
{
publicabstractclassRepositoryBase<TEntity>whereTEntity:
class,new()
{
internalAzureContextcontext;
internalTableSet<TEntity>dbset;

publicRepositoryBase(AzureContextcontext)
{
this.context=context;
this.dbset=context.Set<TEntity>();
}

publicvirtualTEntityGetByID(objectid)
{
returndbset.GetByID(id);
}

publicvirtualList<TEntity>GetAll()
{
returndbset.GetAll();
}

publicvirtualvoidInsert(TEntityentity)
{
dbset.Insert(entity);
}

}
}

FollowedbyourCustomerRepsitorythatinheritsfromthebaseclass.

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 18/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingModel;
usingSystem;

namespaceDAL
{
publicclassCustomerRepository:RepositoryBase<Customer>
{

publicCustomerRepository(AzureContextcontext)
:base(context)
{
if(context==null)
thrownewArgumentNullException("Contextcannotbe
null!");
}

}
}

LastbutnotleastisourUnitofworkthattiesitalltogether.NoteIammanuallyenteringmy
Azuredetailshere.YoudnormallypassitthroughfromyourWebAPIorwhateverelsecallsit,
makeacallfromacongle,oruseDependencyInjection.ThetenantIdwhichisusedasour
partitionkeyoncewegetthere,wouldcomefromsomeothertableforexamplewhenatenant
logsinwedgrabauniqueTEXTstring(PartitionkeysONLYacceptalphanumericsandcannot
startwithanumber,soGUIDSorintsarenogood!)forthattenantandpassitthroughe.g.the
usersemailaddresswithallthenonalphanumericsstrippedout.

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 19/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

usingSystem;

namespaceDAL
{
publicclassUnitOfWork:IDisposable
{
AzureContextcontext;

privateCustomerRepositorycustomerRepository;

publicUnitOfWork(stringtenantId)
{
stringconnectionString=
"DefaultEndpointsProtocol=http;AccountName=<mystorage
name>;AccountKey=<myaccountkey>";

this.context=newAzureContext(connectionString,
tenantId);
}

publicCustomerRepositoryCustomerRepository
{
get
{
if(customerRepository==null)
customerRepository=new
CustomerRepository(context);

returncustomerRepository;
}
}

publicvoidDispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

protectedvirtualvoidDispose(booldisposing)
{
if(!disposing)
{
return;
}
}
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 20/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

}
}
}

Wearenowgoodtogo!TouseitwejustmakethefollowingcallformourWebAPIorwhereever:

UnitOfWorkproxy=newUnitOfWork("test001");
Customercustomer=newCustomer()
{
Company="SquareConnectionLtd",
Email="Bretthargreaves@hotmail.com",
Name="BrettHargreaves",
Telephone="1234512345678",
VATNumber="123456789GB"
};
proxy.CustomerRepository.Insert(customer);

Obviouslyallthisisjustastartingpoint.Oneofthebiggestglaringdierencesbetweenthisanda
normalrepositoryisthatchangesareimmediatelypersisted.Whatweneedtodonextis
implementsomechangetrackinginourAzureProviderandaSaveChangesmethodthatthen
commitseverything.

Butthatsablogforanotherday!

Postedin.NETandtaggedAzure,AzureTableStorage,RepositoryPaern,UnitOfWorkPaern
onJanuary11,2014byBreHargreaves.10Comments

10comments

1.Danielsays:
February3,2014at12:57pm
Hi,Idliketoreadtheblog*and*thecode,butthetemplateyouareusingismakingittoo
dicult

IunderstandthelimitationaboutITableEntity,howeverIwouldarguethatyouarepayingthe
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 21/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

IunderstandthelimitationaboutITableEntity,howeverIwouldarguethatyouarepayingthe
exibilityofthedynamickeywithperformance,CPUandmemoryusage.Onceyoureach
someconsiderableloadonyourservers,youmightconsidermeasuringtheimpactofdynamic
entities,justathought.

Lookingforwardtoseeareadableversion,thanks

REPLY
1.BreHargreavessays:
February3,2014at3:47pm
Thanksforthecomments.

YesitseemedIfellintothatstyleovercontenttrap
OK,Iveupdatedthethemethatshouldmakecodereadingabiteasier.

REPLY
2.XcoderTeam(@xcoderteam)says:
April3,2014at1:00pm
HiMan,

Thiscodequitlong,whynotuploadthesourcecodeinziporuploadtogit.

REPLY
3.fgalarragasays:
May7,2014at6:56pm
Bre,greatpostanywayyoucouldpublishthistoaCodePlexprojectsowecangetthesource
andcollaborateonit?Thereseemnottobeanythingouttherethatworks.

LetmeknowIwouldlovetocontributeandhelp!

F.

REPLY
1.BreHargreavessays:
June13,2014at12:53pm
HithanksforthecommentsIveposteditto
hps://github.com/squareconnection/AzureTSProvider

B.

REPLY
4.BreHargreavessays:
May8,2014at12:31pm
Thanksforeveryonescomments.IhavecreatedagitHubprojecthere:
hps://github.com/squareconnection/AzureTSProvider

AsIexpandthisIwillstartperformingsomeperformancecomparisonsbetweenSQLand
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 22/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

AsIexpandthisIwillstartperformingsomeperformancecomparisonsbetweenSQLand
TableStorage,aswellastheimpactusingDTOsandthendingtheidhas.

REPLY
5.BreHargreavessays:
June13,2014at1:29pm
Imcurrentlyintheprocessofaddingbulkinsertsandsomechangetracking.Whilstinthe
processIdecidedtorunsomeperformancetests.

ForthetestsIreadanumberofproductrecordsfromanexistingdatabaseandthentimed
howlongvariouscommitstooktobothAzureTableStorageandSQL.

With2000records:
MyProvidertook4050secondstocommit(Iranthetestanumberoftimes),and5secondsto
read.
theBaseTablestoragemechanism(sodoingawaywiththeDTOsIused)alsotook4050
secondstocommitand35secondstoread.

EntityFrameworktook1or2seconds!

with100records:
MyProvider:2secondstocommit,<1toread
BaseAzure2Secondstocommit,<1toread
EntityFramework<1second.

AfewpointsonthisAzureiseectivelyawebAPIservicesocomparingittoEntityframe
workdirectlyisabitlikecomparingapplesandoranges.ReallyIshouldsetupaWebAPI
connectedtoSQLanddoitthatway.

WhatIwasmostinterestedinwastheperformanceimpactofusingaDTOandonwritesthe
dierencewasnegligibleifany(infactonacoupleofoccasionsitwasFASTERsoithinkthe
dierencewasmoredowntomyPC)andreadtimesarealsonegligibleatleastfor2k10k
records.

Iwillhopefullypostmyupdatesinthenextweekorso.

Regards

REPLY
1.fgalarragasays:
February4,2015at8:18pm
Bre,

Thankyouforpublishingthecode.Wonderingifwecouldworktogetherontogetthe
repositorytodierentproviders?WouldlovetocontributeonGitHub.Letmeknow.

Thankyou,
http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 23/24
3/23/2016 AzureTableStoragewithRepositoryPattern|BrettHargreaves

Thankyou,
F.

REPLY
1.BreHargreavessays:
February11,2015at8:36pm
HiF.

ThanksfortheoerunfortunatelyImridiculouslybusyatthemomentwhichis
partlywhyImnotabletopostasmanyupdatesasIdlike!

Sothanksagain,andmaybeinthefutureImightbeabletogetmoreinvolvedit
certainlysoundslikeaninterestingproject!

6.rbunn83815says:
May24,2015at3:03pm
Thisisreallygreatwork!IhavebeenstrugglingabittouseTableStoragelikeIwouldthe
EntityFramework.ThepartthattrippedmeupthemostwasthefactthatEntityFramework
hasalotofbuiltincodethatmakesimplementingvariouspaernsmucheasierthanwith
TableStorage.Yousavedmealotoftimeandheadacheswiththispost!Thanks!

REPLY

BLOGATWORDPRESS.COM.THESUITSTHEME.

http://bretthargreaves.com/2014/01/11/azuretablestoragewithrepositorypattern/ 24/24

S-ar putea să vă placă și