实体框架6码首先在SQL Server上:地图“布尔”到“数字(1,0)”,而不是“位”(Entit

2019-09-27 09:43发布

前方警戒#0:升级到EF核心是不是在不久的将来的一个选项。

前方警戒#1:我不能列类型更改为bit ,因为这可能会打破这种使用我开发了一个新的应用程序非常相同的数据库遗留VB应用程序。

前方警戒#2:我也可以不采用int属性==>隐藏布尔属性的方法,因为同样的模型需要针对Oracle数据库(Oracle中,当工作decimal(1,0)确实被映射到bool无问题 - 我需要在SQL Server中的同样的事情发生)。

假设我们有一个简单的表像这样的:

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类:

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;}
}

和同样简单流畅的配置类:

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><numeric(1,0)>在表列在运行时悲惨地失败。 我也试图通过实现EF公约预期的效果:

public sealed class MsSqlConventions : Convention
{
    public MsSqlConventions()
    {
        Properties<bool>().Configure(p => p.HasColumnType("numeric(1,0)")); //fails
    }
}

这种失败,出现以下消息:

存储类型“数值(1,0)”不能在SQL Server提供的清单中找到

虽然这一个:

public sealed class MsSqlConventions : Convention
{
    public MsSqlConventions()
    {
        Properties<bool>().Configure(p => p.HasColumnType("numeric").HasPrecision(1, 0)); //fails
    }
}

这种失败,出现以下消息:

精密和规模已配置财产“FB_YN”。 精度和比例只能配置为小数的性质。

我也试图玩具周围(丰富)SQL Server提供清单一拉:

DbProviderServices.GetProviderManifest();

但我不能做正面或反面出它(还)。 任何见解表示赞赏。

Answer 1:

这里有一个方法来武装捻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 ($1 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();
}


文章来源: Entity Framework 6 Code First on SQL Server: Map “bool” to “numeric(1,0)” instead of “bit”