Skip to content

Instantly share code, notes, and snippets.

@VisualBean
Created December 16, 2018 23:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VisualBean/6d555143fac42fa8e4cb6377bf7eca91 to your computer and use it in GitHub Desktop.
Save VisualBean/6d555143fac42fa8e4cb6377bf7eca91 to your computer and use it in GitHub Desktop.
Azure Table Storage Abstraction that abstracts your model away from the db. The model does not know or care about persistence. (Microsoft.Windows.Azure.Storage < v9.4.0)
public interface IEntity<T>
{
T Id { get; }
}
public abstract class Entity<T> : IEntity<T>
{
private int? _requestedHashCode;
public T Id { get; protected set;}
public bool IsTransient()
{
return this.Id.Equals(default(T));
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity<T>))
return false;
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
Entity<T> item = (Entity<T>)obj;
if (item.IsTransient() || this.IsTransient())
return false;
else
return item.Id.Equals(this.Id);
}
public override int GetHashCode()
{
if (!IsTransient())
{
if (!_requestedHashCode.HasValue)
_requestedHashCode = this.Id.GetHashCode() ^ 31;
// XOR for random distribution. See:
// http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx
return _requestedHashCode.Value;
}
else
return base.GetHashCode();
}
public static bool operator ==(Entity<T> left, Entity<T> right)
{
if (Object.Equals(left, null))
return (Object.Equals(right, null)) ? true : false;
else
return left.Equals(right);
}
public static bool operator !=(Entity<T> left, Entity<T> right)
{
return !(left == right);
}
}
/// <summary>
/// The Table Storage interface.
/// </summary>
/// <seealso cref="Microsoft.WindowsAzure.Storage.Table.ITableEntity" />
public interface ITableStorable : ITableEntity
{
/// <summary>
/// Gets the name of the table.
/// </summary>
/// <value>
/// The name of the table.
/// </value>
string TableName { get; }
/// <summary>
/// Reads the entity.
/// </summary>
/// <param name="properties">The properties.</param>
/// <param name="operationContext">The operation context.</param>
void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext);
/// <summary>
/// Writes the entity.
/// </summary>
/// <param name="operationContext">The operation context.</param>
/// <returns>A Dictionary.</returns>
IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext);
}
/// <summary>
/// A Storable.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <seealso cref="Microsoft.WindowsAzure.Storage.Table.TableEntity" />
public abstract class Storable<T> : TableEntity
{
/// <summary>
/// Gets the name of the table.
/// </summary>
/// <value>
/// The name of the table.
/// </value>
public abstract string TableName { get; }
/// <summary>
/// Gets or sets the entity.
/// </summary>
/// <value>
/// The entity.
/// </value>
public T Entity { get; protected set; }
/// <summary>
/// Reads the entity.
/// </summary>
/// <param name="properties">The properties.</param>
/// <param name="operationContext">The operation context.</param>
public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{
var values = new Dictionary<string, dynamic>();
foreach (var kvp in properties)
{
values.Add(kvp.Key, kvp.Value.PropertyAsObject);
}
var propertiesAsJson = JsonConvert.SerializeObject(values);
this.Entity = JsonConvert.DeserializeObject<T>(propertiesAsJson);
}
/// <summary>
/// Writes the entity.
/// </summary>
/// <param name="operationContext">The operation context.</param>
/// <returns></returns>
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var properties = new Dictionary<string, EntityProperty>();
properties.Add("PartitionKey", new EntityProperty(this.PartitionKey));
properties.Add("RowKey", new EntityProperty(this.RowKey));
var entityAsString = JsonConvert.SerializeObject(this.Entity);
var entityAsDict = JsonConvert.DeserializeObject<IDictionary<string, dynamic>>(entityAsString);
foreach (var kvp in entityAsDict)
{
properties.Add(kvp.Key, new EntityProperty(kvp.Value) );
}
return properties;
}
}
public class StorableUser : Storable<User>, ITableStorable
{
public override string TableName => Tables.UserTable;
public StorableUser()
{
}
public StorableUser(User user)
{
this.Entity = user;
this.PartitionKey = user.Id;
this.RowKey = "";
}
}
public class StorageClient<TStorable> : IStorageClient<TStorable> where TStorable : ITableStorable
{
private readonly CloudTableClient client;
private readonly CloudTable table;
public StorageClient(CloudTableClient client)
{
this.client = client;
TStorable storable = Activator.CreateInstance<TStorable>();
var table = this.client.GetTableReference(storable.TableName);
}
public async Task<TStorable> GetAsync(string partitionKey, string rowKey)
{
var result = await this.table.ExecuteAsync(TableOperation.Retrieve<TStorable>(partitionKey, rowKey));
if (result.HttpStatusCode == 404)
{
return default(TStorable);
}
return result.Result != null ? ((TStorable)result.Result) : default(TStorable);
}
public async Task<TStorable> InsertAsync(TStorable entity)
{
var result = await this.table.ExecuteAsync(TableOperation.Insert(entity, true));
return result.Result != null ? ((TStorable)result.Result) : default(TStorable);
//TODO what if exists?
}
public async Task InsertOrUpdateAsync(TStorable entity)
{
await this.table.ExecuteAsync(TableOperation.InsertOrMerge(entity));
}
public async Task RemoveAsync(TStorable entity)
{
await this.table.ExecuteAsync(TableOperation.Delete(entity));
}
public async Task<bool> ExistsAsync(TStorable entity)
{
var result = await this.table.ExecuteAsync(TableOperation.Retrieve(entity.PartitionKey, entity.RowKey, new List<string> { "PartitionKey", "RowKey" }));
switch (result.HttpStatusCode)
{
case (int)HttpStatusCode.OK:
return true;
case (int)HttpStatusCode.NotFound:
return false;
}
throw new Exception("Something went wrong trying to get columns from table.");
}
private CloudTable tableRef(string tableName)
{
return this.client.GetTableReference(tableName);
}
}
public interface IStorageClient<T>
{
Task<T> GetAsync(string partitionKey, string rowKey);
Task<T> InsertAsync(T entity);
Task InsertOrUpdateAsync(T entity);
Task RemoveAsync(T entity);
Task<bool> ExistsAsync(T entity);
}
public class User : Entity<string>
{
public string Name { get; }
public string Country { get; }
public User(string email, string name, string country)
{
this.Id = email; // From Entity<string>
this.Name = name;
this.Country = country;
}
}
class UserService
{
private readonly IStorageClient<StorableUser> storageClient;
public UserService()
{
var client = this.storageAccount.CreateCloudTableClient();
this.storageClient = new StorageClient<StorableUser>(client);
}
public async Task<User> GetUser(string email)
{
var result = await this.storageClient.GetAsync(email, string.Empty);
if (result.Entity == null)
{
//User does not exist
return null;
}
User user = result.Entity;
return user;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment