У меня есть запрос, похожий на следующий.
var adjustments = DbContext.StorageAdjustments
.Where(a => a.StorageId == id && a.TimeStamp >= start)
.Select(a => new StorageUpdateModel
{
// Maps properties from the StorageAdjustments table to the StorageUpdateModel.
TimeStamp = a.TimeStamp,
Change = a.Quantity,
Product = a.Product.Name,
UpdateType = StorageUpdateType.Adjustment,
UpdateData = Convert.ToString(a.UserName),
UpdateData2 =Convert.ToString(a.Comments),
Id = 0,
});
var trucks = DbContext.StorageTrucks
.Where(t => t.StorageId == id && t.TimeStamp >= start)
.Select(t => new StorageUpdateModel
{
// Maps properties from the StorageTrucks table to the StorageUpdateModel.
TimeStamp = t.TimeStamp,
Change = -t.Quantity,
Product = "",
UpdateType = StorageUpdateType.Truck,
UpdateData = (t.Truck.BillOfLading.HasValue && t.Truck.BillOfLading.Value>0 ? Convert.ToString(t.Truck.BillOfLading.Value) :""),
UpdateData2 = (t.Truck.ManualEntry ? "True" : "False"),
Id = t.TruckId,
});
var railcars = DbContext.StorageRailcars
.Where(r => r.StorageId == id && r.TimeStamp >= start)
.Select(r => new StorageUpdateModel
{
// Maps properties from the StorageRailcars table to the StorageUpdateModel.
TimeStamp = r.TimeStamp,
Change = -r.Quantity,
Product = "",
UpdateType = StorageUpdateType.Railcar,
UpdateData = ((r.Railcar!=null && r.Railcar.RailcarNumber!=null)? Convert.ToString(r.Railcar.RailcarNumber) : ""),
UpdateData2 = "",
Id = r.RailcarId,
});
// Union all three queries into a single query.
var query = adjustments
.Union(trucks)
.Union(railcars)
.OrderByDescending(u => u.TimeStamp)
.AsQueryable();
var results = await query.ToListAsync();
При его выполнении генерируется исключение.
System.InvalidOperationException
HResult=0x80131509
Message=Unable to translate set operation when matching columns on both sides have different store types.
Source=Microsoft.EntityFrameworkCore.Relational
StackTrace:
at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.ApplySetOperation(SetOperationType setOperationType, SelectExpression select2, Boolean distinct)
at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.ApplyUnion(SelectExpression source2, Boolean distinct)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateUnion(ShapedQueryExpression source1, ShapedQueryExpression source2)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
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__DisplayClass12_0`1.<ExecuteAsync>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at RailtraxCore.Pager.<ApplyFilterAsync>d__23`1.MoveNext() in D:\Users\jwood\source\repos\Railtrax\RailtraxCore\Pager.cs:line 117
Но если я изменю строку:
Product = a.Product.Name,
К:
Product = Convert.ToString(a.Product.Name),
Тогда исключение не выдается!
a.Product.Name
уже является строкой. Может ли кто-нибудь предложить какие-либо идеи относительно того, как Convert.ToString()
имеет значение?
Примечание. Типы ссылок, допускающие значение NULL, на этой странице отключены. И StorageUpdateModel.Product
, и a.Product.Name
относятся к типу string
(не string?
). a.Product.Name
— обязательный столбец. И я использую EntityFrameworkCore 7.0.3.
@juharr: Да, та же проблема. Они обе струнные. Стараюсь не усложнять, просто сосредоточившись на a.Product.Name
.
C# — это язык типов. Вы не предоставляете определение класса для StorageUpdateModel.Product
или a.Product.Name
. Они одинаковы? Вы используете обнуляемость? Является ли a.Product.Name
обнуляемым? Какой LINQ вы на самом деле используете - LINQ to Entity Framework и LINQ to Entity Framework Core очень разные, и им также нужна версия.
@NetMage: здесь отключены ссылочные типы, допускающие значение NULL. И StorageUpdateModel.Product
, и a.Product.Name
относятся к типу string
(не string?
). a.Product.Name
— обязательный столбец. Использование EntityFrameworkCore 7.0.3.
Похоже, вы столкнулись с недостатком дизайна в EF Core: он будет исправлен в EF Core 8 (см. #19129 ), но я не уверен, что он будет исправлен в EF Core 7. Было принято окончательное решение. чтобы не проверять на стороне EF Core и позволить SQL беспокоиться об этом.
@NetMage: Да, но я до сих пор не совсем понимаю, что делает Convert.ToString()
. Я погуглил и нашел проблему на GitHub. Но я не могу объяснить, как Convert.ToString()
решает эту проблему.
Поскольку вы выполняете Union
, EF Core не заботится о StorageUpdateModel
и его типах — он будет работать с типами, назначенными каждому свойству. Текущая реализация операций над наборами в EF Core проверяет соответствие типов SQL, поэтому тип a.Product.Name
в базе данных предположительно похож на nvarchar(30)
, а тип ""
— на nvarchar(max)
, поэтому они не совпадают, и возникает (неверное) исключение. Convert.ToString
преобразует подразумеваемый тип SQL в nvarchar(max)
, и теперь они совпадают.
@NetMage: Да, это звучит правильно. Погуглив, я смог найти SqlFunctions.StringConvert()
, но ни слова о том, как Convert.ToString()
обрабатывается EF. Но кажется довольно очевидным, что EF понимает и переводит Convert.ToString()
.
Примечание для следующего раза ... Если вы погуглите и найдете материал, который кажется близким и, возможно, актуальным, включите его в свой вопрос (резюме и ссылка, вероятно, будут достаточно хорошими). Нет смысла, когда куча людей выдает одну и ту же информацию.
+1 к нулевой способности, хотя из памяти EF обычно справляется с этим лучше, но бывают ли случаи, когда продукт для данной строки равен #null ?? Вы можете попробовать что-то вроде этого, чтобы определить, есть ли странная проблема с обработкой нулей: Product = a.Product?.Name ?? "".
EF 6 до того, как ссылочные типы, допускающие значение null, просто возвращали бы #null, если продукт не был найден или не имел имени. Сейчас /w EF Core 7?? Честно говоря, ссылочные типы, допускающие значение Null, обязательно будут проблемой в будущем, поскольку, хотя ваш код может простить их, детали реализации в EF и других библиотеках могут быть немного более педантичными.
К сожалению, в EF Core есть сопоставления функций для конкретного поставщика без обязательного минимального набора переведенных методов, но если вы используете SQL Server, вот задокументированные сопоставления функций для поставщика SQL Server. Вы можете видеть, что Convert.ToString
приводит к nvarchar(max)
, что совместимо со строковыми литералами. Если бы был способ привести строковый литерал к определенному nvarchar(n)
, чтобы соответствовать пониманию EF Core определения столбца базы данных, это сработало бы, но я не уверен, как это сделать.
@NetMage: Это отличная находка. Спасибо! Если вам захочется написать это как ответ, я приму это.
Проблема в том, что Entity Framework гарантирует, что все столбцы совпадают при выполнении Union()
s. Это включает даже максимальную длину строк (например, NVARCHAR(50)
).
Это причина исключения Unable to translate set operation when matching columns on both sides have different store types.
здесь.
Однако Convert.ToString()
преобразует строку в NVARCHAR(MAX)
(как описано здесь). В результате Entity Framework считает столбцы совместимыми.
Что менее ясно, так это то, почему Entity Framework блокирует первый случай, когда очевидно, что это можно заставить работать.
Вывод неверный. Сам SQL требует, чтобы совпадающие столбцы в UNION имели один и тот же тип и либо преобразовывали их неявно, либо через ошибку. Такие запросы обычно являются признаком ошибок дизайна (почему для одного и того же объекта использовались разные типы?) или вызывают ошибки, поскольку числа или даты могут быть преобразованы в текст с использованием неправильного формата.
В этом конкретном случае, похоже, есть несколько проблем с дизайном. Неоднозначные свойства UpdateData
и UpdateData2
содержат совершенно разные вещи. Это проблема ООП. Затем запрос LINQ пытается преобразовать два разных использования в запрос SQL. Первый запрос пытается загрузить коносамент, второй — номер вагона, когда поля таблицы имеют разные типы. Было бы лучше иметь отдельные запросы, загружающие только то, что необходимо, и сопоставлять результаты с StorageUpdateModel
, используя, например, Automapper или Mapperly.
@PanagiotisKanavos: кто-то в Microsoft сказал мне, что ограничением является Entity Framework. Это известная проблема, на которую многие жаловались. UpdateData
и UpdateData2
— обе строки. И мы знаем, что их можно сделать совместимыми, потому что я сделал их совместимыми с Convert.ToString()
.
Почему вы используете его для
a.UserName
иa.Comments
или это одна и та же проблема с разными столбцами? И каков точный тип этих столбцов в БД?