他の質問に対する私の回答と同じアプローチがここでも機能します。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 はそれを処理する方法を認識しています。MyContext
Expression.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"))));
}