如何使表达式将值类型视为引用类型?

时间:2013-11-22 08:57:02

标签: c# generics linq-to-sql expression

我想存储一组访问对象属性的表达式。例如:

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。:此集合中的表达式稍后用作OrderByThenBy方法的参数。

3 个答案:

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

这需要您使用反射创建OrderByThenBy表达式,例如

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的回答我自己发现,在订购数据时,我可以使用两种不同的方法,具体取决于指定的参数:

  1. Queryable.OrderBy Method Expression<Func<TSource, TKey>>
  2. Enumerable.OrderBy Method Func<TSource, TKey>
  3. 当使用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>排序没有问题。

    所以我在这里找到了两个选择:

    1. 在不排序的情况下查询数据库并订购返回的结果集
    2. 为LINQ提供更好的表达式,它可以编译SQL查询,然后在数据库层中应用排序;李在这里回答了一个方法......
    3. 在我的确切情况下,排序是最后一次执行的操作,如果我在程序的内存中订购结果集,我看不出太大的危害...我不会指望大量的数据是返回...

      虽然在更常见的情况下,在数据库层中执行所有可能的操作可能仍然更好......

      P.S。:SO: Order a linq query - 密切讨论......