How to write an expression tree for selecting inside of SelectMany? - c#

Consider the following Person class
// Person.cs
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public string FamilyName { get; set; }
public float Age { get; set; }
public DateTimeOffset BithDate { get; set; }
public IEnumerable<Address> Addresses { get; set; }
public Person()
{
Addresses = new List<Address>();
}
}
// Address.cs
public class Address
{
public string Country { get; set; }
public string City { get; set; }
public string MainStreet { get; set; }
public string Info { get; set; }
public string No { get; set; }
}
Now, I have the snippet code below
var list = PeopleDataGenerator.GetPeople()
.SelectMany(x => x.Addresses)
.Select(x => x.City)
;
How can I do this part .SelectMany(x => x.Addresses) .Select(x => x.City) with Expression Tree?

Unclear if you want it for IEnumerable or IQueryable... For IEnumerable:
Given:
/// <summary>
/// IEnumerable<TResult> Enumerable.SelectMany<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
/// </summary>
public static readonly MethodInfo SelectMany1 = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == nameof(Enumerable.SelectMany)
let args = x.GetGenericArguments()
where args.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], typeof(IEnumerable<>).MakeGenericType(args[1]))
select x).Single();
/// <summary>
/// IEnumerable<TResult> Enumerable.Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)
/// </summary>
public static readonly MethodInfo Select1 = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == nameof(Enumerable.Select)
let args = x.GetGenericArguments()
where args.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
select x).Single();
(I have a gist full of these definitions)
You can
// SelectMany
var par1 = Expression.Parameter(typeof(Person));
var sub1 = Expression.Property(par1, nameof(Person.Addresses));
var lambda1 = Expression.Lambda<Func<Person, IEnumerable<Address>>>(sub1, par1);
var selectMany = Expression.Call(SelectMany1.MakeGenericMethod(typeof(Person), typeof(Address)), par0, lambda1);
// Select
var par2 = Expression.Parameter(typeof(Address));
var sub2 = Expression.Property(par2, nameof(Address.City));
var lambda2 = Expression.Lambda<Func<Address, string>>(sub2, par2);
var select = Expression.Call(Select1.MakeGenericMethod(typeof(Address), typeof(string)), selectMany, lambda2);
// persons => Select(SelectMany(persons))
var par0 = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeof(Person)));
var lambda0 = Expression.Lambda<Func<IEnumerable<Person>, IEnumerable<string>>>(select, par0);
var compiled = lambda0.Compile();
If you want the SelectMany+Select call it is in the select variable. If you want a compilable expression that uses the SelectMany+Select, it is in lambda0 and in compiled.

Related

combine multiple lambda expressions with different types to one expression

I want to combine some separated lambda expressions and build one final expression of them.
example classes :
class Address {
public string city { get; set; }
public string country { get; set; }
}
class ClassA {
public int Id { get; set; }
public Address address { get; set; }
}
class ClassB {
public int Id { get; set; }
public ClassA objectA { get; set; }
}
each class have one lambda expression :
Expression<Func<ClassA,bool>> classARule = a =>
a.Id > 1 && a.address.city == "city1" || a.address.country == "us"
Expression<Func<ClassB,bool>> classBRule = b => b.Id == 100
because ClassB has one property of ClassA it's possible to create an expression with both conditions. example :
// I want to create this expected object at runtime using classARule and classBRule
Expression<Func<ClassB,bool>> expected = b =>
(b.Id == 100) &&
(b.objectA.Id > 1 && b.objectA.address.city == "city1" || b.objectA.address.country == "us")
if I want to generate expected expression at runtime I should somehow convert a parameter of classARule to b.objectA
the problem is I know how to combine two expressions but I don't know how to replace a parameter with some other object. in this case b.objectA
Update - To avoid more confusion
the goal is to achieve Expression<Func<ClassB,bool>> expected expression at runtime using classARule and classBRule
Fortunately, I solved the problem.
The final result here is for others if they encounter such a problem.
public static Expression<Func<B, bool>> Combine<B, A>(this Expression<Func<B, bool>> expr1, Expression<Func<A, bool>> expr2, Expression<Func<B, A>> property)
{
// this is (q) parameter of my property
var replaceParameter = property.Parameters[0];
// replacing all (b) parameter with the (q)
// these two lines converts `b => b.Id == 100` to `q => q.Id == 100`
// using ReplaceExpVisitor class
var leftVisitor = new ReplaceExpVisitor(replaceParameter);
var left = leftVisitor.Visit(expr1.Body);
// the property body is 'q.objectA'
var replaceBody = property.Body;
// now i'm replacing every (a) parameter of my second expression to 'q.objectA'
// these two lines convert this statement:
// a.Id > 1 && a.address.city == "city1" || a.address.country == "us"
// to this :
// q.objectA.Id > 1 && q.objectA.address.city == "city1" || q.objectA.address.country == "us"
var rightVisitor = new ReplaceExpVisitor(replaceBody);
var right = rightVisitor.Visit(expr2.Body);
// creating new expression and pass (q) reference to it (replaceParameter).
return Expression.Lambda<Func<B, bool>>(Expression.AndAlso(left, right), replaceParameter);
}
// this is a simple class to replace all parameters with new expression
private class ReplaceExpVisitor : ExpressionVisitor
{
private readonly Expression _newval;
public ReplaceExpVisitor(Expression newval) => _newval = newval;
protected override Expression VisitParameter(ParameterExpression node)
{
return _newval;
}
}
usage :
var result = classBRule.Combine(classARule, q => q.objectA);
// or
Expression<Func<ClassB,bool>> result =
Combine<ClassB, ClassA>(classBRule, classARule, q => q.objectA);
/*
result is equal to the expected expression in the first example now
result output :
q =>
((q.Id == 100) &&
(((q.objectA.Id > 1) && (q.objectA.address.city == "city1")) ||
(q.objectA.address.country == "us")))
*/
https://dotnetfiddle.net/KnV3Dz
You'll need to compile the expression:
class Address
{
public string city { get; set; }
public string country { get; set; }
}
class ObjectA
{
public int Id { get; set; }
public Address address { get; set; }
}
class ObjectB
{
public int Id { get; set; }
public ObjectA objectA { get; set; }
}
Expression<Func<ObjectB, bool>> expected = b =>
(b.Id == 100) &&
(b.objectA.Id > 1 && b.objectA.address.city == "City1" || b.objectA.address.country == "US");
// Compile the Expression
var expectedItems = expected.Compile();
List<ObjectB> objBs = new List<ObjectB>();
var address = new Address();
var objA = new ObjectA();
var objB = new ObjectB();
address.city = "City1";
address.country = "US";
objA.Id = 1;
objB.Id = 100;
objA.address = address;
objB.objectA = objA;
objBs.Add(objB);
address = new Address();
objA = new ObjectA();
objB = new ObjectB();
address.city = "City2";
address.country = "US";
objA.Id = 3;
objB.Id = 100;
objA.address = address;
objB.objectA = objA;
objBs.Add(objB);
// Use expectedItems
var result = objBs.FirstOrDefault(b => expectedItems(b));

How to determine if all properties of type List<T> in an object are null or empty?

I have an object which contains few int/string properties and some List<T> properties where T are some other classes in the project itself. Is there a cleaner way to determine if only those List<T> properties are empty or null? Maybe using a Linq statement?
I tried searching about it but can't find a short and clean way. Should i opt for reflection?? Can someone provide a sample related to this?
public class A
{
..some properties..
List<ClassA> ListA { get; set; }
List<ClassB> ListB { get; set; }
List<ClassC> ListC { get; set; }
List<ClassD> ListD { get; set; }
..some properties..
}
EDIT 1:
So far i have managed to write a clean code to check if list properties are null. But how can i check if they are empty. I need to convert object to List but i dont know the type of List it is
var matchFound = myObject.GetType().GetProperties()
.Where(x => x.PropertyType == typeof(List<>))
.Select(x => x.GetValue(myObject))
.Any(x => x != null);
EDIT 2:
I ended up using this, a one liner that works fine:
var matchFound = myObject.GetType().GetProperties()
.Where(x =>(x.GetValue(myObject) as IList)?.Count()>0);
Here is what i would do.
/// <summary>
/// caching a Dyctionary of IList types for faster browsing
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static readonly Dictionary<Type, Type> CachedActualType = new Dictionary<Type, Type>();
// Get Internal type of IList.
// When the type is not a list then it will return the same type.
// if type is List<T> it will return the type of T
public static Type GetActualType(this Type type)
{
if (CachedActualType.ContainsKey(type))
return CachedActualType[type];
if (type.GetTypeInfo().IsArray)
CachedActualType.Add(type, type.GetElementType());
else if (type.GenericTypeArguments.Any())
CachedActualType.Add(type, type.GenericTypeArguments.First());// this is almost always find the right type of an IList but if it fail then do the below. dont really remember why this fail sometimes.
else if (type.FullName?.Contains("List`1") ?? false)
CachedActualType.Add(type, type.GetRuntimeProperty("Item").PropertyType);
else
CachedActualType.Add(type, type);
return CachedActualType[type];
}
And then
var matchFound = myObject.GetType().GetProperties()
.Where(x => x.PropertyType.GetActualType() != x.PropertyType &&
(x.GetValue(myObject) as IList)?.Count()>0);
You can actually do even better and dont need to check for type and only try to cast the value.
The value will always be null if the type is not an IList
var matchFound = myObject.GetType().GetProperties()
.Where(x =>(x.GetValue(myObject) as IList)?.Count()>0);
You can use reflection for your requirement, i have just tried it.
class Test
{
}
class UserDetails
{
public List<Test> Test1 { get; set; }
public List<Test> Test2 { get; set; }
public string firstname { get; set; }
public string surname { get; set; }
public string city { get; set; }
public string state { get; set; }
}
Use this query to search, you can customize where condition for your requirement
UserDetails yourObject = new UserDetails();
yourObject.Test1 = new List<Test> { new Test() };
var result = typeof(UserDetails).GetProperties()
.Select(prop => prop)
.Where(property =>
{
if (property.PropertyType == typeof(List<Test>))
{
var value = (List<Test>)property.GetValue(yourObject, null);
return value == null || value.Count == 0;
}
return false;
}).ToList(); // this will return 1 because 1 property has count > 1
Update if use Templete
class UserDetails<T>
{
public List<T> Test1 { get; set; }
public List<T> Test2 { get; set; }
public string firstname { get; set; }
public string surname { get; set; }
public string city { get; set; }
public string state { get; set; }
}
Query
UserDetails<Test> yourObject = new UserDetails<Test>();
yourObject.Test1 = new List<Test> { new Test() };
var result = typeof(UserDetails<Test>).GetProperties()
.Select(prop => prop)
.Where(property =>
{
if (property.PropertyType == typeof(List<Test>))
{
var value = (List<Test>)property.GetValue(yourObject, null);
return value == null || value.Count == 0;
}
return false;
}).ToList();
You need quite a bit of reflection:
// the type to test
public class TestData
{
public string A { get; set; }
public List<string> B { get; set; }
public List<int> C { get; set; }
}
// an helper class used to generate checking functions
public static class ListTester
{
public static Func<T, bool> MakeClassChecker<T>()
where T : class
{
var checkFunctions = EnumerateListProperties<T>()
.Select(MakePropertyChecker<T>)
.ToList();
return instance => checkFunctions.All(f => f(instance));
}
public static IEnumerable<PropertyInfo> EnumerateListProperties<T>()
{
return typeof(T).GetProperties(Instance | Public | NonPublic)
.Where(prop => IsListClosedType(prop.PropertyType));
}
public static Func<T, bool> MakePropertyChecker<T>(PropertyInfo prop)
where T : class
{
var propType = prop.PropertyType;
var listItemType = propType.GenericTypeArguments[0];
var listEmptyChecker = (Func<object, bool>) ListCheckerFactoryMethod
.MakeGenericMethod(listItemType).Invoke(null, new object[0]);
return instance => instance != null && listEmptyChecker(prop.GetValue(instance));
}
private static MethodInfo ListCheckerFactoryMethod
= typeof(ListTester).GetMethod(nameof(ListCheckerFactory), Static | Public);
public static Func<object, bool> ListCheckerFactory<T>()
{
return list => list == null || ((List<T>) list).Count == 0;
}
public static bool IsListClosedType(Type type)
{
return type != null &&
type.IsConstructedGenericType &&
type.GetGenericTypeDefinition() == typeof(List<>);
}
}
[Test]
public void TestTemp()
{
var props = ListTester.EnumerateListProperties<TestData>();
CollectionAssert.AreEquivalent(props.Select(prop => prop.Name), new[] {"B", "C"});
var allListsAreNullOrEmpty = ListTester.MakeClassChecker<TestData>();
Assert.That(allListsAreNullOrEmpty(new TestData()), Is.True);
Assert.That(allListsAreNullOrEmpty(new TestData() {B = new List<string>()}), Is.True);
Assert.That(allListsAreNullOrEmpty(new TestData() {B = new List<string>() {"A"}}), Is.False);
}
Now, for the important bits: you search for properties of closed generic types of List<>.
The selection of the properties is done in IsListClosedType.
Then, for each property, we make a checking function using MakePropertyChecker.
The job of MakePropertyChecker is to build via MakeGenericMethod a version of ListCheckerFactory
of the appropriate type.
You want to check all properites that are of type List<something>
This method can do the trick:
bool IsGenericList(Type t)
{
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>);
}
Now you can modify your Linq query so it returns if at least one of List members is not null or empty
var matchFound = myObject.GetType().GetProperties()
.Where(p => IsGenericList(p.PropertyType))
.Select(p => p.GetValue(myObject) as IEnumerable)
.Any(list => list != null && list.Cast<object>().Any());//Cast<object> needed to be able to use Linq Any()

Dynamic Linq with ".Any" in Where clausule (C# / .Net Core / EF Core)

I'm trying to make some article-filtering based on properties that are stored in another dbset. I'm using some classes:
public class Article
{
public string ArticleCode { get; set; }
public string var1 { get; set; }
public string var2 { get; set; }
public string var3 { get; set; }
public virtual List<ArticleProperty> Properties { get; set; }
}
public class ArticleProperty
{
public string ArticleCode { get; set; }
public string PropertyCode { get; set; }
public string var4 { get; set; }
public string var5 { get; set; }
public string var6 { get; set; }
}
public class ArticleSummary
{
public string ArticleCode { get; set; }
public string var7 { get; set; }
public string var8 { get; set; }
}
public class WebDbContext : DbContext
{
public virtual DbSet<Article> Article{ get; set; }
public virtual DbSet<ArticleProperty> ArticleProperty{ get; set; }
/* some more code */
}
When I create a query like this, it does what I want to do:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(a =>
a.Properties.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "X") ||
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "Y")
)
)
&&
a.Eigenschappen.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "B" && ap.var5 == "Z")
)
)
)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
But now I want to create the last Where-statement dynamically, like this (dataFilter is a Dictionary< string, Dictionary< string, bool>> with some filter-properties):
var query ="";
bool firstA = true;
foreach (KeyValuePair<string, Dictionary<string, bool>> filter in dataFilter)
{
if (firstA)
query += "a => ";
else
query += " && ";
query += "a.Properties.Any(ap =>"
+ "(";
bool firstB = true;
foreach (KeyValuePair<string,bool> filterDetail in filter.Value)
{
if (!firstB)
query += " || ";
query += "(ap.ArticleCode == a.ArticleCode && ap.var4 == \""+filter.Key+"\" && ap.var5 == \""+filterDetail.Key+"\")";
firstB = false;
}
query += ")"
+ ")";
firstA = false;
}
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
The 'query' is as expected but the Where doesn't work, the error:
System.Linq.Dynamic.Core.Exceptions.ParseException: 'No applicable aggregate method 'Any' exists'
This only occurs when there are 2 'Any'- statements (divided by &&, the same as when I do it 'hard-coded'). I don't know why...
Dynamic LINQ has its own expression language. Lambda expressions do not start with a => or ap =>, there is something called current scope which simplifies some queries, but in general is problematic with accessing outer level parameters. All queryable extensions define single scoped parameter called it, which can be omitted.
Shortly, Dynamic LINQ is not well suitable for complex queries with nested lambda expression accessing outer lambda parameters.
The goal can be achieved relatively easy with the combination of compile time and runtime expressions. The idea is simple.
First you create a compile time lambda expression with additional parameters which serve as placeholders. Then you replace the placeholders with actual expressions using the following simple expression visitor:
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
Pretty much like string.Format, but with expressions. Then you can use Expression.AndAlso and Expression.OrElse to produce the && and || parts.
With that being said, here is how it looks in your case:
Expression<Func<Article, string, string, bool>> detailExpr = (a, var4, var5) =>
a.Properties.Any(ap => ap.ArticleCode == a.ArticleCode && ap.var4 == var4 && ap.var5 == var5);
var p_a = detailExpr.Parameters[0];
var p_var4 = detailExpr.Parameters[1];
var p_var5 = detailExpr.Parameters[2];
var body = dataFilter
.Select(filter => filter.Value
.Select(filterDetail => detailExpr.Body
.ReplaceParameter(p_var4, Expression.Constant(filter.Key))
.ReplaceParameter(p_var5, Expression.Constant(filterDetail.Key)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<Article, bool>>(body, p_a);
Then use Where(predicate) in place of your current Where(query).
Instead of using a string, just use the query directly:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query);
foreach(...) {
Articles = Articles.Where(...);
}
Articles = Articles.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});

MVC model require all fields

I have an MVC application with EF6. Is there a way to automatically set all properties of a model to [Required]? Some of our models are large with all required fields. Any way to save lines of code or make this cleaner?
public class Employee{
[Required]
public string Name { get; set; }
[Required]
public string Address 1 { get; set; }
[Required]
public string Address 2 { get; set; }
[Required]
public int SSN { get; set; }
[Required]
public double PayRate { get; set; }
[Required]
public int PayType { get; set; }
[Required]
public string JobTitle { get; set; }
[Required]
public bool FullTime { get; set; }
[Required]
public string Sex { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Name { get; set; }
}
Thanks
You can MakeStringColumnsRequired or MakeAllNullableColumnsRequired by means of Fluent API and "dynamic" Expressions:
protected void MakeStringColumnsRequired<T>(DbModelBuilder modelBuilder)
where T : class
{
var type = typeof(T);
foreach (var prop in type.GetProperties().Where(x => x.PropertyType == typeof(string)))
{
var argParam = Expression.Parameter(type, "x");
var nameProperty = Expression.Property(argParam, prop.Name);
var lambda = Expression.Lambda<Func<T, string>>(nameProperty, argParam);
modelBuilder.Entity<T>().Property(lambda).IsRequired();
}
}
protected void MakeAllNullableColumnsRequired<T>(DbModelBuilder modelBuilder)
where T : class
{
var type = typeof(T);
foreach (var prop in type.GetProperties()
.Where(x => Nullable.GetUnderlyingType(x.PropertyType) != null || x.PropertyType == typeof(string))
)
{
var argParam = Expression.Parameter(type, "x");
var nameProperty = Expression.Property(argParam, prop.Name);
var funcType = typeof(Func<,>);
funcType = funcType.MakeGenericType(typeof(T), prop.PropertyType);
var lambdaMethod = typeof(Expression).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.Name == "Lambda" && x.IsGenericMethod).First();
lambdaMethod = lambdaMethod.MakeGenericMethod(funcType);
var lambda = lambdaMethod.Invoke(null, new object[] { nameProperty, new ParameterExpression[] { argParam } });
var entity = modelBuilder.Entity<T>();
var entityPropertyMethods = entity.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name == "Property" && !x.IsGenericMethod).ToList();
var entityPropertyMethod = entityPropertyMethods
.Where(x => x.GetParameters().First().ParameterType.GetGenericArguments().First().GetGenericArguments().Last() == prop.PropertyType)
.FirstOrDefault();
if(entityPropertyMethod == null)
{
entityPropertyMethod = entity.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name == "Property" && x.IsGenericMethod).Last();
entityPropertyMethod = entityPropertyMethod.MakeGenericMethod(Nullable.GetUnderlyingType(prop.PropertyType));
}
var property = entityPropertyMethod.Invoke(entity, new object[] { lambda });
var isRequired = property.GetType().GetMethod("IsRequired");
isRequired.Invoke(property, null);
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
#if StringColumnsRequired
MakeStringColumnsRequired<Employee>(modelBuilder);
#else
MakeAllNullableColumnsRequired<Employee>(modelBuilder)
#endif
}

LINQ to SQL select property name by string on projection

How can I achieve the projection on the last select? I need the property defined by the string prop.Name to be selected into the SeriesProjection object.
public override IQueryable<SeriesProjection> FilterOn(string column)
{
//Get metadata class property by defined Attributes and parameter column
var prop = typeof(CommunicationMetaData)
.GetProperties()
.Single(p => p.GetCustomAttribute<FilterableAttribute>().ReferenceProperty == column);
var attr = ((FilterableAttribute)prop.GetCustomAttribute(typeof(FilterableAttribute)));
var param = Expression.Parameter(typeof(Communication));
Expression conversion = Expression.Convert(Expression.Property(param, attr.ReferenceProperty), typeof(int));
var condition = Expression.Lambda<Func<Communication, int>>(conversion, param); // for LINQ to SQl/Entities skip Compile() call
var result = DbQuery.Include(prop.Name)
//.GroupBy(c => c.GetType().GetProperty(attr.ReferenceProperty))
.GroupBy(condition)
.OrderByDescending(g => g.Count())
.Select(group => new SeriesProjection()
{
Count = group.Count(),
Id = group.Key,
//set this navigation property dynamically
Name = group.FirstOrDefault().GetType().GetProperty(prop.Name)
});
return result;
}
For the GroupBy I used the fk property name that's always an int on the Communication entity, but for the select I can't figure out the expression.
[EDIT]
System.Data.Entity.Infrastructure.DbQuery<Communication> DbQuery;
---
[MetadataType(typeof(CommunicationMetaData))]
public partial class Communication
{
public int CommunicationId { get; set; }
public Nullable<int> TopicId { get; set; }
public int CreateById { get; set; }
public virtual Employee CreateByEmployee { get; set; }
public virtual Topic Topic { get; set; }
}
---
public class CommunicationMetaData
{
[Filterable("By Employee", nameof(Communication.CreateById))]
public Employee CreateByEmployee { get; set; }
[Filterable("By Topic", nameof(Communication.TopicId))]
public Topic Topic { get; set; }
}
---
[AttributeUsage(AttributeTargets.Property)]
public class FilterableAttribute : System.Attribute
{
public FilterableAttribute(string friendlyName, string referenceProperty)
{
FriendlyName = friendlyName;
ReferenceProperty = referenceProperty;
}
public string FriendlyName { get; set; }
public string ReferenceProperty { get; set; }
}
---
public class SeriesProjection
{
public int Count { get; set; }
public int Id { get; set; }
public object Name { get; set; }
}
Without some expression helper library, you have to build the whole selector expression manually.
The input of the selector will be a parameter of type IGrouping<int, Communication>, the result type - SeriesProjection, and the body will be MemberInit expression:
var projectionParameter = Expression.Parameter(typeof(IGrouping<int, Communication>), "group");
var projectionType = typeof(SeriesProjection);
var projectionBody = Expression.MemberInit(
// new SeriesProjection
Expression.New(projectionType),
// {
// Count = group.Count(),
Expression.Bind(
projectionType.GetProperty(nameof(SeriesProjection.Count)),
Expression.Call(typeof(Enumerable), "Count", new[] { typeof(Communication) }, projectionParameter)),
// Id = group.Key
Expression.Bind(
projectionType.GetProperty(nameof(SeriesProjection.Id)),
Expression.Property(projectionParameter, "Key")),
// Name = group.FirstOrDefault().Property
Expression.Bind(
projectionType.GetProperty(nameof(SeriesProjection.Name)),
Expression.Property(
Expression.Call(typeof(Enumerable), "FirstOrDefault", new[] { typeof(Communication) }, projectionParameter),
prop.Name))
// }
);
var projectionSelector = Expression.Lambda<Func<IGrouping<int, Communication>, SeriesProjection>>(projectionBody, projectionParameter);
and then of course use simply:
var result = DbQuery.Include(prop.Name)
.GroupBy(condition)
.OrderByDescending(g => g.Count())
.Select(projectionSelector);

Categories

Resources