Oracle CHAR 数据类型不适用于实体框架

phi*_*eed 5 .net c# oracle entity-framework

我在使用针对 CHAR 列的 WHERE 子句从 Oracle 数据库返回数据时遇到问题。

我提供了以下步骤,应该可以重新创建问题:

数据库设置

运行以下 SQL 创建数据库表并插入模拟数据:

CREATE TABLE ORDERS (ORDER_NUMBER CHAR(10 BYTE));
INSERT INTO ORDERS VALUES ('123456');
SELECT REPLACE(ORDER_NUMBER, ' ', '#') as ORDER_NUMBER from ORDERS

ORDER_NUMBER
123456####

正如您所看到的,存储在表中的值用空格填充到 10 个字符(用“#”替换以使其明显)。

实体框架设置

public class Order
{
    public string OrderNumber { get; set; }
}

public class OrderMap : EntityTypeConfiguration<Order>
{
    public OrderMap()
    {
        // Primary Key
        HasKey(t => t.OrderNumber);

        Property(t => t.OrderNumber)
            .HasColumnName("ORDER_NUMBER")
            .IsRequired()
            .IsFixedLength()
            .HasMaxLength(10)
            .HasColumnType("CHAR")
            .IsUnicode(false);
    }
}

public class OrdersContext()
{
    static OrdersContext()
    {
        Database.SetInitializer<OrderContext>(null);
    }

    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)  
    {
        modelBuilder.Configurations.Add(new OrderMap());
    }
}
Run Code Online (Sandbox Code Playgroud)

问题

class Program
{
    static void Main(string[] args)
    {           
        using (var context = new OrdersContext())
        {
            var order1 = context.Orders
                                .FirstOrDefault(o => o.OrderNumber == "123456");

            // This works, the record is found in the database and I did not have to pad the filter sting to 10 characters
            Debug.WriteLine("OrderNumber:" + order1.WorksOrder);

            var orderFilter = "123456";
            var order2 = context.Orders
                                .FirstOrDefault(o => o.WorksOrder == orderFilter);

            // This fails. Using a variable to specify the filter does not work. No record is found.
            Debug.WriteLine("OrderNumber:" + order2.WorksOrder);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,问题是当我想使用变量(orderFilter)时,该语句不返回记录。我可以通过orderFilter如下填充来完成这项工作,但我认为我不必这样做:

var orderFilter = "123456".PadRight(10);
var order2 = context.Orders.FirstOrDefault(o => o.WorksOrder == orderFilter);

// This now works
Debug.WriteLine("OrderNumber:" + order2.WorksOrder);
Run Code Online (Sandbox Code Playgroud)

使用绑定变量时生成的 SQL 似乎未在绑定变量上设置正确的数据类型。如果我启用跟踪,我们会看到以下 SQL:

已于 22/04/2015 12:15:12 +01:00 打开连接

从“ORDERS”“Extent1”中选择“Extent1”。“ORDER_NUMBER”作为“ORDER_NUMBER”,其中(“Extent1”。“ORDER_NUMBER”=:p__linq__0)AND(ROWNUM <=(1))

-- p__linq__0: '123456 ' (类型 = 对象)

-- 于 22/04/2015 12:15:12 +01:00 执行 -- 在 13 毫秒内完成,结果:OracleDataReader

我也尝试过使用DbFunctions.AsNonUnicode(orderFilter)但这似乎效果不大。

谁能解释一下这是怎么回事?如何在不填充过滤器或修剪列数据的情况下过滤 CHAR 数据类型?

注意
我无法更改数据库中列的数据类型。

这个问题记录在多个地方,但没有明确的答案:

所有事物的版本号:

  • 实体框架6.1.3
  • .Net框架4.5.1
  • Oracle.ManagedDataAccess 4.121.2.0 ODAC 12c 第 3 版
  • Oracle.ManagedDataAccess.EntityFramework 6.121.2.0
  • Oracle数据库是11.2.0.3.0

Ian*_*ter 4

我可以解释发生了什么事。解决方案是更改 CHAR 列(我注意到您说不能),否则在比较时要注意其中的微妙之处。

\n\n

Tom Kyte 在他的精彩著作“Expert Oracle Database Architecture”中关于 CHAR 数据类型的部分是这个答案的来源。以下内容基于第二版第 499 - 502 页。

\n\n

简短回答

\n\n

字符文字会得到提升,而可变长度绑定变量则不会。

\n\n

解释

\n\n

(以下所有内容均使用 SQLPlus 针对 12.1.0.2 数据库运行(但没有显示任何内容是 12c 所独有的)

\n\n

使用 CHAR 和 VARCHAR2 列创建并填充表:

\n\n
CREATE TABLE ORDERS (ORDER_NUMBER_CHAR       CHAR(10 BYTE),\n                     ORDER_NUMBER_VARCHAR2   VARCHAR2(10 BYTE));\n\nINSERT INTO ORDERS(ORDER_NUMBER_CHAR, \n                   ORDER_NUMBER_VARCHAR2)\nVALUES (\'123456\',\n        \'123456\');\n
Run Code Online (Sandbox Code Playgroud)\n\n

在 WHERE 子句中使用 VARCHAR2 列的第一个查询中,它按预期工作并返回一行。

\n\n
SELECT *\n  FROM orders\n WHERE order_number_varchar2 = \'123456\';\n
Run Code Online (Sandbox Code Playgroud)\n\n

在下一个查询中,WHERE 子句使用 CHAR 列。此查询返回一行,这意味着发生了隐式转换,CHAR(6) 文字被提升为 CHAR(10)

\n\n
SELECT *\n  FROM orders\n WHERE order_number_char = \'123456\';\n
Run Code Online (Sandbox Code Playgroud)\n\n

一定发生了隐式提升,因为字符串的长度不同,\n此查询显示了这一点,该查询不返回任何行

\n\n
SELECT *\n  FROM orders\n WHERE order_number_char = order_number_varchar2;\n
Run Code Online (Sandbox Code Playgroud)\n\n

下一个示例显示 VARCHAR2 绑定变量的提升方式与字符文字不同,因此该查询不返回任何行

\n\n
variable vc2 VARCHAR2(10 BYTE);\nexec :vc2 := \'123456\'; \n\nSELECT *\n  FROM orders\n WHERE order_number_char = :vc2;\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果使用正确的 CHAR 绑定变量,则将找到该记录,最终查询将按预期返回该行。

\n\n
variable the_char CHAR(10 BYTE);\nexec :the_char := \'123456\'; \n\nSELECT *\n  FROM orders\n WHERE order_number_char = :the_char;\n
Run Code Online (Sandbox Code Playgroud)\n\n

您还应该意识到,如果 CHAR 列的大小发生变化,应用程序将会受到影响,即,如果它增加到 20,则 PadRight 方法将需要进行相应的更改。

\n\n

最后,我认为还值得引用 Tom 对 CHAR 数据类型的完整总结。

\n\n
\n

正是由于这些原因\xe2\x80\x94固定宽度存储往往会使\n表和相关索引比正常情况大得多,再加上\n绑定变量问题\xe2\x80\x94我避免了CHAR输入所有\n情况。在单字符字段的情况下,我什至无法对其进行论证,因为在这种情况下,它实际上没有实质性差异。VARCHAR2(1) 和 CHAR(1) 在所有方面都是相同的。在这种情况下,没有令人信服的理由使用 CHAR 类型,并且为了避免任何混淆,我 \xe2\x80\x9c 只是说不,\xe2\x80\x9d 即使对于\n CHAR(1) 字段也是如此。

\n
\n