Почему я вижу разницу между .Cast <int> () и .Select (a => (int) a)?

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

someListOfEnums.Cast<int>()

и

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

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

Возьмите следующий пример: public 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>() приводит к .Cast<int>() where, содержащему имя перечисления (Apple, Orange и т. Д.), Тогда как использование .Select(a => (int)a) - нет.


ОБНОВИТЬ

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

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

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 -по-более оценивали-на-клиенте

Всего 1 ответ


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

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

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

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

Обновить:

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

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

Что касается вашего редактирования:

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

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

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

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

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


Есть идеи?

10000