DbContext get IQueryable for scalar system functions (Count, Any, Sum, Max)
我有 DBContext 和名为 Assignments 的 DbSet。
为可枚举表达式创建可查询并将它们连接起来不是问题,但是我看不到通过延迟执行来获得 IQueryable 的方法,例如 Count、Any、Max、Sum。
基本上我想要一些 IQueryable 扩展,这样我就可以像这样执行它:
1 2 3 | IQueryable<int> query = myDbContext.SelectValue((ctx)=>ctx.Assignments.Where(...).Count()) .UnionAll(myDbContext.SelectValue((ctx)=>ctx.Assignments.Where(...).Count())); |
并获取以下 SQL (query.ToString()):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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] |
重要提示:如您所见,我需要能够在子查询中使用它,使用联合和连接,最后生成一个 SQL 请求。我不能使用 RAW SQL,也不能对实体使用字符串名称,这就是为什么我没有看到 ObjectContextAdapter.ObjectContext.CreateQuery 为我工作。
在这里你可以找到一种使用 ObjectContext 实现它的方法,但我不能在我的情况下使用这种方法,因为它会引发错误:
Unable to create a constant value of type 'Assignment'. Only primitive
types or enumeration types are supported in this context.
与我对其他问题的回答相同的方法也适用于此。这是一个使用 EF5 的独立测试程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | 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 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")))); } } } } |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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] |
是的,实际执行查询也按预期工作。
更新:
根据要求,您可以添加以下内容以使其也适用于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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); } } |
即使从当前上下文中调用它也有效:
1 2 3 4 5 6 | // 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")))); } |
参数替换将上下文存储在
但是,如果将
1 2 3 4 5 6 7 | // 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")))); } |