LINQ to SQL select property name by string on projection

c# expression-trees linq

Question

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; }
}

Accepted Answer

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);


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why