Entity Framework Skip and Take no traducido al comando SQL

var allUseresInclude = _userService.Include(r => r.Profession);

var model = allUseresInclude.Select(s => new ViewModel.User
{
    Profession = s.Profession.Name,
    Id = s.Id,
    ProviderUserId = s.ProviderUserId,
    PlatformId = s.PlatformId,
    Email = s.Email,
    Password = s.Password,
    RefreshToken = s.RefreshToken,
    Name = s.Name,
    Surname = s.Surname,
    Fullname = s.Name + " " + s.Surname,
    Gender = s.Gender,
    ProfessionId = s.ProfessionId,
    BirthDate = s.BirthDate.ToString(),
    IdentityNumber = s.IdentityNumber,
    Phone = s.Phone,
    IsActive = s.IsActive,
    CreatedDate =  _common.DateTimeToTimeZone(s.CreatedDate),//.ToString("dd.MM.yyyy HH:mm"),
    EmailConfirmed = s.EmailConfirmed
});

var tt = model.OrderByDescending(o => o.CreatedDate).Skip(20).Take(30).ToList();

El modelo es una variable IQueryable. Primero, estoy seleccionando las propiedades para mi ViewModel entonces trato de filtrar mi IQueryable. La ejecución de consultas tarda un tiempo y veo resultados correctos en la interfaz de usuario, pero en SQL la consulta generada no tiene compensación o límite

    SELECT [r].[CreatedDate], [r.Profession].[Name] AS [Profession], [r].[Id], [r].[ProviderUserId], [r].[PlatformId], [r].[Email], [r].[Password], [r].[RefreshToken], [r].[Name], [r].[Surname], ([r].[Name] + N' ') + [r].[Surname] AS [Fullname], [r].[Gender], [r].[ProfessionId], CONVERT(VARCHAR(100), [r].[BirthDate]) AS [BirthDate], [r].[IdentityNumber], [r].[Phone], [r].[IsActive], [r].[EmailConfirmed]
FROM [User] AS [r]
LEFT JOIN [Profession] AS [r.Profession] ON [r].[ProfessionId] = [r.Profession].[Id]

Cuando despido esta consulta, devuelve todas las mesas así que supongo que mi patrón y la toma no funciona como se esperaba. ¿Estoy haciendo algo mal?

Pregunta hecha hace 3 años, 5 meses, 1 días - Por nasean


3 Respuestas:

  • Una de las razones para esto es utilizar .ToList() o .Select() a principios. Eso significa que deberías comprobar si tu consulta realmente produce IQueryable.

    var model = allUseresInclude.Select(s => new ViewModel.User
    {
        Profession = s.Profession.Name,
        Id = s.Id,
        ProviderUserId = s.ProviderUserId,
        PlatformId = s.PlatformId,
        Email = s.Email,
        Password = s.Password,
        RefreshToken = s.RefreshToken,
        Name = s.Name,
        Surname = s.Surname,
        Fullname = s.Name + " " + s.Surname,
        Gender = s.Gender,
        ProfessionId = s.ProfessionId,
        BirthDate = s.BirthDate.ToString(),
        IdentityNumber = s.IdentityNumber,
        Phone = s.Phone,
        IsActive = s.IsActive,
        CreatedDate =  _common.DateTimeToTimeZone(s.CreatedDate),//.ToString("dd.MM.yyyy HH:mm"),
        EmailConfirmed = s.EmailConfirmed
    });
    

    En su código preste atención a _common.DateTimeToTimeZone esto obligará a Entity Framework a ser ejecutado en la memoria de lo contrario esto no puede ser ejecutado y traducido a la consulta SQL.

    Si esta parte se ejecuta en la memoria de la aplicación que significa Skip y Take se ejecutan después de eso en la memoria también.

    Usted debe tratar con una solución como esta:

    var model = allUseresInclude.OrderByDescending(o => o.CreatedDate).Skip(20).Take(30).Select(s => new ViewModel.User
    {
        Profession = s.Profession.Name,
        Id = s.Id,
        ProviderUserId = s.ProviderUserId,
        PlatformId = s.PlatformId,
        Email = s.Email,
        Password = s.Password,
        RefreshToken = s.RefreshToken,
        Name = s.Name,
        Surname = s.Surname,
        Fullname = s.Name + " " + s.Surname,
        Gender = s.Gender,
        ProfessionId = s.ProfessionId,
        BirthDate = s.BirthDate.ToString(),
        IdentityNumber = s.IdentityNumber,
        Phone = s.Phone,
        IsActive = s.IsActive,
        CreatedDate =  _common.DateTimeToTimeZone(s.CreatedDate),//.ToString("dd.MM.yyyy HH:mm"),
        EmailConfirmed = s.EmailConfirmed
    }).ToList();
    

    Respondida el Dec 16, 2020 a las 09:18 - por binarybard

    Votos positivos: 0 | Votos negativos: 0

  • El problema que estás enfrentando es que al realizar una operación de Skip y Take en una consulta IQueryable, Entity Framework no traduce directamente esto en el comando SQL resultante. En lugar de eso, lo que Entity Framework hace es ejecutar primero la consulta sin considerar el Skip y Take, y luego aplicar el filtrado en memoria, lo que puede resultar en una recuperación de más registros de los necesarios.

    Para resolver este problema y hacer que Entity Framework traduzca correctamente el Skip y Take al comando SQL, puedes hacer lo siguiente:

    csharp
    var tt = model.OrderByDescending(o => o.CreatedDate).Skip(20).Take(30).ToList();
    

    Puedes usar un método de extensión adicional del común OrderBy para forzar a EF Core a reordenar los registros.

    csharp
    var tt = model.AsEnumerable().OrderBy(o => o.CreatedDate).Skip(20).Take(30).ToList();
    

    Al llamar a AsEnumerable() antes de aplicar el OrderBy, garantizas que la ordenación se realiza en memoria, pero el resto de la consulta se traducirá a SQL correctamente.

    Con estos cambios, Entity Framework debería traducir adecuadamente el Skip y Take al comando SQL resultante, lo que optimizará el rendimiento de tu consulta y evitará recuperar más registros de los que necesitas.

    Respondida el Dec 17, 2020 a las 09:13 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • El método Skip y Take no se traducen directamente a SQL en Entity Framework Core. En su lugar, EF Core utiliza la paginación basada en memoria para filtrar los resultados en memoria después de ejecutar la consulta.

    Para habilitar la paginación basada en bases de datos, puedes utilizar el método AsNoTrackingWithIdentityResolution antes de llamar a Skip y Take. Esto forzará a EF Core a generar una consulta SQL que incluya la cláusula OFFSET-FETCH.

    El siguiente código actualizado debería funcionar:

    
    var model = allUseresInclude.Select(s => new ViewModel.User
    {
        Profession = s.Profession.Name,
        Id = s.Id,
        ProviderUserId = s.ProviderUserId,
        PlatformId = s.PlatformId,
        Email = s.Email,
        Password = s.Password,
        RefreshToken = s.RefreshToken,
        Name = s.Name,
        Surname = s.Surname,
        Fullname = s.Name + " " + s.Surname,
        Gender = s.Gender,
        ProfessionId = s.ProfessionId,
        BirthDate = s.BirthDate.ToString(),
        IdentityNumber = s.IdentityNumber,
        Phone = s.Phone,
        IsActive = s.IsActive,
        CreatedDate =  _common.DateTimeToTimeZone(s.CreatedDate),//.ToString("dd.MM.yyyy HH:mm"),
        EmailConfirmed = s.EmailConfirmed
    }).AsNoTrackingWithIdentityResolution();
    
    var tt = model.OrderByDescending(o => o.CreatedDate).Skip(20).Take(30).ToList();
    

    Ten en cuenta que AsNoTrackingWithIdentityResolution puede afectar el rendimiento de tu aplicación, ya que EF Core necesita realizar un seguimiento de las entidades modificadas para resolver las identidades. Solo debes usar este método cuando sea necesario para habilitar la paginación basada en bases de datos.

    Respondida el Dec 17, 2020 a las 09:24 - por Gemini

    Votos positivos: 0 | Votos negativos: 0