Entity Framework 6 SQL 服务器上的代码优先:将 "bool" 映射到 "numeric(1,0)" 而不是 "bit"
Entity Framework 6 Code First on SQL Server: Map "bool" to "numeric(1,0)" instead of "bit"
转发警告 #0:在不久的将来升级到 EF 核心不是一个选项。
转发警告 #1:我无法将列类型更改为 bit
,因为这可能会破坏使用相同数据库的遗留 VB 应用程序 我正在为其开发新应用程序.
前向警告 #2:我也不能使用 int
属性 ==> hidden bool 属性 方法,因为在针对 Oracle 时需要使用完全相同的模型数据库(在 Oracle decimal(1,0)
中确实毫无问题地映射到 bool
- 我需要在 SQL 服务器中实现同样的事情)。
假设我们有一个像这样的简单 table:
CREATE TABLE FOOBAR
(
FB_ID NUMERIC(11,0) PRIMARY KEY,
FB_YN NUMERIC(1,0) NOT NULL
);
INSERT INTO FOOBAR (FB_ID, FB_YN)
VALUES (1, 1), (2, 0);
一个简单的 poco class:
public class FOOBAR
{
public long FB_ID {get; set;}
// [Column(TypeName = "numeric(1,0)")]
// ^--- doesn't work in ef6 => 'The store type 'numeric(1,0)' could not be found in the SQL Server provider manifest'
// ^--- allegedly this works in EF core with Microsoft.EntityFrameworkCore.Relational nuget package installed
// ^--- https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types
// ^--- but I couldn't find anything similar for EF 6
public bool FB_YN {get; set;}
}
还有一个同样简单流畅的配置 class:
public class FOOBAR_FluentConfiguration : EntityTypeConfiguration<FOOBAR>
{
public FOOBAR_FluentConfiguration()
{
ToTable(tableName: "FOOBAR");
HasKey(x => x.FB_ID);
// Property(x => x.FB_YN).HasColumnType("numeric(1,0)");
// ^--- doesn't work in ef6 => 'The store type 'numeric(1,0)' could not be found in the SQL Server provider manifest'
// ^--- allegedly this works in EF core with Microsoft.EntityFrameworkCore.Relational nuget package installed
// ^--- but I couldn't find anything similar for EF 6
}
}
如评论中所述,任何试图说服 ef6 将 <bool>
映射到 table 中的 <numeric(1,0)>
列的尝试在运行时都惨遭失败。我也尝试通过 EF 约定达到预期的效果:
public sealed class MsSqlConventions : Convention
{
public MsSqlConventions()
{
Properties<bool>().Configure(p => p.HasColumnType("numeric(1,0)")); //fails
}
}
失败并显示以下消息:
The store type 'numeric(1,0)' could not be found in the SQL Server provider manifest
而这个:
public sealed class MsSqlConventions : Convention
{
public MsSqlConventions()
{
Properties<bool>().Configure(p => p.HasColumnType("numeric").HasPrecision(1, 0)); //fails
}
}
失败并显示以下消息:
Precision and scale have been configured for property 'FB_YN'. Precision and scale can only be configured for Decimal properties.
我还尝试玩弄(丰富)SQL 服务器提供商清单 a la:
DbProviderServices.GetProviderManifest();
但我还不能从中得出正面或反面。任何见解表示赞赏。
这是一种将 EF6 转为将数字 (1,0) 列作为 BIT 列处理的方法。这不是最好的东西,我只在底部显示的场景中测试过它,但就我的测试而言,它工作可靠。如果有人检测到事情没有按计划进行的极端情况,请随时发表评论,我会改进这种方法:
<!-- add this to your web.config / app.config -->
<entityFramework>
[...]
<interceptors>
<interceptor type="[Namespace.Path.To].MsSqlServerHotFixerCommandInterceptor, [Dll hosting the class]">
</interceptor>
</interceptors>
</entityFramework>
以及拦截器的实现:
// to future maintainers the reason we introduced this interceptor is that we couldnt find a way to persuade ef6 to map numeric(1,0) columns in sqlserver into bool columns
// to future maintainers we want this sort of select statement
// to future maintainers
// to future maintainers SELECT
// to future maintainers ...
// to future maintainers [Extent2].[FB_YN] AS [FB_YN],
// to future maintainers ...
// to future maintainers FROM ...
// to future maintainers
// to future maintainers to be converted into this sort of select statement
// to future maintainers
// to future maintainers SELECT
// to future maintainers ...
// to future maintainers CAST ([Extent2].[FB_YN] AS BIT) AS [FB_YN], -- the BIT cast ensures that the column will be mapped without trouble into bool properties
// to future maintainers ...
// to future maintainers FROM ...
// to future maintainers
// to future maintainers note0 the regex used assumes that all boolean columns end with the _yn postfix if your boolean columns follow a different naming scheme you
// to future maintainers note0 have to tweak the regular expression accordingly
// to future maintainers
// to future maintainers note1 notice that special care has been taken to ensure that we only tweak the columns that preceed the FROM part we dont want to affect anything
// to future maintainers note1 after the FROM part if the projects involved ever get upgraded to employ efcore then you can do away with this approach by simply following
// to future maintainers note1 the following small guide
// to future maintainers
// to future maintainers https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types
// to future maintainers
public sealed class MsSqlServerHotFixerCommandInterceptor : IDbCommandInterceptor
{
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
HotfixFaultySqlCommands(command, interceptionContext);
}
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
HotfixFaultySqlCommands(command, interceptionContext);
}
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
HotfixFaultySqlCommands(command, interceptionContext);
}
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 HotfixFaultySqlCommands<TResult>(IDbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (!command.CommandText.TrimStart().StartsWith("SELECT", ignoreCase: true, culture: CultureInfo.InvariantCulture))
return;
command.CommandText = BooleanColumnSpotter.Replace(command.CommandText, "CAST ( AS BIT)");
}
static private readonly Regex BooleanColumnSpotter = new Regex(@"((?<!\s+FROM\s+.*)([[][a-zA-Z0-9_]+?[]][.])?[[][a-zA-Z0-9_]+[]])(?=\s+AS\s+[[][a-zA-Z0-9_]+?_YN[]])", RegexOptions.IgnoreCase);
}
还有一些快速测试:
{
// -- DROP TABLE FOOBAR;
//
// CREATE TABLE FOOBAR (
// FB_ID NUMERIC(11,0) PRIMARY KEY,
// FB_YN NUMERIC(1,0) NOT NULL,
// FB2_YN NUMERIC(1,0) NULL
// );
//
// INSERT INTO FOOBAR (FB_ID, FB_YN, FB2_YN)
// VALUES (1, 0, 0);
//
// INSERT INTO FOOBAR (FB_ID, FB_YN, FB2_YN)
// VALUES (2, 1, 1);
//
// INSERT INTO FOOBAR (FB_ID, FB_YN, FB2_YN)
// VALUES (3, 1, null);
var mainDatabaseContext = new YourContext(...);
var test1 = mainDatabaseContext.Set<FOOBAR>().ToList();
var test2 = mainDatabaseContext.Set<FOOBAR>().Take(1).ToList();
var test3 = mainDatabaseContext.Set<FOOBAR>().Take(10).ToList();
var test4 = mainDatabaseContext.Set<FOOBAR>().FirstOrDefault();
var test5 = mainDatabaseContext.Set<FOOBAR>().OrderBy(x => x.FB_ID).ToList();
var test6 = mainDatabaseContext.Set<FOOBAR>().Take(10).Except(mainDatabaseContext.Set<FOOBAR>().Take(10)).SingleOrDefault();
var test7 = mainDatabaseContext.Set<FOOBAR>().Where(x => x.FB_ID == 1).ToList();
var test8 = mainDatabaseContext.Set<FOOBAR>().Where(x => x.FB_YN).ToList();
var test9 = (
from x in mainDatabaseContext.Set<FOOBAR>()
join y in mainDatabaseContext.Set<FOOBAR>() on x.FB_ID equals y.FB_ID into rightSide
from r in rightSide.DefaultIfEmpty()
select r
).ToList();
var test10 = (
from x in mainDatabaseContext.Set<FOOBAR>()
join y in mainDatabaseContext.Set<FOOBAR>() on new {x.FB_YN, FB_YN2 = x.FB2_YN} equals new {y.FB_YN, FB_YN2 = y.FB2_YN} into rightSide
from r in rightSide.DefaultIfEmpty()
select r
).ToList();
}
转发警告 #0:在不久的将来升级到 EF 核心不是一个选项。
转发警告 #1:我无法将列类型更改为 bit
,因为这可能会破坏使用相同数据库的遗留 VB 应用程序 我正在为其开发新应用程序.
前向警告 #2:我也不能使用 int
属性 ==> hidden bool 属性 方法,因为在针对 Oracle 时需要使用完全相同的模型数据库(在 Oracle decimal(1,0)
中确实毫无问题地映射到 bool
- 我需要在 SQL 服务器中实现同样的事情)。
假设我们有一个像这样的简单 table:
CREATE TABLE FOOBAR
(
FB_ID NUMERIC(11,0) PRIMARY KEY,
FB_YN NUMERIC(1,0) NOT NULL
);
INSERT INTO FOOBAR (FB_ID, FB_YN)
VALUES (1, 1), (2, 0);
一个简单的 poco class:
public class FOOBAR
{
public long FB_ID {get; set;}
// [Column(TypeName = "numeric(1,0)")]
// ^--- doesn't work in ef6 => 'The store type 'numeric(1,0)' could not be found in the SQL Server provider manifest'
// ^--- allegedly this works in EF core with Microsoft.EntityFrameworkCore.Relational nuget package installed
// ^--- https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types
// ^--- but I couldn't find anything similar for EF 6
public bool FB_YN {get; set;}
}
还有一个同样简单流畅的配置 class:
public class FOOBAR_FluentConfiguration : EntityTypeConfiguration<FOOBAR>
{
public FOOBAR_FluentConfiguration()
{
ToTable(tableName: "FOOBAR");
HasKey(x => x.FB_ID);
// Property(x => x.FB_YN).HasColumnType("numeric(1,0)");
// ^--- doesn't work in ef6 => 'The store type 'numeric(1,0)' could not be found in the SQL Server provider manifest'
// ^--- allegedly this works in EF core with Microsoft.EntityFrameworkCore.Relational nuget package installed
// ^--- but I couldn't find anything similar for EF 6
}
}
如评论中所述,任何试图说服 ef6 将 <bool>
映射到 table 中的 <numeric(1,0)>
列的尝试在运行时都惨遭失败。我也尝试通过 EF 约定达到预期的效果:
public sealed class MsSqlConventions : Convention
{
public MsSqlConventions()
{
Properties<bool>().Configure(p => p.HasColumnType("numeric(1,0)")); //fails
}
}
失败并显示以下消息:
The store type 'numeric(1,0)' could not be found in the SQL Server provider manifest
而这个:
public sealed class MsSqlConventions : Convention
{
public MsSqlConventions()
{
Properties<bool>().Configure(p => p.HasColumnType("numeric").HasPrecision(1, 0)); //fails
}
}
失败并显示以下消息:
Precision and scale have been configured for property 'FB_YN'. Precision and scale can only be configured for Decimal properties.
我还尝试玩弄(丰富)SQL 服务器提供商清单 a la:
DbProviderServices.GetProviderManifest();
但我还不能从中得出正面或反面。任何见解表示赞赏。
这是一种将 EF6 转为将数字 (1,0) 列作为 BIT 列处理的方法。这不是最好的东西,我只在底部显示的场景中测试过它,但就我的测试而言,它工作可靠。如果有人检测到事情没有按计划进行的极端情况,请随时发表评论,我会改进这种方法:
<!-- add this to your web.config / app.config -->
<entityFramework>
[...]
<interceptors>
<interceptor type="[Namespace.Path.To].MsSqlServerHotFixerCommandInterceptor, [Dll hosting the class]">
</interceptor>
</interceptors>
</entityFramework>
以及拦截器的实现:
// to future maintainers the reason we introduced this interceptor is that we couldnt find a way to persuade ef6 to map numeric(1,0) columns in sqlserver into bool columns
// to future maintainers we want this sort of select statement
// to future maintainers
// to future maintainers SELECT
// to future maintainers ...
// to future maintainers [Extent2].[FB_YN] AS [FB_YN],
// to future maintainers ...
// to future maintainers FROM ...
// to future maintainers
// to future maintainers to be converted into this sort of select statement
// to future maintainers
// to future maintainers SELECT
// to future maintainers ...
// to future maintainers CAST ([Extent2].[FB_YN] AS BIT) AS [FB_YN], -- the BIT cast ensures that the column will be mapped without trouble into bool properties
// to future maintainers ...
// to future maintainers FROM ...
// to future maintainers
// to future maintainers note0 the regex used assumes that all boolean columns end with the _yn postfix if your boolean columns follow a different naming scheme you
// to future maintainers note0 have to tweak the regular expression accordingly
// to future maintainers
// to future maintainers note1 notice that special care has been taken to ensure that we only tweak the columns that preceed the FROM part we dont want to affect anything
// to future maintainers note1 after the FROM part if the projects involved ever get upgraded to employ efcore then you can do away with this approach by simply following
// to future maintainers note1 the following small guide
// to future maintainers
// to future maintainers https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types
// to future maintainers
public sealed class MsSqlServerHotFixerCommandInterceptor : IDbCommandInterceptor
{
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
HotfixFaultySqlCommands(command, interceptionContext);
}
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
HotfixFaultySqlCommands(command, interceptionContext);
}
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
HotfixFaultySqlCommands(command, interceptionContext);
}
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 HotfixFaultySqlCommands<TResult>(IDbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (!command.CommandText.TrimStart().StartsWith("SELECT", ignoreCase: true, culture: CultureInfo.InvariantCulture))
return;
command.CommandText = BooleanColumnSpotter.Replace(command.CommandText, "CAST ( AS BIT)");
}
static private readonly Regex BooleanColumnSpotter = new Regex(@"((?<!\s+FROM\s+.*)([[][a-zA-Z0-9_]+?[]][.])?[[][a-zA-Z0-9_]+[]])(?=\s+AS\s+[[][a-zA-Z0-9_]+?_YN[]])", RegexOptions.IgnoreCase);
}
还有一些快速测试:
{
// -- DROP TABLE FOOBAR;
//
// CREATE TABLE FOOBAR (
// FB_ID NUMERIC(11,0) PRIMARY KEY,
// FB_YN NUMERIC(1,0) NOT NULL,
// FB2_YN NUMERIC(1,0) NULL
// );
//
// INSERT INTO FOOBAR (FB_ID, FB_YN, FB2_YN)
// VALUES (1, 0, 0);
//
// INSERT INTO FOOBAR (FB_ID, FB_YN, FB2_YN)
// VALUES (2, 1, 1);
//
// INSERT INTO FOOBAR (FB_ID, FB_YN, FB2_YN)
// VALUES (3, 1, null);
var mainDatabaseContext = new YourContext(...);
var test1 = mainDatabaseContext.Set<FOOBAR>().ToList();
var test2 = mainDatabaseContext.Set<FOOBAR>().Take(1).ToList();
var test3 = mainDatabaseContext.Set<FOOBAR>().Take(10).ToList();
var test4 = mainDatabaseContext.Set<FOOBAR>().FirstOrDefault();
var test5 = mainDatabaseContext.Set<FOOBAR>().OrderBy(x => x.FB_ID).ToList();
var test6 = mainDatabaseContext.Set<FOOBAR>().Take(10).Except(mainDatabaseContext.Set<FOOBAR>().Take(10)).SingleOrDefault();
var test7 = mainDatabaseContext.Set<FOOBAR>().Where(x => x.FB_ID == 1).ToList();
var test8 = mainDatabaseContext.Set<FOOBAR>().Where(x => x.FB_YN).ToList();
var test9 = (
from x in mainDatabaseContext.Set<FOOBAR>()
join y in mainDatabaseContext.Set<FOOBAR>() on x.FB_ID equals y.FB_ID into rightSide
from r in rightSide.DefaultIfEmpty()
select r
).ToList();
var test10 = (
from x in mainDatabaseContext.Set<FOOBAR>()
join y in mainDatabaseContext.Set<FOOBAR>() on new {x.FB_YN, FB_YN2 = x.FB2_YN} equals new {y.FB_YN, FB_YN2 = y.FB2_YN} into rightSide
from r in rightSide.DefaultIfEmpty()
select r
).ToList();
}