Documente Academic
Documente Profesional
Documente Cultură
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