使用 GroupBy 选择 Entity Framework 中的行

Using GroupBy to pick rows in Entity Framework

我正在尝试获取品牌和型号的所有独特组合的列表,还包括最近的时间戳和来自以下 table 的相应颜色:

+-------+---------+-------------------------+--------+
| Make  | Model   |        Last Seen        | Color  |
+-------+---------+-------------------------+--------+
| Volvo | XC90    | 2022-03-11 13:30:31.623 | Blue   |
| Volvo | XC90    | 2022-03-11 14:30:31.623 | Green  |
| BMW   | M3      | 2022-03-11 15:30:31.623 | Orange |
| Ford  | Mustang | 2022-03-11 16:30:31.623 | Red    |
+-------+---------+-------------------------+--------+

在这种情况下,由于有两行带有品牌 Volvo 和型号 XC90,我想选择具有最新时间戳的行和 return 以下内容:

+-------+---------+-------------------------+--------+
| Make  | Model   |        Last Seen        | Color  |
+-------+---------+-------------------------+--------+  
| Volvo | XC90    | 2022-03-11 14:30:31.623 | Green  |
| BMW   | M3      | 2022-03-11 15:30:31.623 | Orange |
| Ford  | Mustang | 2022-03-11 16:30:31.623 | Red    |
+-------+---------+-------------------------+--------+

我尝试了 GroupBy 和 select 的不同组合,但没有成功。这是我正在尝试做的一个例子,我认为它非常接近,但它抛出了一个异常

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace TestEntityFramework
{
    class Program
    {
        public class Car
        {
            public Guid Id { get; set; }
            public string Make { get; set; }
            public string Model { get; set; }
            public DateTime Time { get; set; }
            public string Color { get; set; }
        }

        public class CarContext : DbContext
        {
            public CarContext(DbContextOptions<CarContext> options) : base(options)
            {
            }

            public DbSet<Car> Cars { get; set; }
        }

        static async Task Main(string[] args)
        {
            DbContextOptions<CarContext> _options = new DbContextOptionsBuilder<CarContext>()
                    .UseInMemoryDatabase("CarsDataBase")
                    .Options;

            Car car1 = new Car() { Id = Guid.NewGuid(), Make = "Volvo", Model = "VC90", Time = DateTime.Now, Color = "Blue" };
            Car car2 = new Car() { Id = Guid.NewGuid(), Make = "Volvo", Model = "VC90", Time = DateTime.Now, Color = "Green" };
            Car car3 = new Car() { Id = Guid.NewGuid(), Make = "BMW", Model = "M3", Time = DateTime.Now, Color = "Red" };
            Car car4 = new Car() { Id = Guid.NewGuid(), Make = "Ford", Model = "Mustang", Time = DateTime.Now, Color = "Orange" };

            using (var context = new CarContext(_options))
            {
                await context.Cars.AddAsync(car1);
                await context.Cars.AddAsync(car2);
                await context.Cars.AddAsync(car3);
                await context.Cars.AddAsync(car4);

                await context.SaveChangesAsync();
            }

            using (var context = new CarContext(_options))
            {
                var cars = context.Cars.GroupBy(o => new { o.Make, o.Model })
                    .Select(n => n.OrderByDescending(c => c.Time).First())
                    .ToList();
            }
        }
     }
}

我收到这个错误:

System.InvalidOperationException   HResult=0x80131509   Message=The LINQ expression 'GroupByShaperExpression: KeySelector: new { 
    Make = ExpressionExtensions.ValueBufferTryReadValue<string>(
        valueBuffer: grouping.Key, 
        index: 0, 
        property: Property: Car.Make (string)), 
    Model = ExpressionExtensions.ValueBufferTryReadValue<string>(
        valueBuffer: grouping.Key, 
        index: 1, 
        property: Property: Car.Model (string))  },  ElementSelector:EntityShaperExpression: 
    EntityType: Car
    ValueBufferExpression: 
        ProjectionBindingExpression: EmptyProjectionMember
    IsNullable: False

    .OrderByDescending(c => c.Time)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.  Source=Microsoft.EntityFrameworkCore.InMemory   StackTrace:    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryExpressionTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)    at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)    at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryExpressionTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)    at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)    at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryExpressionTranslatingExpressionVisitor.TranslateInternal(Expression expression)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryExpressionTranslatingExpressionVisitor.Translate(Expression expression)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryProjectionBindingExpressionVisitor.Visit(Expression expression)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryProjectionBindingExpressionVisitor.Translate(InMemoryQueryExpression queryExpression, Expression expression)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)    at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)    at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)    at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)    at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)    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 TestEntityFramework.Program.<Main>d__2.MoveNext() in C:\Apps\TestEntityFramework\Program.cs:line 52

EF Core 6 应该可以处理您的查询。对于 EF Core 5,使用以下模拟,它接近 EF Core 6 内部所做的:

using (var context = new CarContext(_options))
{
    var query = context.Cars;

    var carQuery = 
        from d in query.Select(o => new { o.Make, o.Model }).Distinct()
        from car in query
            .Where(car => car.Make == d.Make && car.Model == d.Model)
            .OrderByDescending(car => car.Time)
            .Take(1)
        select car;

    var cars = carQuery.ToList();
}