Skip to content
This repository has been archived by the owner on Mar 5, 2022. It is now read-only.

Commit

Permalink
Add sample project
Browse files Browse the repository at this point in the history
Code around `CloudTable` is a bit dirty, I might clean it later if I
feel like it.
  • Loading branch information
gabrielweyer committed Feb 7, 2018
1 parent 75f4eec commit b45dbc7
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 6 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,55 @@
| --- | --- | --- | --- | --- |
| [AppVeyor][app-veyor] | [![Build Status][app-veyor-shield]][app-veyor] | `Windows` | `nestandard2.0` | `netcoreapp2.0.5` |

[Azure Table storage][table-storage] supports a [limited set of data types][supported-types] (namely `byte[]`, `bool`, `DateTime`, `double`, `Guid`, `int`, `long` and `string`). This `NuGet` package allows to store unsupported data type with some limitations:
[Azure Table storage][table-storage] supports a [limited set of data types][supported-types] (namely `byte[]`, `bool`, `DateTime`, `double`, `Guid`, `int`, `long` and `string`). `Unsupported Types` allows to store unsupported data types with some limitations:

- Your `Type` should be serializable / deserializable to and from `JSON` using [Json.NET][json-net]
- The entity should [fit][property-limitations] in `1 MB`

This is distributed via a `NuGet` package but the implementation is so simple that you can just copy the classes into your own solution if that works better for you.

## How it works

1. Your `TableEntity` should inherit from `UnsupportedTypesTableEntity`
1. Decorate the property you want to store with the `UnsupportedTypeAttribute`
1. Decorate the properties you want to store with the `UnsupportedTypeAttribute`
1. That's all

```csharp
public class MyCustomClass
public class Unimportant
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class MyTableEntity : UnsupportedTypesTableEntity
public class UnsupportedTypesTestTableEntity : UnsupportedTypesTableEntity
{
[UnsupportedType]
public MyCustomClass SomeProperty { get; set; }
public Unimportant VeryImportant { get; set; }
}
```

There is a console application in [src/SampleConsole](src/SampleConsole) demonstrating `Unsupported Types`:

- You'll need Latest version of the [Azure storage emulator][azure-storage-emulator]
- Alternatively you can use a [storage account][create-storage-account] and modify `AzureTableStorage:ConnectionString` accordingly in `appsettings.json`

### Output of the console

![Console][console-screenshot]

### Entity stored in storage

![Storage][storage-screenshot]

## Limitations

You will not be able to [filter][filter] the entities using the unsupported types. You'll need to materialize them first and then use [LINQ to Objects][linq-objects].

## Potential improvements

### Cache properties

Each read and write to `Azure Table storage` will trigger the use of `Reflection`. This could improved by caching the unsupported properties, in this case the scan would happen once per application lifetime.
Each read and write to `Azure Table storage` will trigger the use of `Reflection`. This could be improved by caching the unsupported properties, in this case the scan would happen once per application lifetime.

[table-storage]: https://docs.microsoft.com/en-au/azure/cosmos-db/table-storage-overview
[supported-types]: https://docs.microsoft.com/en-us/rest/api/storageservices/understanding-the-table-service-data-model#property-types
Expand All @@ -49,3 +68,9 @@ Each read and write to `Azure Table storage` will trigger the use of `Reflection
[myget]: https://www.myget.org/feed/gabrielweyer-pre-release/package/nuget/TableStorage.UnsupportedTypes
[app-veyor]: https://ci.appveyor.com/project/GabrielWeyer/unsupported-types
[app-veyor-shield]: https://ci.appveyor.com/api/projects/status/github/gabrielweyer/unsupported-types?branch=master&svg=true
[linq-objects]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects
[filter]: https://docs.microsoft.com/en-us/rest/api/storageservices/querying-tables-and-entities#constructing-filter-strings
[azure-storage-emulator]: https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator
[create-storage-account]: https://docs.microsoft.com/en-us/azure/storage/common/storage-quickstart-create-account?tabs=portal
[console-screenshot]: docs/console.png
[storage-screenshot]: docs/storage.png
17 changes: 17 additions & 0 deletions UnsupportedTypes.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{748A1438
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TableStorage.UnsupportedTypes.Tests", "tests\TableStorage.UnsupportedTypes.Tests\TableStorage.UnsupportedTypes.Tests.csproj", "{51786274-37BE-4378-9F55-D1089BDD1F0C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{2A3A2A40-BC6F-43D2-81D7-8F3C2053146B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsole", "samples\SampleConsole\SampleConsole.csproj", "{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -48,9 +52,22 @@ Global
{51786274-37BE-4378-9F55-D1089BDD1F0C}.Release|x64.Build.0 = Release|x64
{51786274-37BE-4378-9F55-D1089BDD1F0C}.Release|x86.ActiveCfg = Release|x86
{51786274-37BE-4378-9F55-D1089BDD1F0C}.Release|x86.Build.0 = Release|x86
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Debug|x64.ActiveCfg = Debug|x64
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Debug|x64.Build.0 = Debug|x64
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Debug|x86.ActiveCfg = Debug|x86
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Debug|x86.Build.0 = Debug|x86
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Release|Any CPU.Build.0 = Release|Any CPU
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Release|x64.ActiveCfg = Release|x64
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Release|x64.Build.0 = Release|x64
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Release|x86.ActiveCfg = Release|x86
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{DCD973F5-57AE-4830-9C36-7C324171D1B5} = {1520A510-7A96-42E5-A2D3-57F003C3931C}
{51786274-37BE-4378-9F55-D1089BDD1F0C} = {748A1438-9625-4BB1-97AC-E665B6AB1F79}
{D1EDBC37-968A-47DD-8B6A-32EECE3FC63A} = {2A3A2A40-BC6F-43D2-81D7-8F3C2053146B}
EndGlobalSection
EndGlobal
Binary file added docs/console.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/storage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions samples/SampleConsole/Configuration/ConfigurationRootExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using Microsoft.Extensions.Configuration;
using Serilog.Events;

namespace TableStorage.UnsupportedTypes.SampleConsole.Configuration
{
public static class ConfigurationRootExtensions
{
public static LogEventLevel GetLoggingLevel(this IConfigurationRoot configuration, string keyName,
LogEventLevel defaultLevel = LogEventLevel.Warning)
{
try
{
return configuration.GetValue($"Logging:LogLevel:{keyName}", LogEventLevel.Warning);
}
catch (Exception)
{
return defaultLevel;
}
}
}
}
26 changes: 26 additions & 0 deletions samples/SampleConsole/Configuration/LoggerConfigurator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Serilog;

namespace TableStorage.UnsupportedTypes.SampleConsole.Configuration
{
public static class LoggerConfigurator
{
public static ILoggerFactory ConfigureSerilog(this IConfigurationRoot configuration)
{
var serilogLevel = configuration.GetLoggingLevel("Serilog");

var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Is(serilogLevel)
.Enrich.WithDemystifiedStackTraces()
.WriteTo.Console(serilogLevel);

var logger = loggerConfiguration.CreateLogger();

ILoggerFactory loggerFactory = new LoggerFactory();
loggerFactory.AddSerilog(logger);

return loggerFactory;
}
}
}
36 changes: 36 additions & 0 deletions samples/SampleConsole/Configuration/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using TableStorage.UnsupportedTypes.SampleConsole.Storage;

namespace TableStorage.UnsupportedTypes.SampleConsole.Configuration
{
public static class ServiceCollectionExtensions
{
public static void AddLogic(this IServiceCollection services)
{
services.AddSingleton<Presenter>();
}

public static async Task AddStorageAsync(this IServiceCollection services, IConfigurationRoot configuration)
{
var tableStorageConnectionString = configuration.GetValue<string>("AzureTableStorage:ConnectionString");

var storageAccount = CloudStorageAccount.Parse(tableStorageConnectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference(nameof(UnsupportedTypesTestTableEntity));

await table.CreateIfNotExistsAsync();

services.AddSingleton(table);
}

public static void AddLogging(this IServiceCollection services, ILoggerFactory loggerFactory)
{
services.AddSingleton(loggerFactory);
services.AddLogging();
}
}
}
29 changes: 29 additions & 0 deletions samples/SampleConsole/Configuration/ServiceProviderConfigurator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace TableStorage.UnsupportedTypes.SampleConsole.Configuration
{
public class ServiceProviderConfigurator
{
public async Task<IServiceProvider> ConfigureTheWorldAsync()
{
IServiceCollection services = new ServiceCollection();

var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();

var configuration = configurationBuilder.Build();

var loggerFactory = configuration.ConfigureSerilog();

services.AddLogging(loggerFactory);
services.AddLogic();
await services.AddStorageAsync(configuration);

return services.BuildServiceProvider();
}
}
}
51 changes: 51 additions & 0 deletions samples/SampleConsole/Presenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Table;
using TableStorage.UnsupportedTypes.SampleConsole.Storage;

namespace TableStorage.UnsupportedTypes.SampleConsole
{
public class Presenter
{
private readonly CloudTable _table;
private readonly ILogger<Presenter> _logger;

public Presenter(CloudTable table, ILogger<Presenter> logger)
{
_table = table;
_logger = logger;
}

public async Task RunTheShowAsync()
{
var entity = new UnsupportedTypesTestTableEntity
{
PartitionKey = "q",
RowKey = "w",
VeryImportant = new Unimportant
{
FirstName = "Some First Name",
LastName = "Some Last Name"
}
};

_logger.LogInformation("Inserting {@InsertEntity}", entity);

var insertOperation = TableOperation.InsertOrReplace(entity);
var insertResult = await _table.ExecuteAsync(insertOperation);

_logger.LogInformation("Insert result was {InsertStatusCode}", insertResult.HttpStatusCode);

var retrieveOperation = TableOperation
.Retrieve<UnsupportedTypesTestTableEntity>(entity.PartitionKey, entity.RowKey);

var retrieveResult = await _table.ExecuteAsync(retrieveOperation);

_logger.LogInformation("Retrieve result was {RetrieveStatusCode}", retrieveResult.HttpStatusCode);

var retrievedEntity = retrieveResult.Result as UnsupportedTypesTestTableEntity;

_logger.LogInformation("Retrieved {@RetrieveEntity}", retrievedEntity);
}
}
}
61 changes: 61 additions & 0 deletions samples/SampleConsole/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using TableStorage.UnsupportedTypes.SampleConsole.Configuration;

namespace TableStorage.UnsupportedTypes.SampleConsole
{
class Program
{
static async Task Main(string[] args)
{
try
{
var providerConfigurator = new ServiceProviderConfigurator();
var serviceProvider = await providerConfigurator.ConfigureTheWorldAsync();

using (var applicationScope = serviceProvider.CreateScope())
{
var presenter = applicationScope
.ServiceProvider
.GetRequiredService<Presenter>();

await presenter.RunTheShowAsync();
}
}
catch (Exception e)
{
DisplayException(e);
}
finally
{
Console.WriteLine("Press Enter to continue...");
Console.ReadLine();
}
}

private static void DisplayException(Exception e)
{
if (e == null) return;

if (_innerExceptionCount > 0)
{
Console.WriteLine("\tInner exception {0}:", _innerExceptionCount);
Console.WriteLine();
}

Console.WriteLine("Exception: {0}", e.GetType());
Console.WriteLine("Message: {0}", e.Message);
Console.WriteLine("StackTrace:");
Console.WriteLine(e.Demystify().StackTrace);
Console.WriteLine();

_innerExceptionCount++;

DisplayException(e.InnerException);
}

private static int _innerExceptionCount = 0;
}
}
31 changes: 31 additions & 0 deletions samples/SampleConsole/SampleConsole.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>TableStorage.UnsupportedTypes.SampleConsole</AssemblyName>
<RootNamespace>TableStorage.UnsupportedTypes.SampleConsole</RootNamespace>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\TableStorage.UnsupportedTypes\TableStorage.UnsupportedTypes.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
<PackageReference Include="Serilog" Version="2.6.0" />
<PackageReference Include="Serilog.Enrichers.Demystify" Version="0.1.0-dev-00011" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="WindowsAzure.Storage" Version="8.7.0" />
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions samples/SampleConsole/Storage/UnsupportedTypesTestTableEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace TableStorage.UnsupportedTypes.SampleConsole.Storage
{
public class UnsupportedTypesTestTableEntity : UnsupportedTypesTableEntity
{
[UnsupportedType]
public Unimportant VeryImportant { get; set; }
}

public class Unimportant
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
14 changes: 14 additions & 0 deletions samples/SampleConsole/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"AzureTableStorage": {
"ConnectionString": "UseDevelopmentStorage=true;"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Information",
"System": "Information",
"Microsoft": "Information",
"Serilog": "Debug"
}
}
}

0 comments on commit b45dbc7

Please sign in to comment.