在运行时根据列表集限制查询的列

本文关键字:查询 运行时 列表 | 更新日期: 2023-09-27 18:08:51

我有以下查询:

// Type T is constrained to a class that contains "ID" property
// propertiesToQuery is a list constructed based on type T
var set = AppContext.Set<T>();
var result = set.SelectMany(x => propertiesToQuery.Select(p => new { x.ID, Value = x.GetType().GetProperty(p.Name).GetValue(x) })
                                                          .Where(p => p.Value != null)
                                                          .Select(p => new SearchIndexItem
                                                                           {
                                                                               Key = p.Value.ToString(),
                                                                               Url = Url.Action("Edit", type.Name, new { p.ID }),
                                                                               Type = type
                                                                           }));

现在,因为linq到实体不允许在查询中使用PropertyInfo,我需要在集合上运行ToList(),以便首先在db上执行查询,然后执行所需的SelectMany()。

这查询比它需要从数据库,这将是一个问题,当有很多数据(查询列的类型是字符串,其他可以是blobs,这些是我不想从拉数据)

那么问题是我如何限制基于运行时构造的列表从db查询的列?

我试图创建一个表达式树,并将其传递给集合上的Select()方法,但问题是创建匿名类型,这可能取决于类型t。

在运行时根据列表集限制查询的列

你的观察:

问题在于创建匿名类型,它可以根据类型T

而有所不同

是准确的;在运行时构造结果是非常有问题的。要做到这一点,唯一简单的方法是使它看起来像是在填充具有所有成员的类型的投影,例如:

// where our list is "Foo", "Bar":
x => new SomeType {
   Foo = x.Foo,
   Bar = x.Bar
   // but other SomeType properties exist, but are't mapped
}

明显的竞争者将是实体类型,所以你部分地映射一组Customer行到Customer对象-但大多数orm不会让你这样做:如果投影是一个实体类型,他们想要整个类型(即x => x)。您可能能够创建实体类型的第二个版本,它是常规POCO/DTO,但不是实体模型的一部分,即

Customer x => new CustomerDto {
   Foo = x.Foo,
   Bar = x.Bar
}

可以在运行时作为Expression.MemberInit的一部分执行。例子:

class Foo
{
    public string A { get; set; }
    public int B { get; set; }
    public DateTime C { get; set; }
}
class FooDto
{
    public string A { get; set; }
    public int B { get; set; }
    public DateTime C { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        var data = new[] { new Foo { A = "a", B = 1, C = DateTime.Now}}
                 .AsQueryable();
        var mapped = PartialMap<Foo, FooDto>(data, "A", "C").ToList();
    }
    static IQueryable<TTo> PartialMap<TFrom, TTo>(
        IQueryable<TFrom> source, params string[] members)
    {
        var p = Expression.Parameter(typeof(TFrom));
        var body = Expression.MemberInit(Expression.New(typeof(TTo)),
            from member in members
            select (MemberBinding)Expression.Bind(
                typeof(TTo).GetMember(member).Single(),
                Expression.PropertyOrField(p, member))
            );
        return source.Select(Expression.Lambda<Func<TFrom, TTo>>(body, p));
    }
}

AC有值,B没有值