Skip to content

Instantly share code, notes, and snippets.

@gh0stwizard
Last active December 6, 2018 10:34
Show Gist options
  • Save gh0stwizard/e2394cf09cd1eac01c1e5a3c22e0d014 to your computer and use it in GitHub Desktop.
Save gh0stwizard/e2394cf09cd1eac01c1e5a3c22e0d014 to your computer and use it in GitHub Desktop.
LINQExtensionToDynamic.cs: convert queryable<Entity> to queryable<object> at runtime
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Nequeo.Reflection; /* https://github.com/nequeo/misc/blob/master/csharp/DynamicTypeBuilder.cs */
/*
This is a proof of concept how create at runtime the code like this:
var query = datacontext.Users.Where(x => x.IsActive);
var results = query.Select(x => new { Id = x.Id, Name = x.Name });
DoSomething(results);
Using the code below the same as above could be written like that:
var query = datacontext.Users.Where(x => x.IsActive);
var dtb = new DynamicTypeBuilder("DynamicUsers");
var results = query.ToDynamic(dtb, new string[] { "Id", "Name" });
DoSomething(results);
Current implementation has limitations:
* Nested objects are not created dynamicaly e.g.
new { Id = x.Id, Ticket = new { Id = x.Ticket.Id } };
The conversion above at the moment is not supported.
* Nested properties will have names without dot ("."):
var results = query.ToDynamic(dtb, new string[] { "Id", "User.Name" });
foreach (var o in results)
if (o.UserName == "...") { ... }
Why?
* LINQ for .Net 4.5 does not support queries with Lists, Dictionaries.
Previous versions using such a way not working anymore.
* LINQ must optimize the result query to reduce workload on database server,
like it done at compile time.
*/
namespace gh0stwizard
{
public static class LinqExtensionToDynamic
{
public static IQueryable<object> ToDynamic<T>(this IOrderedQueryable<T> query, DynamicTypeBuilder typeBuilder, IEnumerable<string> fields)
{
return ((IQueryable<T>)query).ToDynamic(typeBuilder, fields);
}
/// <summary>
/// Creates at runtime queryable of objects for selected fields.
/// </summary>
/// <typeparam name="T">Queryable output type</typeparam>
/// <param name="query">IQueryable object</param>
/// <param name="typeBuilder">DynamicTypeBuilder object</param>
/// <param name="fields">List of fields, e.g. "Id", "User.Name" etc.</param>
/// <returns>IQueryable of object</returns>
public static IQueryable<object> ToDynamic<T>(this IQueryable<T> query, DynamicTypeBuilder typeBuilder, IEnumerable<string> fields)
{
var iType = typeof(T);
var itemParam = Expression.Parameter(iType, "_");
var members = fields.ToDictionary(k => k.Replace(".", ""), v => CreateExpression(itemParam, v));
var dynaprop = fields.Select(f => new DynamicProperty(f.Replace(".", ""), iType.GetPropertyType(f)));
var dynaname = string.Format("{0}{1}", iType.Name, dynaprop.GetHashCode());
var dynatest = typeBuilder.Create(dynaname, dynaprop);
var dynatype = dynatest.GetType();
var newExp = Expression.New(dynatype.GetConstructor(new Type[0]));
var bindings = members.Select(x => Expression.Bind(dynatype.GetProperty(x.Key), x.Value.Body));
var body = Expression.MemberInit(newExp, bindings);
var lambda = Expression.Lambda<Func<T, object>>(body, itemParam);
return query.Select(lambda);
}
private static LambdaExpression CreateExpression(ParameterExpression param, string propertyName)
{
Expression body = param;
foreach (var member in propertyName.Split('.'))
body = Expression.PropertyOrField(body, member);
return Expression.Lambda(body, param);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment