我想存储一组访问对象属性的表达式。例如:
class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Id);
list.Add(e => e.Name);
list.Add(e => e.Parent);
list.Add(e => e.Date);
list.Add(e => e.Value);
list.Add(e => e.Active);
StringBuilder b = new StringBuilder();
list.ForEach(f => b.AppendLine(f.ToString()));
Console.WriteLine(b.ToString());
Console.ReadLine();
}
此代码输出:
e => Convert(e.Id)
e => e.Name
e => e.Parent
e => Convert(e.Date)
e => Convert(e.Value)
e => Convert(e.Active)
它会为值类型添加Convert
。
最后我想在LINQ to SQL中使用这些表达式,我不需要在表达式中使用Convert
,因为它们可以成功地转换为SQL。
我怎样才能做到这一点?
P.S。:此集合中的表达式稍后用作OrderBy
和ThenBy
方法的参数。
答案 0 :(得分:3)
如果在proeprty类型中创建泛型函数,则可以避免转换:
private static LambdaExpression GetExpression<TProp>
(Expression<Func<Entity, TProp>> expr)
{
return expr;
}
然后您可以更改list
的类型:
var list = new List<LambdaExpression>();
list.Add(GetExpression(e => e.Id));
list.Add(GetExpression(e => e.Name));
这需要您使用反射创建OrderBy
和ThenBy
表达式,例如
LambdaExpression idExpr = list[0];
Type keyType = idExpr.ReturnType;
var orderByMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Entity), keyType);
var ordered = (IQueryable<Entity>)
orderByMethod.Invoke(null, new object[] { source, idExpr });
答案 1 :(得分:1)
我修补了EF代码首次尝试使用您的代码
public class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
public class EntityContext : DbContext
{
public EntityContext()
: base(new SqlCeConnection("Data Source=Database.sdf;Persist Security Info=False;"),
contextOwnsConnection: true)
{
// Using a SQL Compact database as backend
}
public DbSet<Entity> Entities { get; set; }
}
并在上下文中尝试了一些linq
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Date);
list.Add(e => e.Name);
using (var c = new EntityContext())
{
//each time a new record is added
var data = new Entity
{
Name = string.Format("Data{0}", c.Entities.Count()),
Date = DateTime.Now
};
c.Entities.Add(data);
c.SaveChanges();
// sort by date
foreach (var e in c.Entities.OrderBy(list.First().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
// sort by name .. in reverse
foreach (var e in c.Entities.OrderByDescending(list.Last().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
}
Console.ReadLine();
}
运行代码没有问题。
UPDATE LINQ to SQL也是如此:我在本地SQL Server中构建了一个与该类结构相同的表,并尝试OrderBy
它:没问题。
我的回答是“你不必担心”。
答案 2 :(得分:0)
感谢Alex的回答我自己发现,在订购数据时,我可以使用两种不同的方法,具体取决于指定的参数:
Expression<Func<TSource, TKey>>
Func<TSource, TKey>
当使用Queryable.OrderBy
时,LINQ将OrderBy
子句编译到SQL语句中,在数据库上执行。因此,当我尝试给它Expression<Func<TEntity, object>>
看起来像e => Convert(e.Field)
时,LINQ抛出InvalidOperationException
,说Cannot order by type 'System.Object'
。
当使用Enumerable.OrderBy
时,LINQ不会将OrderBy
子句编译到SQL查询中,而是执行当前查询并对查询返回的可枚举实体应用排序,在程序中记忆。按Func<TEntity, object>
排序没有问题。
所以我在这里找到了两个选择:
在我的确切情况下,排序是最后一次执行的操作,如果我在程序的内存中订购结果集,我看不出太大的危害...我不会指望大量的数据是返回...
虽然在更常见的情况下,在数据库层中执行所有可能的操作可能仍然更好......
P.S。:SO: Order a linq query - 密切讨论......