[linq2db bug] Исключение в запросе с OUTER APPLY
От: Петрухин Эдуард Россия  
Дата: 27.07.18 09:12
Оценка:
Версия 2.2, MS SQL Server 2016

Есть две таблицы:
[Table("Authors")]
public class Author
{
    [PrimaryKey, Identity]
    public int Id { get; set; }

    [Column(CanBeNull = false)]
    public string Name { get; set; }
}

[Table("Books")]
public class Book
{
    [PrimaryKey, Identity]
    public int Id { get; set; }

    [Column(CanBeNull = false)]
    public int AuthorId { get; set; }

    [Column(CanBeNull = false)]
    public string Title { get; set; }
}

Обращение к ним идёт через две переменные:
var authors = db.GetTable<Author>();
var books = db.GetTable<Book>();


Запрос
from author in authors
let booksCount =
(
    from book in books
    where book.AuthorId == author.Id
    select Sql.Ext.Count(book.Id).ToValue()
).Single()
where booksCount > 42
select new
{
    author.Name,
    BooksCount = booksCount
}

генерирует следующий SQL код:
SELECT
        [t3].[Name],
        [t2].[c1] as [c11],
        [t2].[c2] as [c21]
FROM
        [Authors] [t3]
                OUTER APPLY (
                        SELECT
                                COUNT([t1].[Id]) as [c1],
                                1 as [c2]
                        FROM
                                [Books] [t1]
                        WHERE
                                [t1].[AuthorId] = [t3].[Id]
                ) [t2]
WHERE
        (
                SELECT
                        COUNT([t4].[Id])
                FROM
                        [Books] [t4]
                WHERE
                        [t4].[AuthorId] = [t3].[Id]
        ) > 42

Запрос получается неестественный и, возможно, не оптимальный.

Запрос
from author in authors
let booksAgg =
(
    from book in books
    where book.AuthorId == author.Id
    select new
    {
        Count = Sql.Ext.Count(book.Id).ToValue()
    }
).Single()
where booksAgg.Count > 42
select new
{
    author.Name,
    BooksCount = booksAgg.Count
}

падает:
LinqToDB.Linq.LinqException: 'Table([Books]).Where(book => (book.AuthorId == <>h__TransparentIdentifier0.author.Id)).Select(book => new <>f__AnonymousType5`1(Count = Sql.Ext.Count(book.Id).ToValue())).Single().Count' cannot be converted to SQL.
   at LinqToDB.Linq.Builder.ExpressionBuilder.ConvertToSql(IBuildContext context, Expression expression, Boolean unwrap) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 1029
   at LinqToDB.Linq.Builder.ExpressionBuilder.ConvertCompare(IBuildContext context, ExpressionType nodeType, Expression left, Expression right) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 1588
   at LinqToDB.Linq.Builder.ExpressionBuilder.ConvertPredicate(IBuildContext context, Expression expression) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 1370
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSearchCondition(IBuildContext context, Expression expression, List`1 conditions) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 2529
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildWhere(IBuildContext parent, IBuildContext sequence, LambdaExpression condition, Boolean checkForSubQuery, Boolean enforceHaving) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 48
   at LinqToDB.Linq.Builder.WhereBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\WhereBuilder.cs:line 23
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.cs:line 175
   at LinqToDB.Linq.Builder.SelectBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\SelectBuilder.cs:line 36
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.cs:line 175
   at LinqToDB.Linq.Builder.ExpressionBuilder.Build[T]() in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.cs:line 146
   at LinqToDB.Linq.Query`1.CreateQuery(IDataContext dataContext, Expression expr) in C:\projects\linq2db\Source\LinqToDB\Linq\Query.cs:line 280
   at LinqToDB.Linq.Query`1.GetQuery(IDataContext dataContext, Expression& expr) in C:\projects\linq2db\Source\LinqToDB\Linq\Query.cs:line 233
   at LinqToDB.Linq.ExpressionQuery`1.GetQuery(Expression& expression, Boolean cache) in C:\projects\linq2db\Source\LinqToDB\Linq\ExpressionQuery.cs:line 84
   at LinqToDB.Linq.ExpressionQuery`1.get_SqlText() in C:\projects\linq2db\Source\LinqToDB\Linq\ExpressionQuery.cs:line 53
   at ConsoleApp.Program.WorkWithDb(DbMain db) in C:\Src\Personal\TestPolygon\ConsoleApp\Program.cs:line 68
   at ConsoleApp.Program.WorkWithDb(String dataSource, String initialCatalog) in C:\Src\Personal\TestPolygon\ConsoleApp\Program.Db.cs:line 57
   at ConsoleApp.Program.Main() in C:\Src\Personal\TestPolygon\ConsoleApp\Program.Core.cs:line 21


Если делать фильтрацию попозже:
var query =
    from author in authors
    let booksAgg =
    (
        from book in books
        where book.AuthorId == author.Id
        select new
        {
            Count = Sql.Ext.Count(book.Id).ToValue()
        }
    ).Single()
    select new
    {
        author.Name,
        BooksCount = booksAgg.Count
    };

query = query.Where(x => x.BooksCount > 42);

то падает с другим исключением:
System.NotImplementedException: The method or operation is not implemented.
   at LinqToDB.Linq.Builder.SelectContext.ProcessMemberAccess[T](Expression expression, MemberExpression levelExpression, Int32 level, Func`6 action) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\SelectContext.cs:line 974
   at LinqToDB.Linq.Builder.SelectContext.IsExpressionInternal(Expression expression, Int32 level, RequestFor requestFlag) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\SelectContext.cs:line 731
   at LinqToDB.Linq.Builder.SelectContext.IsExpression(Expression expression, Int32 level, RequestFor requestFlag) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\SelectContext.cs:line 627
   at LinqToDB.Linq.Builder.ExpressionContext.IsExpression(Expression expression, Int32 level, RequestFor requestFlag) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionContext.cs:line 116
   at LinqToDB.Linq.Builder.ExpressionBuilder.<>c__DisplayClass97_0.<CheckSubQueryForWhere>b__0(Expression expr) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 90
   at LinqToDB.Expressions.Extensions.Visit(Expression expr, Func`2 func) in C:\projects\linq2db\Source\LinqToDB\Expressions\Extensions.cs:line 373
   at LinqToDB.Expressions.Extensions.Visit(Expression expr, Func`2 func) in C:\projects\linq2db\Source\LinqToDB\Expressions\Extensions.cs:line 418
   at LinqToDB.Linq.Builder.ExpressionBuilder.CheckSubQueryForWhere(IBuildContext context, Expression expression, Boolean& makeHaving) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 149
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildWhere(IBuildContext parent, IBuildContext sequence, LambdaExpression condition, Boolean checkForSubQuery, Boolean enforceHaving) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.SqlBuilder.cs:line 32
   at LinqToDB.Linq.Builder.WhereBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\WhereBuilder.cs:line 23
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo) in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.cs:line 175
   at LinqToDB.Linq.Builder.ExpressionBuilder.Build[T]() in C:\projects\linq2db\Source\LinqToDB\Linq\Builder\ExpressionBuilder.cs:line 146
   at LinqToDB.Linq.Query`1.CreateQuery(IDataContext dataContext, Expression expr) in C:\projects\linq2db\Source\LinqToDB\Linq\Query.cs:line 280
   at LinqToDB.Linq.Query`1.GetQuery(IDataContext dataContext, Expression& expr) in C:\projects\linq2db\Source\LinqToDB\Linq\Query.cs:line 233
   at LinqToDB.Linq.ExpressionQuery`1.GetQuery(Expression& expression, Boolean cache) in C:\projects\linq2db\Source\LinqToDB\Linq\ExpressionQuery.cs:line 84
   at LinqToDB.Linq.ExpressionQuery`1.get_SqlText() in C:\projects\linq2db\Source\LinqToDB\Linq\ExpressionQuery.cs:line 53
   at ConsoleApp.Program.WorkWithDb(DbMain db) in C:\Src\Personal\TestPolygon\ConsoleApp\Program.cs:line 22
   at ConsoleApp.Program.WorkWithDb(String dataSource, String initialCatalog) in C:\Src\Personal\TestPolygon\ConsoleApp\Program.Db.cs:line 56
   at ConsoleApp.Program.Main() in C:\Src\Personal\TestPolygon\ConsoleApp\Program.Core.cs:line 21


Для меня больше важен вариант с
from book in books
where book.AuthorId == author.Id
select new
{
    Count = Sql.Ext.Count(book.Id).ToValue()
}

потому что на практике надо считать по подзапросу не один агрегат, а несколько.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.