我有以下查询:
// 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 to entities不允许在查询中使用PropertyInfo,我需要在集合上运行ToList()以便首先在db上执行查询,然后执行所需的SelectMany()。
这会从数据库中查询超出需要的数据,当存在大量数据时会出现问题(查询列的类型为字符串,其他列可能是blob,这些是我不想提取的数据从)
所以问题是如何根据运行时构建的列表限制从db查询的列?
我试图创建一个表达式树,并将其传递给集合上的Select()方法,但问题在于创建了匿名类型,它可以根据类型T而不同。
答案 0 :(得分:1)
你在这里的观察:
问题在于创建匿名类型,它可以根据类型T
而不同
准确;在运行时构造结果 columns 是非常有问题的。唯一简单的方法就是让它看起来像是在填充具有所有成员的类型的投影,例如相当于:
// 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));
}
}
在输出中,A
和C
有值,但B
没有。