oracle / dotnet-db-samples

.NET code samples for Oracle database developers #OracleDotNet
http://otn.oracle.com/dotnet
MIT License
408 stars 190 forks source link

INNER JOIN instead of LEFT JOIN #318

Closed jamezamm closed 8 months ago

jamezamm commented 8 months ago

Oracle - 19c Enterprise Edition Release 19.0.0.0.0 - Production Oracle.EntityFrameworkCore - 7.21.12 Oracle.ManagedDataAccess.Core - 3.21.120 EFCore - 7.0.12

Test DbContext with Entities

[PrimaryKey(nameof(Id))]
[Table("LOGIN_TOKENS", Schema = "WEB_ADMIN")]
public class TestToken
{
    [Column("ID")] public string Id { get; set; }
    [Column("DESCRIPTION")] public string? Description { get; set; }
    [Column("PERSON_ID")] public string? PersonId { get; set; }

    [ForeignKey(nameof(PersonId))]
    public virtual TestPerson Person { get; set; }
}

[PrimaryKey(nameof(Id))]
[Table("PERSON_DETAILS", Schema = "GATESEC_DBAU")]
public class TestPerson
{
    [Column("ID_NO")] public string Id { get; set; }
    [Column("PNAME")] public string Name { get; set; }
    [Column("SURNAME")] public string Surname { get; set; }
}

public class TestDbContext : DbContext
{
    public TestDbContext()
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.LogTo(message => Debug.WriteLine(message), Microsoft.Extensions.Logging.LogLevel.Information);

        StringBuilder sb = new StringBuilder();
        sb.Append($"Persist Security Info=True;");
        sb.Append($"User ID=USERNAME;");
        sb.Append($"Password=PASSWORD;");
        sb.Append($"Data Source=(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = IP)(PORT = PORT)) ) (CONNECT_DATA =(SERVICE_NAME = SERVICE)))");

        options.UseOracle(sb.ToString());
    }

    public DbSet<TestToken>? Tokens { get; set; }
    public DbSet<TestPerson>? Persons { get; set; }

}

The query

using (TestDbContext db = new TestDbContext())
{
    var list = db.Tokens.Include(x => x.Person).ToList();
}

The generated sql

SELECT "t"."ID", "t"."DESCRIPTION", "t"."PERSON_ID", "p"."ID_NO", "p"."PNAME", "p"."SURNAME"
FROM "WEB_ADMIN"."LOGIN_TOKENS" "t"
INNER JOIN "GATESEC_DBAU"."PERSON_DETAILS" "p" ON "l"."PERSON_ID" = "p"."ID_NO"

The sql is generated with an INNER JOIN instead of a LEFT JOIN, even tough TestToken.PersonId foreign key is nullable (string?).

Am I missing something or is this an issue?

alexkeh commented 8 months ago

Does the PostgreSQL or SQL Server EF Core providers create LEFT JOIN SQL with the same model and LINQ? Was the LEFT JOIN used in earlier versions of Oracle EF Core or EF Core? If so, which component version was it?

jamezamm commented 8 months ago

Was informed by the EFCore team that this has been solved in NET 8, thus I will close this issue.