Bug #116028 EntityFramework SQL generation of TPT pseudo discriminator column incorrect
Submitted: 6 Sep 12:15 Modified: 30 Sep 19:40
Reporter: Kristoffer Sjöberg (OCA) Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / NET Severity:S2 (Serious)
Version:8.x, 9.x OS:Any
Assigned to: CPU Architecture:Any

[6 Sep 12:15] Kristoffer Sjöberg
Description:
When using Table-Per-Type with a base class and multiple implementations, when this is queried via a join, the generated column C1 from each join to the specific tables clash, causing it to be aliased.

The Discriminator column is generated via a CASE-WHEN construction, but the condition for the base class is incorrectly generated:
CASE WHEN ((NOT ((`Join2`.`C1` = 1) AND (`Join2`.`C1` IS NOT NULL))) AND (NOT ((`Join2`.`C1` = 1) AND (`Join2`.`C1` IS NOT NULL)))) THEN (@gp3)  WHEN ((`Join2`.`C1` = 1) AND (`Join2`.`C1` IS NOT NULL)) THEN (@gp4)  ELSE (@gp5) END AS `C1`

The condition for @gp3 contains "(NOT ((`Join2`.`C1` = 1) AND (`Join2`.`C1` IS NOT NULL)))" twice, and the ELSE clause returning @gp5 can never be reached.

This has the consequence that the materialized objects will be of the wrong type, typically instance of the base class instead of the subclass. It may also result in an exception (depending on the contents already in the DbContext):

System.NotSupportedException : All objects in the EntitySet 'VehicleDbContext4.Vehicles' must have unique primary keys. However, an instance of type 'MySql.Data.EntityFramework.CodeFirst.Tests.Vehicle4' and an instance of type 'MySql.Data.EntityFramework.CodeFirst.Tests.Car4' both have the same primary key value. 

How to repeat:
Defining a model/DbContext like this:
```public class Car4 : Vehicle4
{
  public string CarProperty { get; set; }
}

public class Bike4 : Vehicle4
{
  public string BikeProperty { get; set; }
}
public class Manufacturer4
{
  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  public Guid ManufacturerId { get; set; }
  public string Name { get; set; }
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  public Guid GroupIdentifier { get; set; }
  public virtual ICollection<Vehicle4> Vehicles { get; set; }
}
public class Vehicle4
{
  public int Id { get; set; }
  public int Year { get; set; }

  [MaxLength(1024)]
  public string Name { get; set; }
  public Guid ManufacturerId { get; set; }
  [ForeignKey(nameof(ManufacturerId))]
  public virtual Manufacturer4 Manufacturer { get; set; }
}
[DbConfigurationType(typeof(MySqlEFConfiguration))]
public class VehicleDbContext4 : DbContext
{
  public DbSet<Vehicle4> Vehicles { get; set; }
  public DbSet<Manufacturer4> Manufacturers { get; set; }

  public VehicleDbContext4() : base(CodeFirstFixture.GetEFConnectionString<VehicleDbContext4>())
  {
    Database.SetInitializer<VehicleDbContext4>(new VehicleDBInitializer4());

  }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Entity<Vehicle4>().ToTable("Vehicles");
    modelBuilder.Entity<Car4>().ToTable("Cars");
    modelBuilder.Entity<Bike4>().ToTable("Bikes");
  }
}
public class VehicleDBInitializer4 : DropCreateDatabaseReallyAlways<VehicleDbContext4>
{
}
```
and running this code

```
[Test]
public void BugXXXXX_Test1()
{
#if DEBUG
  Debug.WriteLine(new StackTrace().GetFrame(0).GetMethod().Name);
#endif
  List<Vehicle4> vehicles;
  using (VehicleDbContext4 context = new VehicleDbContext4())
  {
    context.Database.Delete();
    context.Database.Initialize(true);
    var manuf = context.Manufacturers.Add(new Manufacturer4 { Name = "ACME" });
    context.Vehicles.Add(new Car4 { Id = 1, Name = "Mustang", Year = 2012, CarProperty = "Car", Manufacturer = manuf });
    context.Vehicles.Add(new Bike4 { Id = 101, Name = "Mountain", Year = 2011, BikeProperty = "Bike", Manufacturer = manuf });
    context.SaveChanges();

    // EXCEPTION OCCURS HERE:
    vehicles = context.Manufacturers.SelectMany(v => v.Vehicles).ToList();

    int records = -1;
    using (MySqlConnection conn = new MySqlConnection(context.Database.Connection.ConnectionString))
    {
      conn.Open();
      MySqlCommand cmd = new MySqlCommand("SELECT COUNT(*) FROM Vehicles", conn);
      records = Convert.ToInt32(cmd.ExecuteScalar());
    }

    Assert.AreEqual(context.Vehicles.Count(), records);
  }
}```

will result in this exception:
```System.NotSupportedException : All objects in the EntitySet 'VehicleDbContext4.Vehicles' must have unique primary keys. However, an instance of type 'MySql.Data.EntityFramework.CodeFirst.Tests.Vehicle4' and an instance of type 'MySql.Data.EntityFramework.CodeFirst.Tests.Car4' both have the same primary key value. ```

Suggested fix:
Applying the patch
```diff --git a/EntityFramework/src/Statements/SelectStatement.cs b/EntityFramework/src/Statements/SelectStatement.cs
index 96d8ab4e..74e7b2fe 100644
--- a/EntityFramework/src/Statements/SelectStatement.cs
+++ b/EntityFramework/src/Statements/SelectStatement.cs
@@ -232,7 +232,7 @@ namespace MySql.Data.EntityFramework
           if (generator.GetTopOp() == OpType.Join)
           {
             newColumn.ColumnAlias = cf.ColumnAlias;
-            newColumn.PushInput(cf.ColumnName);
+            newColumn.PushInput(cf.ActualColumnName);
             if (cf.TableName != null)
               newColumn.PushInput(cf.TableName);
           }
--```

seems to address the issue.
[6 Sep 12:40] MySQL Verification Team
Hello Kristoffer,

Thank you for the report and contribution.

regards,
Umesh
[6 Sep 15:43] OCA Admin
Contribution submitted via Github - Use ActualColumnName for joins 
(*) Contribution by Kristoffer Sjöberg (Github ksjoberg, mysql-connector-net/pull/71#issuecomment-2334259501): I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it.

Contribution: git_patch_2057927174.txt (text/plain), 6.02 KiB.

[7 Sep 14:42] Kristoffer Sjöberg
8.x series as well as 9.x series are affected by this issue.
[30 Sep 19:40] Philip Olson
Posted by developer:
 
Fixed as of the upcoming MySQL Connector/NET 9.1.0 release, and here's the proposed changelog entry from the documentation team:

Changed EntityFramework to use the actual column name in JOIN statements
to prevent clashes.

Our thanks to Kristoffer Sjöberg for the contribution.

Thank you for the bug report.