Используемые библиотеки:
Devart.Data.dll => 5.0.1878.0
Devart.Data.MySql.dll => 8.10.1086.0
Devart.Data.MySql.Design.dll => 8.10.1086.0
Devart.Data.MySql.Entity.EF6.dll => 8.10.1086.0
EntityFramework => 6.2.0
Мы ориентируемся на .net4.7.1 из проекта ASP.NET MVC5. Наши репозитории построены на основе EF .edmx (подход db-first). Наши запросы выглядят так:
var results = _db.NB_FILTERS
.Select(x => new ReportFiltersDTO { IsHiddenSubFilter = x.NFS_SUBFILTER_YN ?? false })
.ToList();
NFS_SUBFILTER_YN объявлен как логическое значение в нашем .edmx, также известном как:
<Property Name = "NFS_SUBFILTER_YN" Type = "boolean" />
Ddl NB_FILTERS выглядит так:
CREATE TABLE NB_FILTERS (
[...]
NFS_SUBFILTER_YN BIT(1) NULL,
[...]
)
В таком сценарии, если некоторые строки имеют значение NULL, данное выражение linq возвращает «истина» для всех из них вместо «ложь». Похоже, что виноват в автоматически сгенерированном sql:
actual => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 0 ELSE Extent1.NFS_SUBFILTER_YN END
corrected => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 1 ELSE CAST(Extent1.NFS_SUBFILTER_YN AS SIGNED) END
Как можно обойти эту ошибку без массового изменения базовых таблиц и / или самих операторов linq до тех пор, пока Devart не выпустит фактическое исправление?
Примечания:
Для тех, кто заинтересован, мы уведомили разработчиков devart об этой проблеме в надежде, что она будет решена в какой-то момент:
Достаточно интересно, что такие запросы работают, когда задействованные столбцы являются столбцами с десятичным числом (x, y), допускающими значение NULL [автоматически сгенерированный sql правильно использует CAST (... AS SIGNED)]. Похоже, что каким-то образом бит (x) был пропущен из списка типов, которые подходят для такой обработки.
Хороший аргумент. К сожалению, моя команда не может настроить базу данных на данный момент как из-за временных ограничений, так и из-за того, что поломка может быть вызвана в других частях / платформах, поражающих тот же БД. Еще один типичный день в мире дев-лэндов :(
Ваша проблема и решение ниже напоминают мне первый день в моем универе и "историю качелей деревьев" :) :)
Что делать, если вы делаете IsHiddenSubFilter = (bool?)x.NFS_SUBFILTER_YN ?? false?
@GertArnold x.NFS_SUBFILTER_YN уже объявлен как Nullable <bool>, поэтому приведение не имеет никакого эффекта
Конечно, но это может удачно повлиять на сгенерированный SQL.
@GertArnold Я тоже тестировал, но, к сожалению, без радости





В качестве временного временного решения мы решили использовать перехватчик EF6 в mysql, чтобы обнаруживать и исправлять операторы sql на лету, нацеленные на логические столбцы (к счастью, большинство, если не все наши логические столбцы имеют постфикс _YN, который помогает отличная сделка):
using System.Data;
using System.Data.Common;
using System.Data.Entity.Infrastructure.Interception;
using System.Text.RegularExpressions;
namespace Some.Namespace
{
//to future maintainers the devart mysql drivers for ef6 ver8.10.1086.0 suffer from what appears to be a very annoying b.ug which cause nullable bit(1) columns set to null
//to future maintainers to always be materialized to true when we evaluate them like so
//to future maintainers
//to future maintainers x.SOME_BOOLEAN_COLUMN ?? false
//to future maintainers
//to future maintainers this is obviously flat out wrong and to remedy this issue we intercept the offending sql snippets and hotfix them like so
//to future maintainers
//to future maintainers before => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 1 ELSE Extent1.NFS_SUBFILTER_YN END
//to future maintainers after => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 1 ELSE CAST(Extent1.NFS_SUBFILTER_YN AS SIGNED) END
//to future maintainers
public sealed class MySqlHotFixerCommandInterceptor : IDbCommandInterceptor
{
//beforeexecution
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) => LogWarningBeforeExecutionIfSynchronousExecutionIsFound(command, interceptionContext);
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) => LogWarningBeforeExecutionIfSynchronousExecutionIsFound(command, interceptionContext);
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) => LogWarningBeforeExecutionIfSynchronousExecutionIsFound(command, interceptionContext);
//afterexecution
public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) {}
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) {}
public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) {}
static private void LogWarningBeforeExecutionIfSynchronousExecutionIsFound<TResult>(IDbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (interceptionContext.Exception != null)
return;
command.CommandText = FaultySwitchCaseSpotter1.Replace(command.CommandText, "CAST($3 AS SIGNED)");
command.CommandText = FaultySwitchCaseSpotter2.Replace(command.CommandText, "CAST($3 AS SIGNED)");
}
private static readonly Regex FaultySwitchCaseSpotter1 = new Regex(@"(?<=CASE\s+WHEN\s+(`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?`?\s+IS\s+NULL\s+THEN\s+(0|1)\s+ELSE\s+)((`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?`?)(?=\s*END)", RegexOptions.IgnoreCase);
private static readonly Regex FaultySwitchCaseSpotter2 = new Regex(@"(?<=CASE\s+WHEN\s+(`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?_YN`?\s+IS\s+NULL\s+THEN\s+(\d+|.[a-zA-Z0-9_]+?|`?[a-zA-Z0-9_]+?`?[.]`?[a-zA-Z0-9_]+?`?)\s+ELSE\s+)((`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?_YN`?)(?=\s*END)", RegexOptions.IgnoreCase);
}
}
Также необходимо настроить web.config / app.config, например:
<configuration>
<entityFramework>
<defaultConnectionFactory type = "System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
[...]
</defaultConnectionFactory>
<providers>
[...]
</providers>
<interceptors>
<!-- keep first -->
<interceptor type = "Some.Namespace.MySqlHotFixerCommandInterceptor, Organotiki.Infrastructure.Core">
</interceptor>
[...]
</interceptors>
</entityFramework>
</configuration>
зачем разрешать нулевое значение, если это бит (1) тип? Используя / разрешая null, вы разрешаете (3) истинные, ложные, неизвестные значения, когда вашему приложению требуется только (2) истинное / ложное. Я бы посоветовал изменить ваш столбец и установить его как ненулевое, значение по умолчанию = false. Вам не нужно иметь дело с лишними хлопотами.