【问题标题】:How to make expression treat value type as a reference type?如何使表达式将值类型视为引用类型?
【发布时间】:2013-11-22 08:57:01
【问题描述】:

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

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 方法的参数。

【问题讨论】:

  • Convert 在那里有什么害处?如果要对值类型进行装箱(如果要编译表达式),则隐式需要 Convert 操作。或者你只是在获得表达式的字符串表示之后?一种选择是通过在Expression&lt;S, T&gt; 周围创建一个包装器类(如Wrapper&lt;S&gt;)使其完全通用,并将包装器添加到列表而不是表达式本身。这行得通吗?
  • @nawfal 我想Convert 出现在那里是由于Func 签名,因为它应该返回object,并且它确实尝试将值装箱。在一个真正的应用程序中,我确实有一个表达式集合,就像 OP 中描述的那样。它们实际上用于OrderByThenBy 与LINQ to SQL IQuerable&lt;Entity&gt;。所以我得到一个例外,说,它未能通过object订购
  • 这是个好问题。你只关心 OrderBy 和 ThenBy 吗?还是您希望它用于一大堆查询方法?
  • 您想要的动态 Linq 功能,并且已经有一些库可用(您可以进行搜索)。但我在这里建议的是anthony's answer here。基本上思路和我之前评论中提到的一样,备份包装器而不是表达式本身,然后将做Linq操作的任务委托给类本身。但是,如果您需要负担更多的方法,那就需要更多的工作。稍后我会尝试提供答案。
  • @nawfal 谢谢。我会尽快考虑清楚,然后写在这里。

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


【解决方案1】:

如果你在属性类型中创建一个泛型函数,你可以避免转换:

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:我会详细检查你的答案...然后回信
【解决方案2】:

我修补了一个 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它:没问题。

我的回答是“你不必担心这个”。

【讨论】:

  • LINQ to SQL 在这方面似乎与 EF 不同。至少,在您的代码中,您将Func&lt;Entity, object&gt; 传递给OrderBy,而在LINQ to SQL 中它需要Expression&lt;Func&lt;Entity,object&gt;&gt;....并且它确实会在那里引发异常...
  • +1:所以,我不得不担心 :)....但从长远来看,我确实考虑将所有代码移植到 EF...所以我会检查你的代码,并在未来记住这一点
  • 很高兴能提供帮助。仅供参考,我尝试在 LINQ to SQL dbml 上下文中运行相同的代码(在 sql server 表上复制该类),但它仍然运行良好。
  • 在我看来,我找到了解释,为什么你的代码有效。主要区别在于您使用Enumerable.OrderBy,而我尝试使用Queryable.OrderBy。有关更多信息,请在下面查看我的回答...无论如何,非常感谢您,因为您的回答让我看到了这一点,我非常感谢!
  • 啊,我明白了,我没有意识到,但它是有道理的(当我看到代码工作时,当我尝试在dbml 我预计它会爆炸,但它没有......毫无疑问,如果/当我必须以这种方式使用它时,我会牢记这一点)
【解决方案3】:

感谢 Alex 的回答,我自己发现,在排序数据时,我可以使用两种不同的方法,具体取决于指定的参数:

  1. Queryable.OrderBy Method Expression&lt;Func&lt;TSource, TKey&gt;&gt;
  2. Enumerable.OrderBy Method Func&lt;TSource, TKey&gt;

当使用Queryable.OrderBy 时,LINQ 将OrderBy 子句编译成SQL 语句,在数据库上执行。所以当我尝试给它一个看起来像e =&gt; Convert(e.Field)Expression&lt;Func&lt;TEntity, object&gt;&gt;时,LINQ会抛出一个InvalidOperationException,说Cannot order by type 'System.Object'

当使用Enumerable.OrderBy 时,LINQ 不会将OrderBy 子句编译到 SQL 查询中,而是执行当前查询并对程序内存中查询返回的可枚举实体应用排序。这里Func&lt;TEntity, object&gt;下单没问题。

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

  1. 查询数据库不排序,返回结果集排序
  2. 为LINQ提供更好的表达式,它可以编译SQL查询,然后在数据库层应用排序; Lee 的回答在这里提出了一种方法..

在我的确切情况下,排序是执行的最后一个操作,如果我在程序的内存中对结果集进行排序...我不会期望有大量数据是返回...

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

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

【讨论】:

    猜你喜欢
    • 2021-02-16
    • 1970-01-01
    • 2013-06-07
    • 1970-01-01
    • 2022-10-23
    • 1970-01-01
    • 1970-01-01
    • 2017-06-09
    • 2021-10-30
    相关资源
    最近更新 更多