Skip to content

Instantly share code, notes, and snippets.

@Slackwise
Created February 5, 2019 00:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Slackwise/965ac1947b69c60e21aa030be96b657b to your computer and use it in GitHub Desktop.
Save Slackwise/965ac1947b69c60e21aa030be96b657b to your computer and use it in GitHub Desktop.
Functional extensions for working with raw DapperRows as IDictionary<string, object>.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Web;
namespace SkylineAPI
{
public static class DapperRowExtensions
{
#region General Functions
public static ImmutableDictionary<string, object> ToImmutableDapperRow(dynamic dapperRow) =>
((IDictionary<string, object>)dapperRow).ToImmutableDictionary();
public static ImmutableDictionary<string, object> ToImmutableDapperRow(this IDictionary<string, object> dapperRow) =>
dapperRow.ToImmutableDictionary();
/// <summary>
/// Converts an <c>IEnumerable DapperRow</c> <c>KeyValuePair</c>s to <c>IDictionary&lt;string, object&gt;</c>.
/// </summary>
/// <remarks>LINQ functions return an <c>IEnumerable&lt;KeyValuePair&gt;</c>, breaking the type expectations of functions looking for a <c>DapperRow</c> of <c>IDictionary&lt;string, object&gt;</c>. This function is just a shorthand for casting to a <c>Dictionary</c>, which is a frequent operation.</remarks>
/// <remarks></remarks>
/// <param name="dapperKVPs"><c>IEnumerable</c> of <c>DapperRow</c> <c>KeyValuePair</c>s.</param>
/// <returns><c>DapperRow</c> style <c>IDictionary&lt;string, object&gt;</c>.</returns>
public static ImmutableDictionary<string, object> ToDapperRow(this IEnumerable<KeyValuePair<string, object>> dapperKVPs) => dapperKVPs.ToImmutableDictionary(r => r.Key, r => r.Value);
/// <summary>
/// Recursively apply's <paramref name="f"/> to every value of <typeparamref name="InputType"/> in a <c>DapperRow</c> or nested <c>DapperRow</c>s or <c>IEnumerable</c>'s of <c>DapperRow</c>s.
/// </summary>
/// <remarks>During our transformation of <c>DapeprRow</c> objects into their intended JSON serializable form, we need to be able to apply functions recursively to the values, such as trimming all strings from the Skyline databases.</remarks>
/// <typeparam name="InputType">The <c>KeyValuePair.Value</c> type to apply <paramref name="f"/> to.</typeparam>
/// <typeparam name="OutputType">The new <c>KeyValuePair.Value</c>c> type after applying <paramref name="f"/>.</typeparam>
/// <param name="dapperRow">The <c>dynamic DapperRow</c> to be treated as an <c>IDictionary&lt;string, object&gt;</c>.</param>
/// <param name="f">The function to transform the <c>KeyValuePair.Value</c>.</param>
/// <returns><c>dapperRow</c>, but with all the <c>InputType</c> values as <c>OutputType</c> values.</returns>
public static ImmutableDictionary<string, object> RecursivelyApplyFunctionToValues<InputType, OutputType>(this ImmutableDictionary<string, object> dapperRow, Func<InputType, OutputType> f) =>
dapperRow.Select(fieldKV => new KeyValuePair<string, object>(fieldKV.Key,
fieldKV.Value is IEnumerable<object> ? ((IEnumerable<ImmutableDictionary<string, object>>)fieldKV.Value).Select((ImmutableDictionary<string, object> kv) => RecursivelyApplyFunctionToValues(kv, f))
: fieldKV.Value is IImmutableDictionary<string, object> ? RecursivelyApplyFunctionToValues((ImmutableDictionary<string, object>)fieldKV.Value, f)
: fieldKV.Value is InputType ? f((InputType)fieldKV.Value)
: /* is other value */ fieldKV.Value
)).ToImmutableDictionary();
/// <summary>
/// Recursively apply a <paramref name="reducer"/> function to every value of <typeparamref name="ValueType"/>, to accumulate a new <typeparamref name="AccumulatorType"/>.
/// </summary>
/// <remarks>During our transformation of <c>DapeprRow</c> objects into their intended JSON serializable form, we will need to extract data such as all the keys of the <c>Dictionary</c> form.</remarks>
/// <typeparam name="AccumulatorType">The output type of the <paramref name="reduce"/> function as well as this function.</typeparam>
/// <typeparam name="ValueType">The type of the <c>KeyValuePair.Value</c> to replace.</typeparam>
/// <param name="dapperRow">The <c>DapperRow</c> to reduce to <c>AccumulatorType</c>.</param>
/// <param name="accumulator">The object and initial value to reduce to.</param>
/// <param name="reducer">The <paramref name="reducer"/> function to apply to all <c>KeyValuePair.Value</c>s of <typeparamref name="ValueType"/>.</param>
/// <returns>The reduced <paramref name="accumulator"/> object of <typeparamref name="AccumulatorType"/>.</returns>
public static AccumulatorType RecursivelyReduceValues<AccumulatorType, ValueType>(this ImmutableDictionary<string, object> dapperRow, AccumulatorType accumulator, Func<AccumulatorType, ValueType, AccumulatorType> reducer) =>
dapperRow.Aggregate(accumulator, (collectedValues, fieldKV) =>
fieldKV.Value is IEnumerable<object> ? ((IEnumerable<ImmutableDictionary<string, object>>)fieldKV.Value).Aggregate(collectedValues, (collectedRowValues, row) => RecursivelyReduceValues(row, collectedValues, reducer))
: fieldKV.Value is IImmutableDictionary<string, object> ? RecursivelyReduceValues((ImmutableDictionary<string, object>)fieldKV.Value, collectedValues, reducer)
: fieldKV.Value is ValueType ? reducer(collectedValues, (ValueType)fieldKV.Value)
: /* is other value */ collectedValues);
#endregion
#region Common Transformation Functions
/// <summary>
/// Recursively trims all string <c>Value</c>s of the <c>KeyValuePair</c> representations of a <c>DapperRow</c>.
/// </summary>
/// <param name="dapperRow">The row to be trimmed recursively.</param>
/// <returns>A new <c>DapperRow</c> with all the strings trimmed.</returns>
public static ImmutableDictionary<string, object> TrimStringFields(ImmutableDictionary<string, object> dapperRow) =>
RecursivelyApplyFunctionToValues(dapperRow, (string s) => s.Trim());
/// <summary>
/// Recursively trim strings, remove NUL characters, and <c>null</c> strings that are empty.
/// </summary>
/// <param name="dapperRow">The row to transform.</param>
/// <returns>A new <c>DapeprRow</c> with all the blank strings <c>null</c>'d.</returns>
public static ImmutableDictionary<string, object> NullBlankStrings(ImmutableDictionary<string, object> dapperRow) =>
RecursivelyApplyFunctionToValues(dapperRow, (string s) => Utils.NUllBlankString(s));
public static KeyValuePair<string, object> NullBlankStrings(KeyValuePair<string, object> dapperKVP)
{
var k = dapperKVP.Key;
var v = dapperKVP.Value;
if (v is string)
return new KeyValuePair<string, object>(k, Utils.NUllBlankString(v));
else
return dapperKVP;
}
#endregion
#region Branching Functions
/// <summary>
/// Filter out top-level <paramref name="keys"/> of a <c>DapperRow</c>.
/// </summary>
/// <param name="dapperRow">The row to extract keys from.</param>
/// <param name="keys">A list of keys to filter out.</param>
/// <returns>The DapperRow with <paramref name="keys"/> filtered out.</returns>
private static ImmutableDictionary<string, object> FilterOutKeys(ImmutableDictionary<string, object> dapperRow, ImmutableList<string> keys) =>
dapperRow.Where(kv => !keys.Contains(kv.Key)).ToImmutableDictionary();
private static ImmutableDictionary<string, object> BranchOutNewFields(ImmutableDictionary<string, object> dapperRow, ImmutableDictionary<string, object> branchFieldMap, ImmutableDictionary<string, object> newFields = null) =>
branchFieldMap.Aggregate(
newFields?.ToImmutableDictionary() ?? ImmutableDictionary<string, object>.Empty,
(currentNewFields, currentFieldKV) =>
currentNewFields.Add(
currentFieldKV.Key,
currentFieldKV.Value is IDictionary<string, object>
? BranchOutNewFields(dapperRow, ((IDictionary<string, object>)currentFieldKV.Value).ToImmutableDictionary(), currentNewFields)
: dapperRow[(string)currentFieldKV.Value]));
private static ImmutableList<object> CollectOldFields(ImmutableDictionary<string, object> branchFieldMap, ImmutableList<object> oldFieldValues = null) =>
branchFieldMap.Values.Aggregate(
oldFieldValues ?? ImmutableList<object>.Empty,
(collectedOldValues, currentFieldValue) =>
currentFieldValue is IDictionary<string, object>
? CollectOldFields(((IDictionary<string, object>)currentFieldValue).ToImmutableDictionary(), collectedOldValues)
: collectedOldValues.Add(currentFieldValue));
public static ImmutableDictionary<string, object> BranchOut(ImmutableDictionary<string, object> dapperRow, IDictionary<string, object> branchFieldMap)
{
var immutableBranchFieldMap = branchFieldMap.ToImmutableDictionary();
var newFields = BranchOutNewFields(dapperRow, immutableBranchFieldMap);
var oldFields = CollectOldFields(immutableBranchFieldMap);
var okayFields = FilterOutKeys(dapperRow, oldFields.Cast<string>().ToImmutableList());
return okayFields.Concat(newFields).ToImmutableDictionary();
}
public static ImmutableDictionary<string, object> BranchOutGroupAsList(IGrouping<object, ImmutableDictionary<string, object>> dapperRowGroup, string branchName, Func<ImmutableDictionary<string, object>, bool> existanceCheckFunction, IDictionary<string, object> branchFieldMap)
{
var immutableBranchFieldMap = branchFieldMap.ToImmutableDictionary();
var rows = dapperRowGroup.Cast<ImmutableDictionary<string, object>>().ToImmutableList();
var keys = branchFieldMap.ToImmutableDictionary().RecursivelyReduceValues(ImmutableList<string>.Empty, (ImmutableList<string> strings, string s) => strings.Add(s));
var primary = FilterOutKeys(rows.First(), keys);
var others = rows.Where(existanceCheckFunction).Select(row => BranchOutNewFields(row, immutableBranchFieldMap));
return primary.Add(branchName, others);
}
public static ImmutableDictionary<string, object> BranchOutGroupAsList(IGrouping<object, ImmutableDictionary<string, object>> dapperRows, string branchName, string keyField, IDictionary<string, object> branchFieldMap) =>
BranchOutGroupAsList(dapperRows, branchName, row => row[keyField] != null, branchFieldMap);
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment