Skip to content

Commit 6a8fda1

Browse files
author
jaguzman
committed
Added a new azure storage table service repository context provider
1 parent d98b22f commit 6a8fda1

18 files changed

+536
-36
lines changed

DotNetToolkit.Repository.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetToolkit.Repository.Ca
5151
EndProject
5252
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetToolkit.Repository.AzureStorageBlob", "src\DotNetToolkit.Repository.AzureStorageBlob\DotNetToolkit.Repository.AzureStorageBlob.csproj", "{6B934185-F853-4664-B90A-B923690461D8}"
5353
EndProject
54+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetToolkit.Repository.AzureStorageTable", "src\DotNetToolkit.Repository.AzureStorageTable\DotNetToolkit.Repository.AzureStorageTable.csproj", "{567784ED-4989-4664-A8F5-E7AA31107A3C}"
55+
EndProject
5456
Global
5557
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5658
Debug|Any CPU = Debug|Any CPU
@@ -136,6 +138,10 @@ Global
136138
{6B934185-F853-4664-B90A-B923690461D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
137139
{6B934185-F853-4664-B90A-B923690461D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
138140
{6B934185-F853-4664-B90A-B923690461D8}.Release|Any CPU.Build.0 = Release|Any CPU
141+
{567784ED-4989-4664-A8F5-E7AA31107A3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
142+
{567784ED-4989-4664-A8F5-E7AA31107A3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
143+
{567784ED-4989-4664-A8F5-E7AA31107A3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
144+
{567784ED-4989-4664-A8F5-E7AA31107A3C}.Release|Any CPU.Build.0 = Release|Any CPU
139145
EndGlobalSection
140146
GlobalSection(SolutionProperties) = preSolution
141147
HideSolutionNode = FALSE
@@ -161,6 +167,7 @@ Global
161167
{BC5B405A-725D-47EC-8FE1-D420CFED8939} = {DD273D5E-6D6C-41FA-A0C8-646CC53C4DC3}
162168
{45E365E2-B249-4AB8-AC90-8C0CDE5CC61D} = {DD273D5E-6D6C-41FA-A0C8-646CC53C4DC3}
163169
{6B934185-F853-4664-B90A-B923690461D8} = {DD273D5E-6D6C-41FA-A0C8-646CC53C4DC3}
170+
{567784ED-4989-4664-A8F5-E7AA31107A3C} = {DD273D5E-6D6C-41FA-A0C8-646CC53C4DC3}
164171
EndGlobalSection
165172
GlobalSection(ExtensibilityGlobals) = postSolution
166173
SolutionGuid = {96973E0C-81D1-42DE-9F78-7103241B4E07}

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ If you want to use DotNetToolkit.Repository for the first time, the [Getting Sta
8080
<td><a href="https://www.nuget.org/packages/DotNetToolkit.Repository.AzureStorageBlob/"><img src="https://img.shields.io/nuget/v/DotNetToolkit.Repository.AzureStorageBlob.svg" alt="DotNetToolkit.Repository.AzureStorageBlob"></a></td>
8181
<td><a href="https://www.nuget.org/packages/DotNetToolkit.Repository.AzureStorageBlob/"><img src="https://img.shields.io/nuget/dt/DotNetToolkit.Repository.AzureStorageBlob.svg" alt="DotNetToolkit.Repository.AzureStorageBlob"></a></td>
8282
<td><a href="https://www.myget.org/feed/dotnettoolkitrepository-dev/package/nuget/DotNetToolkit.Repository.AzureStorageBlob"><img src="https://img.shields.io/myget/dotnettoolkitrepository-dev/v/DotNetToolkit.Repository.AzureStorageBlob.svg?label=myget" alt="MyGet (dev)"></a></td>
83+
</tr>
84+
<tr>
85+
<td><a href="https://github.com/johelvisguzman/DotNetToolkit.Repository/tree/master/src/DotNetToolkit.Repository.AzureStorageTable/">DotNetToolkit.Repository.AzureStorageTable</a></td>
86+
<td><a href="https://www.nuget.org/packages/DotNetToolkit.Repository.AzureStorageTable/"><img src="https://img.shields.io/nuget/v/DotNetToolkit.Repository.AzureStorageTable.svg" alt="DotNetToolkit.Repository.AzureStorageTable"></a></td>
87+
<td><a href="https://www.nuget.org/packages/DotNetToolkit.Repository.AzureStorageTable/"><img src="https://img.shields.io/nuget/dt/DotNetToolkit.Repository.AzureStorageTable.svg" alt="DotNetToolkit.Repository.AzureStorageTable"></a></td>
88+
<td><a href="https://www.myget.org/feed/dotnettoolkitrepository-dev/package/nuget/DotNetToolkit.Repository.AzureStorageTable"><img src="https://img.shields.io/myget/dotnettoolkitrepository-dev/v/DotNetToolkit.Repository.AzureStorageTable.svg?label=myget" alt="MyGet (dev)"></a></td>
8389
</tr>
8490
<tr>
8591
<th colspan="4">Caching Providers</th>
@@ -141,4 +147,4 @@ If you want more details about the project, please checkout the [project wiki](h
141147

142148
## Performance
143149

144-
Checkout the most current [benchmark results](https://github.com/johelvisguzman/DotNetToolkit.Repository/wiki/Performance)!
150+
Checkout the most current [benchmark results](https://github.com/johelvisguzman/DotNetToolkit.Repository/wiki/Performance)!

appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ after_build:
5555
- dotnet pack .\src\DotNetToolkit.Repository.Json\DotNetToolkit.Repository.Json.csproj --configuration Release
5656
- dotnet pack .\src\DotNetToolkit.Repository.Xml\DotNetToolkit.Repository.Xml.csproj --configuration Release
5757
- dotnet pack .\src\DotNetToolkit.Repository.AzureStorageBlob\DotNetToolkit.Repository.AzureStorageBlob.csproj --configuration Release
58+
- dotnet pack .\src\DotNetToolkit.Repository.AzureStorageTable\DotNetToolkit.Repository.AzureStorageTable.csproj --configuration Release
5859
- dotnet pack .\src\DotNetToolkit.Repository.Extensions.Microsoft.DependencyInjection\DotNetToolkit.Repository.Extensions.Microsoft.DependencyInjection.csproj --configuration Release
5960
- dotnet pack .\src\DotNetToolkit.Repository.Extensions.Unity\DotNetToolkit.Repository.Extensions.Unity.csproj --configuration Release
6061
- dotnet pack .\src\DotNetToolkit.Repository.Extensions.Ninject\DotNetToolkit.Repository.Extensions.Ninject.csproj --configuration Release
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<Import Project="..\..\build\common.props" />
4+
5+
<PropertyGroup>
6+
<TargetFramework>net452</TargetFramework>
7+
<AssemblyName>DotNetToolkit.Repository.AzureStorageTable</AssemblyName>
8+
<RootNamespace>DotNetToolkit.Repository.AzureStorageTable</RootNamespace>
9+
<Description>A repository context provider for the Microsoft Azure Storage Table service for storing structured NoSQL data in the cloud.</Description>
10+
</PropertyGroup>
11+
12+
<ItemGroup Condition="'$(TargetFramework)' == 'net452'">
13+
<Reference Include="System.Configuration" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="Microsoft.Azure.CosmosDB.Table" Version="2.1.1" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\DotNetToolkit.Repository\DotNetToolkit.Repository.csproj" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace DotNetToolkit.Repository.AzureStorageTable
2+
{
3+
using Configuration;
4+
using Microsoft.Azure.CosmosDB.Table;
5+
6+
/// <summary>
7+
/// Represents a repository context provider for the Microsoft Azure Storage Table service for storing structured NoSQL data in the cloud.
8+
/// </summary>
9+
/// <seealso cref="IRepositoryContext" />
10+
public interface IAzureStorageTableRepositoryContext : IRepositoryContext
11+
{
12+
/// <summary>
13+
/// Gest the cloud Table.
14+
/// </summary>
15+
CloudTable Table { get; }
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace DotNetToolkit.Repository.AzureStorageTable.Internal
2+
{
3+
using Microsoft.Azure.CosmosDB.Table;
4+
using System;
5+
using System.Reflection;
6+
using Utility;
7+
8+
internal static class AzureStorageTableConventionsHelper
9+
{
10+
public static PropertyInfo[] GetPrimaryKeyPropertyInfos(Type entityType)
11+
{
12+
Guard.NotNull(entityType, nameof(entityType));
13+
14+
var partitionKey = entityType.GetProperty(nameof(ITableEntity.PartitionKey));
15+
var rowKey = entityType.GetProperty(nameof(ITableEntity.RowKey));
16+
17+
return new[] { partitionKey, rowKey };
18+
}
19+
}
20+
}
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
namespace DotNetToolkit.Repository.AzureStorageTable.Internal
2+
{
3+
using Configuration;
4+
using Configuration.Conventions;
5+
using Extensions;
6+
using Extensions.Internal;
7+
using Microsoft.Azure.CosmosDB.Table;
8+
using Microsoft.Azure.CosmosDB.Table.Queryable;
9+
using Microsoft.Azure.Storage;
10+
using Queries;
11+
using Queries.Strategies;
12+
using System;
13+
using System.Collections.Generic;
14+
using System.Configuration;
15+
using System.Linq;
16+
using System.Reflection;
17+
using Utility;
18+
19+
/// <summary>
20+
/// An implementation of <see cref="IAzureStorageTableRepositoryContext" />.
21+
/// </summary>
22+
/// <seealso cref="IAzureStorageTableRepositoryContext" />
23+
internal class AzureStorageTableRepositoryContext : LinqRepositoryContextBase, IAzureStorageTableRepositoryContext
24+
{
25+
#region Properties
26+
27+
/// <summary>
28+
/// Gest the cloud Table.
29+
/// </summary>
30+
public CloudTable Table { get; private set; }
31+
32+
#endregion
33+
34+
#region Constructors
35+
36+
/// <summary>
37+
/// Initializes a new instance of the <see cref="AzureStorageTableRepositoryContext" /> class.
38+
/// </summary>
39+
/// <param name="nameOrConnectionString">Either the database name or a connection string.</param>
40+
/// <param name="tableName">The name of the table.</param>
41+
/// <param name="createIfNotExists">Creates the container if it does not exist.</param>
42+
public AzureStorageTableRepositoryContext(string nameOrConnectionString, string tableName = null, bool createIfNotExists = false)
43+
{
44+
Guard.NotEmpty(nameOrConnectionString, nameof(nameOrConnectionString));
45+
46+
ConfigureConventions();
47+
48+
var css = GetConnectionStringSettings(nameOrConnectionString);
49+
50+
var connectionString = css != null
51+
? css.ConnectionString
52+
: nameOrConnectionString;
53+
54+
var account = CloudStorageAccount.Parse(connectionString);
55+
var client = account.CreateCloudTableClient();
56+
57+
if (string.IsNullOrEmpty(tableName))
58+
tableName = GetType().Name;
59+
60+
Table = client.GetTableReference(tableName);
61+
62+
if (createIfNotExists)
63+
Table.CreateIfNotExists();
64+
}
65+
66+
#endregion
67+
68+
#region Private Methods
69+
70+
private void ConfigureConventions()
71+
{
72+
Conventions = new RepositoryConventions(GetType())
73+
{
74+
PrimaryKeysCallback = type => AzureStorageTableConventionsHelper.GetPrimaryKeyPropertyInfos(type)
75+
};
76+
}
77+
78+
private IEnumerable<TElement> ExecuteTableQuery<TElement>(TableQuery<TElement> tableQuery)
79+
{
80+
var nextQuery = tableQuery;
81+
var continuationToken = default(TableContinuationToken);
82+
var results = new List<TElement>();
83+
84+
do
85+
{
86+
var queryResult = nextQuery.ExecuteSegmented(continuationToken);
87+
88+
results.Capacity += queryResult.Results.Count;
89+
results.AddRange(queryResult.Results);
90+
91+
continuationToken = queryResult.ContinuationToken;
92+
93+
if (continuationToken != null && tableQuery.TakeCount.HasValue)
94+
{
95+
var itemsToLoad = tableQuery.TakeCount.Value - results.Count;
96+
97+
nextQuery = itemsToLoad > 0
98+
? tableQuery.Take<TElement>(itemsToLoad).AsTableQuery()
99+
: null;
100+
}
101+
102+
} while (continuationToken != null && nextQuery != null);
103+
104+
return results;
105+
}
106+
107+
private TableQuery<TEntity> InvokeCreateQuery<TEntity>()
108+
{
109+
return (TableQuery<TEntity>)typeof(CloudTable)
110+
.GetRuntimeMethods()
111+
.Single(x => x.Name == nameof(CloudTable.CreateQuery) &&
112+
x.IsGenericMethodDefinition &&
113+
x.GetGenericArguments().Length == 1 &&
114+
x.GetParameters().Length == 0)
115+
.MakeGenericMethod(typeof(TEntity))
116+
.Invoke(Table, null);
117+
}
118+
119+
private TableOperation InvokeRetrieve<TEntity>(string partitionKey, string rowKey)
120+
{
121+
return (TableOperation)typeof(TableOperation)
122+
.GetRuntimeMethods()
123+
.Single(x => x.Name == nameof(TableOperation.Retrieve) &&
124+
x.IsGenericMethodDefinition &&
125+
x.GetGenericArguments().Length == 1 &&
126+
x.GetParameters().Length == 3)
127+
.MakeGenericMethod(typeof(TEntity))
128+
.Invoke(Table, new[] { partitionKey, rowKey, null });
129+
}
130+
131+
private static void ThrowsIfNotTableEntity<TEntity>()
132+
{
133+
if (!typeof(TEntity).ImplementsInterface(typeof(ITableEntity)))
134+
throw new InvalidOperationException($"The specified '{typeof(TEntity).FullName}' type must implement '{typeof(ITableEntity).FullName}.'");
135+
}
136+
137+
private static ConnectionStringSettings GetConnectionStringSettings(string nameOrConnectionString)
138+
{
139+
var css = ConfigurationManager.ConnectionStrings[nameOrConnectionString];
140+
141+
if (css != null)
142+
return css;
143+
144+
for (var i = 0; i < ConfigurationManager.ConnectionStrings.Count; i++)
145+
{
146+
css = ConfigurationManager.ConnectionStrings[i];
147+
148+
if (css.ConnectionString.Equals(nameOrConnectionString))
149+
return css;
150+
}
151+
152+
return null;
153+
}
154+
155+
#endregion
156+
157+
#region Overrides of LinqRepositoryContextBase
158+
159+
/// <summary>
160+
/// Returns the entity's query.
161+
/// </summary>
162+
/// <typeparam name="TEntity">The type of the of the entity.</typeparam>
163+
/// <returns>The entity's query.</returns>
164+
protected override IQueryable<TEntity> AsQueryable<TEntity>()
165+
{
166+
ThrowsIfNotTableEntity<TEntity>();
167+
168+
return ExecuteTableQuery<TEntity>(
169+
InvokeCreateQuery<TEntity>())
170+
.AsQueryable();
171+
}
172+
173+
/// <summary>
174+
/// Apply a fetching options to the specified entity's query.
175+
/// </summary>
176+
/// <returns>The entity's query with the applied options.</returns>
177+
protected override IQueryable<TEntity> ApplyFetchingOptions<TEntity>(IQueryable<TEntity> query, IQueryOptions<TEntity> options)
178+
{
179+
ThrowsIfNotTableEntity<TEntity>();
180+
181+
if (options?.FetchStrategy?.PropertyPaths?.Any() == true)
182+
Logger.Debug("The azure storage table context does not support fetching strategy.");
183+
184+
return query;
185+
}
186+
187+
/// <summary>
188+
/// Tracks the specified entity in memory and will be inserted into the database when <see cref="SaveChanges" /> is called.
189+
/// </summary>
190+
/// <typeparam name="TEntity">The type of the entity.</typeparam>
191+
/// <param name="entity">The entity.</param>
192+
public override void Add<TEntity>(TEntity entity)
193+
{
194+
Guard.NotNull(entity, nameof(entity));
195+
196+
ThrowsIfNotTableEntity<TEntity>();
197+
198+
var operation = TableOperation.InsertOrReplace((ITableEntity)entity);
199+
200+
Table.Execute(operation);
201+
}
202+
203+
/// <summary>
204+
/// Tracks the specified entity in memory and will be updated in the database when <see cref="SaveChanges" /> is called.
205+
/// </summary>
206+
/// <typeparam name="TEntity">The type of the entity.</typeparam>
207+
/// <param name="entity">The entity.</param>
208+
public override void Update<TEntity>(TEntity entity)
209+
{
210+
Guard.NotNull(entity, nameof(entity));
211+
212+
ThrowsIfNotTableEntity<TEntity>();
213+
214+
var operation = TableOperation.Replace((ITableEntity)entity);
215+
216+
Table.Execute(operation);
217+
}
218+
219+
/// <summary>
220+
/// Tracks the specified entity in memory and will be removed from the database when <see cref="SaveChanges" /> is called.
221+
/// </summary>
222+
/// <typeparam name="TEntity">The type of the entity.</typeparam>
223+
/// <param name="entity">The entity.</param>
224+
public override void Remove<TEntity>(TEntity entity)
225+
{
226+
Guard.NotNull(entity, nameof(entity));
227+
228+
ThrowsIfNotTableEntity<TEntity>();
229+
230+
var operation = TableOperation.Delete((ITableEntity)entity);
231+
232+
Table.Execute(operation);
233+
}
234+
235+
/// <summary>
236+
/// Saves all changes made in this context to the database.
237+
/// </summary>
238+
/// <returns>The number of state entries written to the database.</returns>
239+
public override int SaveChanges()
240+
{
241+
return -1;
242+
}
243+
244+
/// <summary>
245+
/// Finds an entity with the given primary key values in the repository.
246+
/// </summary>
247+
/// <typeparam name="TEntity">The type of the of the entity.</typeparam>
248+
/// <param name="fetchStrategy">Defines the child objects that should be retrieved when loading the entity.</param>
249+
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
250+
/// <returns>The entity found in the repository.</returns>
251+
public override TEntity Find<TEntity>(IFetchQueryStrategy<TEntity> fetchStrategy, params object[] keyValues)
252+
{
253+
Guard.NotEmpty(keyValues, nameof(keyValues));
254+
255+
ThrowsIfNotTableEntity<TEntity>();
256+
257+
string partitionKey = string.Empty;
258+
string rowKey;
259+
260+
if (keyValues.Length == 1)
261+
{
262+
rowKey = keyValues[0].ToString();
263+
}
264+
else
265+
{
266+
partitionKey = keyValues[0].ToString();
267+
rowKey = keyValues[1].ToString();
268+
}
269+
270+
var opertation = InvokeRetrieve<TEntity>(partitionKey, rowKey);
271+
var tableResult = Table.Execute(opertation);
272+
273+
return tableResult.Result as TEntity;
274+
}
275+
276+
#endregion
277+
}
278+
}

0 commit comments

Comments
 (0)