1

割り当てと呼ばれる DbSet を持つ DBContext があります。列挙可能な式のクエリ可能を作成してそれらを連結することは問題ではありませんが、Count、Any、Max、Sum などの関数の遅延実行で IQueryable を取得する方法がわかりません。

基本的に、次のように実行できるように、いくつかの IQueryable 拡張機能が必要です。

IQueryable<int> query = 
          myDbContext.SelectValue((ctx)=>ctx.Assignments.Where(...).Count())
.UnionAll(myDbContext.SelectValue((ctx)=>ctx.Assignments.Where(...).Count()));

次の SQL (query.ToString()) を取得します。

SELECT 
[UnionAll1].[C1] AS [C1]
FROM  (SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        COUNT([Extent1].[UserId]) AS [A1]
        FROM [dbo].[Assignments] AS [Extent1]
                WHERE ...
    )  AS [GroupBy1]
UNION ALL
    SELECT 
    [GroupBy2].[A1] AS [C1]
    FROM ( SELECT 
        COUNT([Extent2].[UserId]) AS [A1]
        FROM [dbo].[Assignments] AS [Extent2]
                WHERE ...
    )  AS [GroupBy2]) AS [UnionAll1]

重要: ご覧のとおり、最後に ONE SQL REQUEST GENERATED を使用して、ユニオンと結合を使用してサブクエリで使用できるようにする必要があります。RAW SQL を使用できず、エンティティに文字列名を使用できないため、ObjectContextAdapter.ObjectContext.CreateQuery が機能していません。

ここでは、ObjectContext を使用してそれを実現する方法を見つけることができますが、エラーがスローされるため、私の場合はこのアプローチを使用できません。

タイプ '代入' の定数値を作成できません。このコンテキストでは、プリミティブ型または列挙型のみがサポートされています。

4

1 に答える 1

1

他の質問に対する私の回答と同じアプローチがここでも機能します。EF5 を使用した自己完結型のテスト プログラムを次に示します。

using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ScratchProject
{
    public class A
    {
        public int Id { get; set; }

        public string TextA { get; set; }
    }

    public class B
    {
        public int Id { get; set; }

        public string TextB { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<A> As { get; set; }

        public DbSet<B> Bs { get; set; }

        protected IQueryProvider QueryProvider
        {
            get
            {
                IQueryable queryable = As;
                return queryable.Provider;
            }
        }

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

        static MethodInfo GetMethodInfo(Expression<Action> expression)
        {
            return ((MethodCallExpression)expression.Body).Method;
        }
    }

    static class Program
    {
        static void Main()
        {
            using (var context = new MyContext())
            {
                Console.WriteLine(context.CreateScalarQuery(() => context.As.Count(a => a.TextA != "A"))
                    .Concat(context.CreateScalarQuery(() => context.Bs.Count(b => b.TextB != "B"))));
            }
        }
    }
}

出力:

SELECT
[UnionAll1].[C1] AS [C1]
FROM  (SELECT
        [GroupBy1].[A1] AS [C1]
        FROM ( SELECT
                COUNT(1) AS [A1]
                FROM [dbo].[A] AS [Extent1]
                WHERE N'A' <> [Extent1].[TextA]
        )  AS [GroupBy1]
UNION ALL
        SELECT
        [GroupBy2].[A1] AS [C1]
        FROM ( SELECT
                COUNT(1) AS [A1]
                FROM [dbo].[B] AS [Extent2]
                WHERE N'B' <> [Extent2].[TextB]
        )  AS [GroupBy2]) AS [UnionAll1]

はい、実際にクエリを実行すると、期待どおりに動作します。

更新

要求に応じて、これを機能させるために追加できるものを次に示しますExpression<Func<MyContext, TResult>> expression)

public IQueryable<TResult> CreateScalarQuery<TResult>(Expression<Func<MyContext, TResult>> expression)
{
    var parameterReplacer = new ParameterReplacer(expression.Parameters[0], Expression.Property(Expression.Constant(new Tuple<MyContext>(this)), "Item1"));
    return CreateScalarQuery(Expression.Lambda<Func<TResult>>(parameterReplacer.Visit(expression.Body)));
}

class ParameterReplacer : ExpressionVisitor
{
    readonly ParameterExpression parameter;
    readonly Expression replacement;

    public ParameterReplacer(ParameterExpression parameter, Expression replacement)
    {
        this.parameter = parameter;
        this.replacement = replacement;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node == parameter)
            return replacement;

        return base.VisitParameter(node);
    }
}

これは、現在のコンテキスト内から呼び出された場合でも機能します。

// member of MyContext
public void Test1()
{
    Console.WriteLine(this.CreateScalarQuery(ctx => ctx.As.Count(a => a.TextA != "A"))
        .Concat(this.CreateScalarQuery(ctx => ctx.Bs.Count(b => b.TextB != "B"))));
}

EF は を処理する方法を認識していないため、パラメーターの置換により、コンテキストTuple<MyContext>が直接ではなくa に格納されます。いずれにせよ、これは C# コンパイラが決して生成しないものであるため、EF はそれを処理する方法を知る必要はありません。クラスのメンバーとしてコンテキストを取得することは、C# コンパイラが生成するものであるため、EF はそれを処理する方法を認識しています。MyContextExpression.Constant(this)

ただし、ローカル変数に保存すると、より単純なバージョンもCreateScalarQuery動作させることができます。this

// member of MyContext
public void Test2()
{
    var context = this;
    Console.WriteLine(this.CreateScalarQuery(() => context.As.Count(a => a.TextA != "A"))
        .Concat(this.CreateScalarQuery(() => context.Bs.Count(b => b.TextB != "B"))));
}
于 2013-10-16T07:14:41.477 に答える