-
-
Save Slackwise/965ac1947b69c60e21aa030be96b657b to your computer and use it in GitHub Desktop.
Functional extensions for working with raw DapperRows as IDictionary<string, object>.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<string, object></c>. | |
/// </summary> | |
/// <remarks>LINQ functions return an <c>IEnumerable<KeyValuePair></c>, breaking the type expectations of functions looking for a <c>DapperRow</c> of <c>IDictionary<string, object></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<string, object></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<string, object></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