如何获取 IQueryable.Count 的 ToTraceString

发布于 2024-12-18 11:29:10 字数 842 浏览 3 评论 0原文

我使用 ((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 查询表达式只能从实现 IQueryable 接口的实例构造。

I use ((ObjectQuery)IQueryable).ToTraceString() to obtain and tweak SQL code that is going to be executed by LINQ.

My problem is that unlike most IQueryable methods IQueryable.Count as defined like this:

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

executes query without compiling and returning IQueryable.
I wanted to do the trick by something like this:

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

But then CreateQuery gives me the following exception:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

忆依然 2024-12-25 11:29:10

这是我在尝试这样做时想到的一个实际有效的答案。异常表示“只能从实现 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]

Here's an actual working answer I came up with when I tried to so the same. The exception says "can only be constructed from instances that implement the IQueryable interface", so the answer seems simple: return a queryable something. Is that possible when returning a .Count()? Yes!

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)) })));
    }
}

To use it:

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

Basically, what this does is wrap a non-IQueryable query in a subselect. It transforms the query into

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

except lets the context's QueryProvider handle the query. The generated SQL is pretty much what you should expect:

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Entity] AS [Extent1]
)  AS [GroupBy1]
为你拒绝所有暧昧 2024-12-25 11:29:10

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

您有两个选择:

  • (推荐)使用 eSQL:

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

    public static IQueryable CountCompile(此 IQueryable 源)
    {
        // 你应该缓存这个MethodInfo
        返回 (IQueryable)source.Provider.GetType().GetMethod("CreateQuery", BindingFlags.NonPublic | BindingFlags.Instance, null,
                                            new[] {typeof (表达式), typeof (类型)}, null)
            .Invoke(source.Provider, 新对象[]
                                         {
                                             表达式.调用(
                                                 typeof(可查询),“计数”,
                                                 new[] {source.ElementType}, source.Expression),
                                             源.ElementType
                                         });
    }
    

You cannot create a query object for 'Count' since it is does not return an IQueryable (which makes sense - it returns a single value).

You have two options:

  • (Recommended) Use eSQL:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString()
    
  • Use Reflection to call a private method that doesn't perform the IQueryable check (this is wrong for obvious reasons but if you just need it for debugging, it may be handy):

    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
                                         });
    }
    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文