Почему я вижу разницу между .Cast <int>() и .Select (a => (int) a)? - PullRequest
5 голосов
/ 15 января 2020

Я пытаюсь выяснить разницу между следующим:

someListOfEnums.Cast<int>()

и

someListOfEnums.Select(a => (int)a)?

Я обнаружил, что первое вызывает исключение при использовании в Where пункт в Entity Framework Core 3.1, но последний не делает. Я бы ожидал, что они будут действовать аналогично.

Возьмите следующий пример: publi c enum Fruit {Apple, Banana, Orange}

public class FruitTable
{
    public int Id { get; set; }
    public Fruit Value { get; set; }
}

public class FruitContext : DbContext
{
    public DbSet<FruitTable> Fruit { get; set; }
}

public void TestMethod(FruitContext context)
{
    var list = new List<Fruit>{Fruit.Apple, Fruit.Orange};

    var breaks = list.Cast<int>();
    var works = list.Select(a => (int)a);

    var fruits1 = context.Fruit.Where(a => works.Contains(a.Value)).ToList();  //This works
    var fruits2 = context.Fruit.Where(a => breaks.Contains(a.Value)).ToList();  //This breaks
}

Кажется например, использование .Cast<int>() приводит к предложению where, содержащему имя перечисления (Apple, Orange и т. д. c.), тогда как использование .Select(a => (int)a) - нет.


UPDATE

Я понял, что мой пример выше не вызывает той же проблемы (извинения). Я прошел и создал программу, которая определенно воспроизводит проблему.

Используя следующую базу данных:

CREATE DATABASE Fruit

USE Fruit

CREATE TABLE Fruit
(
Id INT NOT NULL PRIMARY KEY,
Value INT NOT NULL,
)

INSERT INTO Fruit VALUES (1, 0)
INSERT INTO Fruit VALUES (3, 2)

Следующая программа:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace ConsoleApp
{
    public class Program
    {
        static void Main(string[] args)
        {
            FruitTable.TestMethod(new FruitContext());
        }

        public enum Fruit
        {
            Apple,
            Banana,
            Orange
        }

        public class FruitTable
        {
            public int Id { get; set; }
            public int Value { get; set; }

            public static void TestMethod(FruitContext context)
            {
                IEnumerable<Fruit> list = new Fruit[] {Fruit.Apple, Fruit.Orange};

                var breaks = list.Cast<int>();
                var works = list.Select(a => (int) a);

                var fruits1 = context.Fruit.Where(a => works.Contains(a.Value)).ToList(); //This works
                var fruits2 = context.Fruit.Where(a => breaks.Contains(a.Value)).ToList(); //This breaks
            }
        }

        public class FruitContext : DbContext
        {
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer("Server=.;Database=fruit;Trusted_Connection=True;ConnectRetryCount=0");
            }

            public DbSet<FruitTable> Fruit { get; set; }
        }
    }
}

Вызывает следующая ошибка:

'Неверное имя столбца' Orange '. Неверное имя столбца 'Apple'. '


Редактировать

Просто добавить проблему не было в. Net Core 2.2, это появился, когда мы перешли на 3.1. Думая об этом - это может быть из-за этого: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq -queries-are-no-больше не оценивается-на-клиенте

1 Ответ

11 голосов
/ 15 января 2020

На самом деле, с. net перспективы Cast<int> и Select(a => (int)a различны. Cast поместит значения в object с и затем распакует их обратно в int.

static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source) {
            foreach (object obj in source) yield return (TResult)obj;
        }

И, как правило, Объект может быть распакован только на тот тип, к которому он относится . В противном случае будет сгенерировано исключение.

Но, поскольку базовое значение вашего Enum равно Int, Cast<int> будет работать, как и ожидалось.

Обновление :

Как прокомментировано, для решения проблемы вы можете добавить ToList() в конец запроса. Теперь этот запрос будет правильно оценен в. net сторону. В противном случае EF Core 3.0 попытается сгенерировать Sql и в случае сбоя выдаст исключение.

 var breaks = list.Cast<int>().ToList();

Относительно вашего редактирования:

Просто добавить, что проблема отсутствовала в. Net Core 2.2, он появился, когда мы перешли на 3.1. Подумайте об этом - это может быть связано с этим:

В той ссылке действительно хорошо объяснено, почему он работал. net core 2.2. Похоже, что в предыдущих версиях, когда EF Core не мог преобразовать выражение, являющееся частью запроса, в SQL или в параметр, он автоматически оценивал выражение на клиенте.

И это действительно плохой. Потому что, как было отмечено:

Например, условие в вызове Where (), которое не может быть переведено, может привести к тому, что все строки таблицы будут перенесены с сервера базы данных, а фильтр - в быть примененным к клиенту.

Итак, ранее вы просто загружали все данные на клиент, а затем применяли фильтр на стороне клиента.

...