【问题标题】:How to obtain ToTraceString for IQueryable.Count如何获取 IQueryable.Count 的 ToTraceString
【发布时间】:2012-01-25 07:59:09
【问题描述】:

我使用((ObjectQuery)IQueryable).ToTraceString() 来获取和调整将由 LINQ 执行的 SQL 代码。

我的问题是,与大多数 IQueryable 方法不同,IQueryable.Count 定义如下:

    public static int Count(this IQueryable source) {
        return (int)source.Provider.Execute(
            Expression.Call(
                typeof(Queryable), "Count",
                new Type[] { source.ElementType }, source.Expression));
    }

执行查询而不编译和返回 IQueryable。 我想通过这样的方式来做到这一点:

public static IQueryable CountCompile(this IQueryable source) {
    return source.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable), "Count",
            new Type[] { source.ElementType }, source.Expression));
}

但是 CreateQuery 给了我以下异常:

LINQ to Entities query expressions can only be constructed from instances that implement the IQueryable interface.

【问题讨论】:

    标签: c# linq lambda linq-to-entities expression-trees


    【解决方案1】:

    当我尝试这样做时,我想出了一个实际可行的答案。异常说“只能从实现 IQueryable 接口的实例构造”,所以答案似乎很简单:返回一个可查询的东西。返回.Count() 时可能吗?是的!

    public partial class YourObjectContext
    {
        private static MethodInfo GetMethodInfo(Expression<Action> expression)
        {
            return ((MethodCallExpression)expression.Body).Method;
        }
        public IQueryable<TResult> CreateScalarQuery<TResult>(Expression<Func<TResult>> expression)
        {
            return QueryProvider.CreateQuery<TResult>(
                Expression.Call(
                    method: GetMethodInfo(() => Queryable.Select<int, TResult>(null, (Expression<Func<int, TResult>>)null)),
                    arg0: Expression.Call(
                        method: GetMethodInfo(() => Queryable.AsQueryable<int>(null)),
                        arg0: Expression.NewArrayInit(typeof(int), Expression.Constant(1))),
                    arg1: Expression.Lambda(body: expression.Body, parameters: new[] { Expression.Parameter(typeof(int)) })));
        }
    }
    

    使用它:

    var query = context.CreateScalarQuery(() => context.Entity.Count());
    MessageBox.Show(((ObjectQuery)query).ToTraceString());
    

    基本上,它的作用是将非 IQueryable 查询包装在子选择中。它将查询转换为

    from dummy in new int[] { 1 }.AsQueryable()
    select context.Entity.Count()
    

    except 让上下文的 QueryProvider 处理查询。生成的 SQL 几乎是您所期望的:

    SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        COUNT(1) AS [A1]
        FROM [dbo].[Entity] AS [Extent1]
    )  AS [GroupBy1]
    

    【讨论】:

    • 太好了,你知道如何在 DBContext 上实现同样的效果,而不使用它的 ObjectContext 吗?当我尝试使用相同的方法时 - 它会引发异常 - 无法创建实例 {Entity I want to count},查询只能使用原始值
    • @PhilippMunin 当我在使用 DbContext 的项目中尝试这个时,它工作得很好。您能否提供更多详细信息来说明如何使其失败? (如果重要的话,我的项目使用的是相当新的但仍是预发布的 EF6 版本。)
    • 我为此创建了单独的问题:stackoverflow.com/questions/19385346/… 如果您能找到解决方法,将不胜感激
    【解决方案2】:

    您不能为“Count”创建查询对象,因为它不返回 IQueryable(这是有道理的 - 它返回单个值)。

    你有两个选择:

    • (推荐)使用 eSQL:

      context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString()
      
    • 使用 Reflection 调用不执行 IQueryable 检查的私有方法(由于显而易见的原因,这是错误的,但如果您只是需要它进行调试,它可能会很方便):

      public static IQueryable CountCompile(this IQueryable source)
      {
          // you should cache this MethodInfo
          return (IQueryable)source.Provider.GetType().GetMethod("CreateQuery", BindingFlags.NonPublic | BindingFlags.Instance, null,
                                              new[] {typeof (Expression), typeof (Type)}, null)
              .Invoke(source.Provider, new object[]
                                           {
                                               Expression.Call(
                                                   typeof (Queryable), "Count",
                                                   new[] {source.ElementType}, source.Expression),
                                               source.ElementType
                                           });
      }
      

    【讨论】:

    • 我考虑过第一个解决方案,但您的第二个解决方案非常有趣,也是我一直在寻找的。我定义了 Value 以通过 CountCompile 完成 Count 解决方案: public static T Value(this IQueryable source) { return (T)source.Provider.Execute(source.Expression);这样我就可以使用 query.CountCompile().Value() 而不是 Count()。
    • 您应该知道,此解决方案依赖于内部实现细节,并且在任何 EF 版本中都可能失败。
    • 另外,如果您需要操作由 EF 生成的 SQL,您可能需要考虑创建一个自定义提供程序,尽管这比您要复杂的 很多重新建议。看看这个样本:archive.msdn.microsoft.com/EFSampleProvider
    • 我认为使用 eSQL select count(1) 代替 Linq Count() 也依赖于内部实现细节,因为 Linq Count() 可能会或可能不会生成与 select count(1) 相同的 SQL 代码.我觉得使用相同的方法 Count(即使是私有的)来构建表达式树实际上对实现的依赖程度较低,假设它在内部生成与 public Count 相同的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多