Bug #72526 Fails to generate correct SQL for one-to-one relationships in Entity Framework 6
Submitted: 3 May 2014 17:47 Modified: 2 Feb 2015 7:53
Reporter: Emil Lenngren Email Updates:
Status: No Feedback Impact on me:
None 
Category:Connector / NET Severity:S2 (Serious)
Version:6.8.3 OS:Any
Assigned to: Assigned Account CPU Architecture:Any

[3 May 2014 17:47] Emil Lenngren
Description:
I have three tables like this:

CREATE TABLE `eftest`.`left` (
  `ID` INT NOT NULL AUTO_INCREMENT,
  `Item` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`ID`))

CREATE  TABLE `eftest`.`middle` (
  `ID` INT NOT NULL AUTO_INCREMENT ,
  `LeftId` INT NULL ,
  PRIMARY KEY (`ID`) ,
  INDEX `FK_MIDDLE_LEFT_idx` (`LeftId` ASC) ,
  CONSTRAINT `FK_MIDDLE_LEFT`
    FOREIGN KEY (`LeftId` )
    REFERENCES `eftest`.`left` (`ID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION);

CREATE  TABLE `eftest`.`right` (
  `ID` INT NOT NULL AUTO_INCREMENT ,
  `MiddleId` INT NOT NULL ,
  PRIMARY KEY (`ID`) ,
  INDEX `FK_RIGHT_MIDDLE_idx` (`MiddleId` ASC) ,
  CONSTRAINT `FK_RIGHT_MIDDLE`
    FOREIGN KEY (`MiddleId` )
    REFERENCES `eftest`.`middle` (`ID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION
);

So a `right` points to a `middle`, and a `middle` points to a `left`.
In my setup, a `left` is always referenced by exactly a single `middle`, so I have a one-to-one relationship here.

I import these tables to Entity Framework Model Designer in Visual Studio 2013 by "Update Model from Database". I have chosen "Pluralize or singuralize generated object names" but not "Include foreign key columns in the model" (don't know if this really matters). 
I changed "middles" navigation property on "left" from "* (Many)" to "1 (One)". I also renamed the navigation property to "middle".

This doesn't work:
using (var db = new eftestEntities()) {
    db.rights.Where(r => r.ID > 0).Select(r => new { lid = r.middle.left.ID, r.middle.left.Item }).ToArray();
}

This query is generated in EF 6.1 together with MySql Connector .Net 6.8.3:
SELECT
`Apply1`.`ID`, 
`Apply1`.`LeftId`, 
`Apply1`.`Item`
FROM (SELECT
`Filter1`.`ID`, 
`Filter1`.`MiddleId`, 
`Filter1`.`ID1`, 
`Filter1`.`LeftId`, 
(SELECT
`Extent3`.`ID`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent4`.`LeftId` IS NOT NULL) AND (`Extent3`.`ID` = `Extent4`.`LeftId`) INNER JOIN `middle` AS `Extent5` ON `Extent5`.`ID` = `Extent4`.`ID`
 WHERE `Extent1`.`MiddleId` = `Extent5`.`ID`) AS `ID2`, 
(SELECT
`Extent3`.`Item`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent4`.`LeftId` IS NOT NULL) AND (`Extent3`.`ID` = `Extent4`.`LeftId`) INNER JOIN `middle` AS `Extent5` ON `Extent5`.`ID` = `Extent4`.`ID`
 WHERE `Extent1`.`MiddleId` = `Extent5`.`ID`) AS `Item`, 
(SELECT
`Extent4`.`ID` AS `ID1`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent4`.`LeftId` IS NOT NULL) AND (`Extent3`.`ID` = `Extent4`.`LeftId`) INNER JOIN `middle` AS `Extent5` ON `Extent5`.`ID` = `Extent4`.`ID`
 WHERE `Extent1`.`MiddleId` = `Extent5`.`ID`) AS `ID3`, 
(SELECT
`Extent4`.`LeftId`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent4`.`LeftId` IS NOT NULL) AND (`Extent3`.`ID` = `Extent4`.`LeftId`) INNER JOIN `middle` AS `Extent5` ON `Extent5`.`ID` = `Extent4`.`ID`
 WHERE `Extent1`.`MiddleId` = `Extent5`.`ID`) AS `LEFTID1`, 
(SELECT
`Extent5`.`ID` AS `ID2`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent4`.`LeftId` IS NOT NULL) AND (`Extent3`.`ID` = `Extent4`.`LeftId`) INNER JOIN `middle` AS `Extent5` ON `Extent5`.`ID` = `Extent4`.`ID`
 WHERE `Extent1`.`MiddleId` = `Extent5`.`ID`) AS `ID4`, 
(SELECT
`Extent5`.`LeftId` AS `LEFTID1`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent4`.`LeftId` IS NOT NULL) AND (`Extent3`.`ID` = `Extent4`.`LeftId`) INNER JOIN `middle` AS `Extent5` ON `Extent5`.`ID` = `Extent4`.`ID`
 WHERE `Extent1`.`MiddleId` = `Extent5`.`ID`) AS `LEFTID2`
FROM (SELECT
`Extent1`.`ID`, 
`Extent1`.`MiddleId`, 
`Extent2`.`ID` AS `ID1`, 
`Extent2`.`LeftId`
FROM `right` AS `Extent1` INNER JOIN `middle` AS `Extent2` ON `Extent1`.`MiddleId` = `Extent2`.`ID`
 WHERE `Extent1`.`ID` > 0) AS `Filter1`) AS `Apply1`

with the error message: "Unknown column 'Extent1.MiddleId' in 'where clause'"
The error occurs because `Extent1` is declared inside a subquery, and can thus not be used outside that subquery. It doesn't matter if I use ObjectContext or DbContext. It seems stupid to have all those subqueries that fetches one column each, and most of them aren't even used later.

Note that if I omit ".Where(r => r.ID > 0)", it crashes with this error instead when trying to build the query:
An unhandled exception of type 'System.NullReferenceException' occurred in EntityFramework.dll
   at MySql.Data.Entity.SelectStatement.AddDefaultColumns(Scope scope)
   at MySql.Data.Entity.SelectStatement.Wrap(Scope scope)
   at MySql.Data.Entity.SelectGenerator.Visit(DbApplyExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DbApplyExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
   at MySql.Data.Entity.SqlGenerator.VisitInputExpression(DbExpression e, String name, TypeUsage type)
   at MySql.Data.Entity.SelectGenerator.VisitInputExpressionEnsureSelect(DbExpression e, String name, TypeUsage type)
   at MySql.Data.Entity.SelectGenerator.Visit(DbProjectExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DbProjectExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
   at MySql.Data.Entity.SelectGenerator.GenerateSQL(DbCommandTree tree)
   at MySql.Data.MySqlClient.MySqlProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree)
   at System.Data.Entity.Core.Common.DbProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree, DbInterceptionContext interceptionContext)
...

EF 5 does a much better job. It creates this query:
SELECT
`Extent1`.`ID`, 
`Extent2`.`LeftId`, 
`Join2`.`Item`
FROM `right` AS `Extent1` INNER JOIN `middle` AS `Extent2` ON `Extent1`.`MiddleId` = `Extent2`.`ID` LEFT OUTER JOIN (SELECT
`Extent3`.`ID`, 
`Extent3`.`Item`, 
`Extent4`.`ID` AS `ID1`, 
`Extent4`.`LeftId`
FROM `left` AS `Extent3` LEFT OUTER JOIN `middle` AS `Extent4` ON (`Extent3`.`ID` = `Extent4`.`LeftId`) AND (`Extent4`.`LeftId` IS NOT NULL)) AS `Join2` ON `Join2`.`ID1` = `Extent1`.`MiddleId`
 WHERE `Extent1`.`ID` > 0

I have also seen some other strange results when upgrading to EF 6 when using "Include".
I have a lot of rows in the datatbase, and run a query like db.rights.Include("middle.left").Where(...).ToArray().
When I now try to fetch the objects' middle.left, for about the first 10 or 20 rows, I get the correct `left` objects, but for the rest of the rows, middle.left is null when it actually isn't null. If I omit the Include, it works as expected, but takes of course much longer to execute. Here the generated SQL seems to be correct but the results aren't read correctly into the entity objects. That's very annoying since you don't discover the bug until you have enough data. It's harder to create a test case on this...

How to repeat:
Follow steps above
[27 Nov 2014 9:41] Emil Lenngren
I now realize that EF6 generates a much worse query tree than the previous of EF versions did, I can't blame MySQL for that.
I also noticed that generally generating SQL for query trees containing an DbApplyExpression is hard for MySQL, since MySQL doesn't support Apply/Lateral joins...
[2 Jan 2015 7:53] Chiranjeevi Battula
Hello Emil Lenngren,

Thank you for the bug report.
I tried to reproduce the issue at my end using Visual Studio 2013 (C#.Net), MySQL Connector/Net 6.9.5 and EF Version : MySql.Data.Entity.EF6 (6.9.5.0) but Couldn't trace out any issue.
please try in latest version.

otuput:

SELECT
`Extent1`.`ID`, 
`Extent2`.`LeftId`, 
`Extent3`.`Item`
FROM `right` AS `Extent1` INNER JOIN `middle` AS `Extent2` ON `Extent1`.`MiddleId` = `Extent2`.`ID` LEFT OUTER JOIN `left` AS `Extent3` ON `Extent2`.`LeftId` = `Extent3`.`ID`
 WHERE `Extent1`.`ID` > 0

Thanks,
Chiranjeevi.
[3 Feb 2015 1:00] Bugs System
No feedback was provided for this bug for over a month, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".