Skip to Main Content

ODP.NET

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

TPT inheritance doesn't work in Oracle.EntityFrameworkCore with DB generated IDs

user-xzpxwNov 5 2023

Hello,

At the moment I am using the latest version of Oracle.EntityFrameworkCore 7.21.12 but this problem was introduced in this package since version 7 was released (everything worked before that).

I have 2 tables in Oracle, one "base" table with ID (which is generated by the database sequence) and NAME and one "derived" table with ID (FK to the base table) and ANOTHERNAME. Corresponding entities look like this

[Table("BASE")]
public class Base
{
    [DataMember, Column("ID"), Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; }

    [DataMember, Column("NAME"), Required]
    public string Name { get; set; }
}

[Table("DERIVED")]
public class Derived : Base
{
    [DataMember, Column("ANOTHERNAME"), Required]
    public string AnotherName { get; set; }
}

I have temporary Ids created at the client side for the entities, so EF model additionally configured to ignore it

public class BaseConfiguration : IEntityTypeConfiguration<Base>
{
    public void Configure(EntityTypeBuilder<Base> builder)
    {
        builder.Property(p => p.Id).Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
    }
}

After I add new instance of Derived entity to the context and try to save changes, I am getting the following error from Oracle: "ORA-01400: cannot insert NULL into (???)". The following SQL statements are actually generated and executed (I simplified them, leaving only meaningful part). EF correctly splits them into 2 separate batches. The first one

...
INSERT INTO "AAA"."BASE" ("NAME")
VALUES (:p3)
RETURNING "ID", INTO "lBASE_1"(1)."ID";
OPEN :cur0 FOR SELECT "lBASE"(1)."ID" FROM DUAL;
...

and the second one

INSERT INTO "AAA"."DERIVED" ("ANOTHERNAME")
VALUES (:p4)
RETURNING "ID" INTO "lDERIVED_0"(1)."ID";
OPEN :cur0 FOR SELECT "lDERIVED_0"(1)."ID" FROM DUAL;

As you may see the actual issue is with the second batch. Instead of inserting ID from the base table to the SQL of the derived table, it tries to return it as well, thinking it should be generated by the database.

After some debugging of EF Core code I can see the following in ModificationCommand.cs

...
void HandleColumn(IColumnMappingBase columnMapping)
{
    var property = columnMapping.Property;  // <---- actually prop of the base class here
    var column = columnMapping.Column;
    var storedProcedureParameter = columnMapping is IStoredProcedureParameterMapping parameterMapping
        ? parameterMapping.Parameter
        : null;
    var isKey = property.IsPrimaryKey();
    var isCondition = !adding
        && (isKey
            || storedProcedureParameter is { ForOriginalValue: true }  
            || (property.IsConcurrencyToken && storedProcedureParameter is null));

    // Store-generated properties generally need to be read back (unless we're deleting).
    // One exception is if the property is mapped to a non-output parameter.
    var readValue = state != EntityState.Deleted
        && ColumnModification.IsStoreGenerated(entry, property) // <----  store generated is true for the base prop
        && (storedProcedureParameter is null || storedProcedureParameter.Direction.HasFlag(ParameterDirection.Output));
...

This method initialize command parameter and is called for all column mappings in the command. And column mappings for derived class in additional to the explicit column "ANOTHERNAME" contain implicit column "ID" which is the key from the base class (this is correct). EF takes "property" from this mapping and this property is actually the property of the base class with all related configurations (doesn't matter by attributes or in code). So obviously "readValue" will be true because ColumnModification.IsStoreGenerated returns true for the base ID property. And when readValue is true, Oracle doesn't put this parameter to the command's parameters list but instead "RETURNING" statement is generated for it even for the derived class.

I've reported this issue to the EF Core github at first, but they told this is an Oracle issue. I actually tried to create a simple project with EF Core + SQLite provider and it working there, so this really looks like an Oracle issue.

I wasn't able to find other ways to report an issue with Oracle.EntityFrameworkCore except this forum. If there is more correct place to do this please let me know.

Comments
Post Details
Added on Nov 5 2023
1 comment
397 views