如何从实体框架代码优先方法自动增加 oracle 表?

Posted

技术标签:

【中文标题】如何从实体框架代码优先方法自动增加 oracle 表?【英文标题】:How to auto-increment oracle table from Entity Framework code-first approach? 【发布时间】:2020-11-24 16:46:29 【问题描述】:

我有下表,我刚刚添加了装饰器DatabaseGenerated,如下所示:

public class Reference

        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Key]
        public decimal ReferenceId  get; set; 
        public string AddressType  get; set; 
        public string RefferenceType  get; set; 
        public string RefferenceValue  get; set; 
        [ForeignKey("Shipment")]
        public decimal? TrackingNumber  get; set; 
        public Shipment Shipment  get; set; 

而我最新推送到 oracle 19c 数据库的向上迁移是:

public override void Up()

    DropPrimaryKey("SALOGSEARCH.References");
    AlterColumn("SALOGSEARCH.References", "ReferenceId", c => c.Decimal(nullable: false, precision: 20, scale: 0, identity: true));
    AddPrimaryKey("SALOGSEARCH.References", "ReferenceId");

然而,当我在添加后执行我的上下文保存时:

using (var context = new DbContext())

    context.Reference.Add(shipperReference);
    context.SaveChanges();

我遇到了异常

ORA-01400: 无法插入 NULL

我假设标签DatabaseGeneratedOption.Identity 会在Oracle 数据库中生成一个序列,并从next 或其他东西的默认值调用它。但事实并非如此。

我现在是否必须为每列手动创建触发器和序列?

如果是这样,那么如果我必须自己干预数据库,那么代码的意义何在。

我不确定这是否有任何影响,但我正在调整OnModelCreating,如下所示:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

    modelBuilder.HasDefaultSchema("SALOGSEARCH");

    modelBuilder.Properties<string>().Configure(s => s.HasMaxLength(400).HasColumnType("Varchar2"));
    modelBuilder.Properties<decimal>().Configure(s => s.HasPrecision(20, 0).HasColumnType("Number"));

任何从 c# 方面解决此问题的指针将不胜感激。

编辑1: 在this tutorial 之后,我开始意识到模型类(表)的 Id 需要是一个 int,在 oracle 术语中它是 number(10, 0) 以便自动创建所需的序列(作为列默认调用nextVal) 在数据库端。

然而,从向上迁移来看,似乎没有任何迹象表明这个序列的创建,所以它只是让我相信这个创建发生在我知识领域之外的更深的地方。

【问题讨论】:

标识列既不被序列也不被触发器处理。 【参考方案1】:

DatabaseGeneratedOption.Identity 需要一个序列或一个标识列。

从 12c 版本开始支持标识列,触发器和序列均未引用该列。它们由 Oracle 内部处理。如果出现错误

ORA-01400: 无法插入 NULL

这可能是因为您在没有明确命名列的情况下进行插入,或者因为您使用了错误的 IDENTITY 类型,或者因为您没有序列。

IDENTITY 列有这些选项

GENERATED ALWAYS:Oracle 总是为标识列生成一个值。尝试将值插入标识列会导致错误。 GENERATED BY DEFAULT:如果您不提供值,Oracle 会为标识列生成一个值。如果您提供一个值,Oracle 会将该值插入到标识列中。对于此选项,如果您将 NULL 值插入标识列,Oracle 将发出错误。 GENERATED BY DEFAULT ON NULL:如果您提供 NULL 值或根本不提供值,Oracle 会为标识列生成一个值。

例子

SQL> create table t ( c1 number generated  by default as identity ( start with 1 increment by 1 ) , c2 number ) ;

Table created.

SQL> insert into t ( c2 ) values ( 1 ) ;

1 row created.

SQL> select * from t ;

        C1         C2
---------- ----------
         1          1

SQL>  insert into t ( c1 , c2 ) values ( null , 1 ) ;
 insert into t ( c1 , c2 ) values ( null , 1 )
                                    *

第 1 行出现错误: ORA-01400: 无法将 NULL 插入 ("MY_SCHEMA"."T"."C1")

但是我可以进行插入并明确引用 IDENTITY

SQL>  insert into t ( c1, c2 ) values ( 2, 2 ) ;

1 row created.

SQL> select * from t ;

        C1         C2
---------- ----------
         1          1
         2          2

SQL> insert into t ( c2 ) values ( 3 ) ;

1 row created.

SQL> select * from t ;

        C1         C2
---------- ----------
         1          1
         2          2
         2          3

在你的情况下,我会使用GENERATED BY DEFAULT ON NULL:

SQL> create table t ( c1 number generated by default on null as identity ( start with 

1 increment by 1 ) , c2 number ) ;

Table created.

SQL>  insert into t values ( null , 1 ) ;

1 row created.

SQL> select * from t ;

        C1         C2
---------- ----------
         1          1

【讨论】:

我当然明白你写下的sql部分。但我似乎无法弄清楚我是否可以从我的模型类的 c# 属性甚至从向上迁移中执行 GENERATED BY DEFAULT ON NULL 命令。 我相信你做不到。 IDENTITY 列的处理方式因数据库提供程序而异。您可以尝试使用触发器/序列组合,但无论如何DatabaseGeneratedOption 都不会创建这些组合 但这不是一些非常狭窄的用例场景。这就像最常见的用例,我不知道如何使用实体框架中的代码优先方法对其进行调整。

以上是关于如何从实体框架代码优先方法自动增加 oracle 表?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用实体框架代码优先从数据库中删除所有相关实体

在实体框架数据库优先方法中,如何从存储过程返回多个结果集?

使用实体框架和代码优先方法创建数据库时,是不是可以从 SSMS 在数据库表上创建索引

实体框架代码优先 CTP5:如何定义非原始类型

如何在实体框架代码优先方法中使用表值函数?

使用带有代码优先的实体框架,没有自动增量