Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projection to SQL fails while using Math.Floor with LINQ Sum expression #27124

Closed
JuliusSenkus opened this issue Jan 6, 2022 · 1 comment · Fixed by #28068
Closed

Projection to SQL fails while using Math.Floor with LINQ Sum expression #27124

JuliusSenkus opened this issue Jan 6, 2022 · 1 comment · Fixed by #28068
Assignees
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@JuliusSenkus
Copy link

Bug

Failing to project SQL expression when Math.Floor is used inside Sum (if retrieving only sum, no other properties, everything works like expected). By documentation it seems, that it simply should wrap contents with ROUND function.

Sample code:

// See https://aka.ms/new-console-template for more information
using Microsoft.EntityFrameworkCore;

using var ctx = new MyContext();
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
ctx.Carts.Add(
    new Cart { 
        Name = "Candy", 
        CartItems = new List<CartItem> { 
        new CartItem { Name = "Candy", Price = 1.15M, Quantity = 1.15M, }, 
        new CartItem { Name = "Salty Candy", Price = 1.15M, Quantity = 1.15M, }, 
        } 
    });
ctx.SaveChanges();

var cartsV1 = ctx.Carts.Select(c => new { Cart = c.Name, TotalAmount = c.CartItems.Sum(i => Math.Round(i.Quantity * i.Price, 2)), }).ToList();
var cartsV2 = ctx.Carts.Select(c => new { Cart = c.Name, TotalAmount = c.CartItems.Select(i => i.Quantity * i.Price).Sum(i => Math.Round(i, 2)), }).ToList();

Console.WriteLine($"Cart value: {cartsV1.First().TotalAmount}");

public class CartItem
{
    public Guid Id { get; set; }
    public string Name { get; set; } = String.Empty;
    public decimal Price { get; set; }
    public decimal Quantity { get; set; }
}

public class Cart {
    public Guid Id { get; set; }
    public string Name { get; set; } = String.Empty;
    public ICollection<CartItem> CartItems { get; set; } = new List<CartItem>();
}

public class MyContext : DbContext {
    public DbSet<Cart> Carts { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=FailedConversion;Integrated Security=true;");
    }
}

Stack traces:

   at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.ApplyProjection(Expression shaperExpression, ResultCardinality resultCardinality, QuerySplittingBehavior querySplittingBehavior)
   at Microsoft.EntityFrameworkCore.Query.Internal.SelectExpressionProjectionApplyingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPostprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Program.<Main>$(String[] args) in C:\Users\Julius.Senkus\Documents\Projects\TestProjects\TestConsole\TestConsole\Program.cs:line 17

Provider and version information

EF Core version: 6.0.1
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Windows 10
IDE: Visual Studio 2022

@JuliusSenkus
Copy link
Author

For anyone else simplest option to workaround this issue would be multiply everything by one:

-  var cartsV1 = ctx.Carts.Select(c => new { Cart = c.Name, TotalAmount = c.CartItems.Sum(i => Math.Round(i.Quantity * i.Price, 2)), }).ToList();
+  var cartsV1 = ctx.Carts.Select(c => new { Cart = c.Name, TotalAmount = c.CartItems.Select(i => Math.Round(i.Quantity * i.Price, 2)).Select(t => t * 1).Sum(), }).ToList();
// Desired result: `SUM(ROUND(c.Quantity * c.Price, 2))`
// Workaround solution: `SUM(ROUND(c.Quantity * c.Price, 2) * 1)`

From ef core code what I saw, it fails on not being able to extract the type from Math.Round expressions (innerSelectExpression.Limit is null).

@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label May 20, 2022
@ghost ghost closed this as completed in #28068 May 21, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0, 7.0.0-preview5 May 25, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0-preview5, 7.0.0 Nov 5, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants