LINQ to SQL在投影时按字符串选择属性名称

时间:2016-08-15 06:42:56

标签: c# linq expression-trees

如何在最后一次选择中实现投影?我需要将字符串prop.Name定义的属性选择到SeriesProjection对象中。

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

对于GroupBy,我使用的fk属性名称始终是Communication实体上的int,但对于select,我无法弄清楚表达式。

[编辑]

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

1 个答案:

答案 0 :(得分:1)

如果没有一些表达式帮助程序库,则必须手动构建整个选择器表达式。

选择器的输入将是IGrouping<int, Communication>类型的参数,结果类型为SeriesProjection,正文将为MemberInit表达式:

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

然后当然简单地使用:

var result = DbQuery.Include(prop.Name)
        .GroupBy(condition)
        .OrderByDescending(g => g.Count())
        .Select(projectionSelector);