Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Load related item(s)
  • Loading branch information
petero-dk committed Oct 16, 2022
commit 3549b5efc8ba24afeddcd6995e5aab33accf1762
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
namespace CoreHelpers.WindowsAzure.Storage.Table.Attributes
{
public class RelatedTableAttribute : Attribute
{
/// <summary>
/// The partitionkey of the related table, if this is the name of a property on the model the property value will be used.
/// </summary>
public string PartitionKey { get; set; }

/// <summary>
/// The rowkey of the related table, if this is a property on the model, the property value will be loaded, if it is empty this will default to the name of the type.
/// </summary>
public string RowKey { get; set; }

/// <summary>
///
/// </summary>
/// <param name="partitionKey">The partitionkey of the related table, if this is the name of a property on the model the property value will be used.</param>
public RelatedTableAttribute(string partitionKey)
{
PartitionKey = partitionKey;
}

/// <summary>
///
/// </summary>
/// <param name="partitionKey">The partitionkey of the related table, if this is the name of a property on the model the property value will be used.</param>
/// <param name="rowKey">The rowkey of the related table, if this is a property on the model, the property value will be loaded, if it is empty this will default to the name of the type.</param>
public RelatedTableAttribute(string partitionKey, string rowKey)
{
PartitionKey = partitionKey;
RowKey = rowKey;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Linq;

namespace CoreHelpers.WindowsAzure.Storage.Table.Extensions
{
public enum ExportEdmType
Expand Down Expand Up @@ -34,7 +36,25 @@ public static ExportEdmType GetEdmPropertyType(this Type type)
else if (type == typeof(Int64))
return ExportEdmType.Int64;
else
throw new NotImplementedException($"Datatype {type.ToString()} not supporter");
throw new NotImplementedException($"Datatype {type.ToString()} not supporter");
}

public static bool IsDerivedFromGenericParent(this Type type, Type parentType)
{
if (!parentType.IsGenericType)
{
throw new ArgumentException("type must be generic", "parentType");
}
if (type == null || type == typeof(object))
{
return false;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == parentType)
{
return true;
}
return type.BaseType.IsDerivedFromGenericParent(parentType)
|| type.GetInterfaces().Any(t => t.IsDerivedFromGenericParent(parentType));
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions CoreHelpers.WindowsAzure.Storage.Table/Internal/DynamicLazy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace CoreHelpers.WindowsAzure.Storage.Table.Internal
{
internal class DynamicLazy<T> : Lazy<T>
{
public DynamicLazy(Func<object> factory) : base(() => (T)factory())
{

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using CoreHelpers.WindowsAzure.Storage.Table.Extensions;
using CoreHelpers.WindowsAzure.Storage.Table.Internal;
using HandlebarsDotNet;
using Newtonsoft.Json.Linq;

namespace CoreHelpers.WindowsAzure.Storage.Table.Serialization
{
Expand All @@ -17,11 +18,11 @@ internal static class TableEntityDynamic
{
if (context as StorageContext == null)
throw new Exception("Invalid interface implementation");
else
else
return TableEntityDynamic.ToEntity<T>(model, (context as StorageContext).GetEntityMapper<T>(), context);
}

public static TableEntity ToEntity<T>(T model, StorageEntityMapper entityMapper, IStorageContext context) where T: new()
public static TableEntity ToEntity<T>(T model, StorageEntityMapper entityMapper, IStorageContext context) where T : new()
{
var builder = new TableEntityBuilder();

Expand All @@ -41,12 +42,17 @@ internal static class TableEntityDynamic
// check if we have a special convert attached via attribute if so generate the required target
// properties with the correct converter
var virtualTypeAttribute = property.GetCustomAttributes().Where(a => a is IVirtualTypeAttribute).Select(a => a as IVirtualTypeAttribute).FirstOrDefault<IVirtualTypeAttribute>();

var relatedTableAttribute = property.GetCustomAttributes().Where(a => a is RelatedTableAttribute).Select(a => a as RelatedTableAttribute).FirstOrDefault<RelatedTableAttribute>();

if (virtualTypeAttribute != null)
virtualTypeAttribute.WriteProperty<T>(property, model, builder);
virtualTypeAttribute.WriteProperty<T>(property, model, builder);
else if (relatedTableAttribute != null)
continue;
else
builder.AddProperty(property.Name, property.GetValue(model, null));
builder.AddProperty(property.Name, property.GetValue(model, null));
}

// build the result
return builder.Build();
}
Expand All @@ -58,18 +64,23 @@ internal static class TableEntityDynamic

// get all properties from model
IEnumerable<PropertyInfo> objectProperties = model.GetType().GetTypeInfo().GetProperties();

// visit all properties
foreach (PropertyInfo property in objectProperties)
{
if (ShouldSkipProperty(property))
continue;

// check if we have a special convert attached via attribute if so generate the required target
// properties with the correct converter
var virtualTypeAttribute = property.GetCustomAttributes().Where(a => a is IVirtualTypeAttribute).Select(a => a as IVirtualTypeAttribute).FirstOrDefault<IVirtualTypeAttribute>();

var relatedTableAttribute = property.GetCustomAttributes().Where(a => a is RelatedTableAttribute).Select(a => a as RelatedTableAttribute).FirstOrDefault<RelatedTableAttribute>();

if (virtualTypeAttribute != null)
virtualTypeAttribute.ReadProperty<T>(entity, property, model);
else if (relatedTableAttribute != null)
property.SetValue(model, LoadRelatedTableProperty(context, model, objectProperties, property));
else
{
if (!entity.ContainsKey(property.Name))
Expand All @@ -80,7 +91,7 @@ internal static class TableEntityDynamic
if (!entity.TryGetValue(property.Name, out objectValue))
continue;

if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?) || property.PropertyType == typeof(DateTimeOffset) || property.PropertyType == typeof(DateTimeOffset?) )
if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?) || property.PropertyType == typeof(DateTimeOffset) || property.PropertyType == typeof(DateTimeOffset?))
property.SetDateTimeOffsetValue(model, objectValue);
else
property.SetValue(model, objectValue);
Expand All @@ -90,6 +101,68 @@ internal static class TableEntityDynamic
return model;
}

private static object LoadRelatedTableProperty<T>(IStorageContext context, T model, IEnumerable<PropertyInfo> objectProperties, PropertyInfo property) where T : class, new()
{
var relatedTable = property.GetCustomAttribute<RelatedTableAttribute>();
var isLazy = false;
var isEnumerable = false;


Type endType;
if (property.PropertyType.IsDerivedFromGenericParent(typeof(Lazy<>)))
{
endType = property.PropertyType.GetTypeInfo().GenericTypeArguments[0];
isLazy = true;
}
else
endType = property.PropertyType;

if (endType.IsDerivedFromGenericParent(typeof(IEnumerable<>)))
isEnumerable = true;

// determine the partition key
string extPartition = relatedTable.PartitionKey;
if (!string.IsNullOrWhiteSpace(extPartition))
{
// if the partition key is the name of a property on the model, get the value
var partitionProperty = objectProperties.Where((pi) => pi.Name == relatedTable.PartitionKey).FirstOrDefault();
if (partitionProperty != null)
extPartition = partitionProperty.GetValue(model).ToString();
}

string extRowKey = relatedTable.RowKey ?? endType.Name;
// if the row key is the name of a property on the model, get the value
var rowkeyProperty = objectProperties.Where((pi) => pi.Name == extRowKey).FirstOrDefault();
if (rowkeyProperty != null)
extRowKey = rowkeyProperty.GetValue(model).ToString();

var method = typeof(StorageContext).GetMethod(nameof(StorageContext.QueryAsync),
isEnumerable ?
new[] { typeof(string), typeof(int) } :
new[] { typeof(string), typeof(string), typeof(int) });
var generic = method.MakeGenericMethod(endType);

// if the property is a lazy type, create the lazy initialization
if (isLazy)
{
var lazyType = typeof(DynamicLazy<>);
var constructed = lazyType.MakeGenericType(endType);

object o = Activator.CreateInstance(constructed, new Func<object>(() =>
{
var waitable = (dynamic)generic.Invoke(context, new object[] { extPartition, extRowKey, 1 });
return waitable.Result;
}));
return o;

}
else
{
var waitable = (dynamic)generic.Invoke(context, new object[] { extPartition, extRowKey, 1 });
return waitable.Result;
}
}

private static S GetTableStorageDefaultProperty<S, T>(string format, T model) where S : class
{
if (typeof(S) == typeof(string) && format.Contains("{{") && format.Contains("}}"))
Expand Down