Skip to content

Instantly share code, notes, and snippets.

Created December 15, 2014 10:57
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 anonymous/c3170f255e3f9da857d3 to your computer and use it in GitHub Desktop.
Save anonymous/c3170f255e3f9da857d3 to your computer and use it in GitHub Desktop.
Dynamic GroupBy
public AveragePartySize[] overview(Context[] filters)
{
using (var ctx = new FSMDbContext(dbConnection))
{
NameValueCollection fields = new NameValueCollection();
fields.Add("Year", "Respondent.currentVisitYear");
fields.Add("Month", "Respondent.currentVisitMonth");
Tuple<Type, Expression<Func<ChartJoin, object>>> t = Chart.GetGroupBy(fields);
var grouping = withFilters(
Chart.JoinBrandVisited(Enumerable.Empty<ChartJoin>().AsQueryable(), ctx),
ctx,
filters
)
.Where(x => x.Respondent.status == 1)
.GroupBy(
t.Item2
);
var query =
grouping
.Select(
GetAveragePartySizeSelector(t.Item1)
);
return query.ToArray();
}
}
public static Tuple<Type, Expression<Func<ChartJoin, object>>> GetGroupBy(NameValueCollection fields)
{
var parameter = Expression.Parameter(typeof(ChartJoin));
Dictionary<string, Type> others = fields.AllKeys
.ToDictionary(
f => f,
f => GetNestedProperty(typeof(ChartJoin), fields.Get(f)).PropertyType
);
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(others);
IEnumerable<MemberBinding> bindings = dynamicType
.GetFields()
.Select(
p => Expression.Bind(
p,
getNestedPropertyOrField(parameter, fields[p.Name])
)
)
.OfType<MemberBinding>();
Expression<Func<ChartJoin, object>> selector =
Expression.Lambda<Func<ChartJoin, object>>(
Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),
parameter
);
var lambda = Expression.Lambda<Func<ChartJoin, object>>(selector, parameter);
return new Tuple<Type, Expression<Func<ChartJoin, object>>> (dynamicType, lambda);
}
public static PropertyInfo GetNestedProperty(Type baseType, string propertyName)
{
string[] parts = propertyName.Split('.');
return (parts.Length > 1)
? GetNestedProperty(baseType.GetProperty(parts[0]).PropertyType, parts.Skip(1).Aggregate((a, i) => a + "." + i))
: baseType.GetProperty(propertyName);
}
private static Expression getNestedPropertyOrField(ParameterExpression context, String path)
{
MemberExpression member = null;
try
{
foreach (string field in path.Split(new Char[] { '.' }))
member = member == null ? Expression.PropertyOrField(context, field) : Expression.PropertyOrField(member, field);
}
catch (ArgumentException e)
{
return Expression.Throw(Expression.Constant(e));
}
return member;
}
/**
* NOTE! This class is on loan from Ethan J. Brown. Thanks!!!
*/
public static class LinqRuntimeTypeBuilder
{
private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
private static ModuleBuilder moduleBuilder = null;
private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();
static LinqRuntimeTypeBuilder()
{
moduleBuilder = Thread
.GetDomain()
.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run)
.DefineDynamicModule(assemblyName.Name);
}
private static string GetTypeKey(Dictionary<string, Type> fields)
{
string key = string.Empty;
foreach (var field in fields)
key += field.Key + ";" + field.Value.Name + ";";
return key;
}
public static Type GetDynamicType(Dictionary<string, Type> fields)
{
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
{
Monitor.Enter(builtTypes);
string className = GetTypeKey(fields);
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder
.DefineType(
className,
TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable
);
foreach (var field in fields)
typeBuilder.DefineField(
field.Key,
field.Value,
FieldAttributes.Public
);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
}
catch (Exception e)
{
throw e;
}
finally
{
Monitor.Exit(builtTypes);
}
}
}
public static Expression<Func<IGrouping<object, ChartJoin>, AveragePartySize>> GetAveragePartySizeSelector(Type dynamicType)
{
// x =>
var ParameterType = typeof(IGrouping<object, ChartJoin>);
var parameter = Expression.Parameter(ParameterType);
// x => x.Sum(m => (m.BrandVisited.NUM_PAID * m.Respondent.MWEIGHT)) / x.Sum(m => m.Respondent.MWEIGHT)
var m = Expression.Parameter(typeof(ChartJoin), "m");
.... bunch of stuff here to make the FractionExpression
// Results
MemberInfo PartySize = typeof(AveragePartySize).GetMember("PartySize")[0];
MemberBinding Measurement = Expression.Bind(
PartySize,
Expression.Convert(FractionExpression, typeof(double))
);
MemberInitExpression selector = Results(parameter, typeof(AveragePartySize), dynamicType, Measurement);
// Lambda
var lambda = Expression.Lambda<Func<IGrouping<object, ChartJoin>, AveragePartySize>>(selector, parameter);
return lambda;
}
protected static MemberInitExpression Results(ParameterExpression x, Type resultType, Type dynamicType, MemberBinding measurement)
{
// x => x.Count()
var CountExpression = Count(x);
// x => x.Key
var Key = Expression.Property(x, "Key");
NewExpression Result = Expression.New(resultType);
MemberInfo Year = resultType.GetMember("Year")[0];
MemberBinding YearBinding = Expression.Bind(
Year,
Expression.Convert(
Expression.PropertyOrField(Expression.Convert(Key, dynamicType), "Year"),
typeof(int)
)
);
MemberInfo Month = resultType.GetMember("Month")[0];
MemberBinding MonthBinding = Expression.Bind(
Month,
Expression.Convert(
Expression.PropertyOrField(Expression.Convert(Key, dynamicType), "Month"),
typeof(int)
)
);
MemberInitExpression selector = Expression.MemberInit(
Result,
YearBinding,
MonthBinding,
measurement
);
return selector;
}
protected static MethodCallExpression Count(ParameterExpression x)
{
// x => x.Count()
MethodInfo CountMethod = (typeof(Enumerable))
.GetMethods()
.First(
method => method.Name == "Count"
&& method.IsGenericMethod
)
.MakeGenericMethod(typeof(ChartJoin));
MethodCallExpression CountExpression = Expression.Call(null, CountMethod, x);
return CountExpression;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment