Skip to content

Commit

Permalink
Fix #6091 - Left Outer Join Results Difference Between EF Core 1.0 an…
Browse files Browse the repository at this point in the history
…d EF 6.x

Turns out that we have fixed this indirectly (albeit inefficiently) via #5953d9 and #e514b49.

I have prototyped an efficient solution that uses ROW_NUMBER for group delineation. Created #6441 to
track the improvement.
  • Loading branch information
anpete committed Aug 30, 2016
1 parent cd0bc8b commit dabdda9
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ public virtual SelectExpression TryGetQuery([NotNull] IQuerySource querySource)
SelectExpression selectExpression;
return QueriesBySource.TryGetValue(querySource, out selectExpression)
? selectExpression
: QueriesBySource.Values.SingleOrDefault(se => se.HandlesQuerySource(querySource));
: QueriesBySource.Values.LastOrDefault(se => se.HandlesQuerySource(querySource));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4818,6 +4818,51 @@ join o in os on c.CustomerID equals o.CustomerID into orders
from o in orders
select new { c, o });
}

[ConditionalFact]
public virtual void GroupJoin_outer_projection()
{
AssertQuery<Customer, Order>((cs, os) =>
cs.GroupJoin(os, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.City, o }),
asserter: (l2oResults, efResults) => { Assert.Equal(l2oResults.Count, efResults.Count); });
}

[ConditionalFact]
public virtual void GroupJoin_outer_projection2()
{
AssertQuery<Customer, Order>((cs, os) =>
cs.GroupJoin(os, c => c.CustomerID, o => o.CustomerID, (c, g) => new { c.City, g = g.Select(o => o.CustomerID) }),
asserter: (l2oResults, efResults) => { Assert.Equal(l2oResults.Count, efResults.Count); });
}

[ConditionalFact]
public virtual void GroupJoin_outer_projection_reverse()
{
AssertQuery<Customer, Order>((cs, os) =>
os.GroupJoin(cs, o => o.CustomerID, c => c.CustomerID, (o, c) => new { o.CustomerID, c }),
asserter: (l2oResults, efResults) => { Assert.Equal(l2oResults.Count, efResults.Count); });
}

[ConditionalFact]
public virtual void GroupJoin_outer_projection_reverse2()
{
AssertQuery<Customer, Order>((cs, os) =>
os.GroupJoin(cs, o => o.CustomerID, c => c.CustomerID, (o, g) => new { o.CustomerID, g = g.Select(c => c.City) }),
asserter: (l2oResults, efResults) => { Assert.Equal(l2oResults.Count, efResults.Count); });
}

[ConditionalFact]
public virtual void GroupJoin_subquery_projection_outer_mixed()
{
AssertQuery<Customer, Order>((cs, os) =>
from c in cs
from o0 in os.Take(1)
join o1 in os on c.CustomerID equals o1.CustomerID into orders
from o2 in orders
select new { A = c.CustomerID, B = o0.CustomerID, C = o2.CustomerID },
asserter:
(l2oResults, efResults) => { Assert.Equal(l2oResults.Count, efResults.Count); });
}

[ConditionalFact]
public virtual void GroupJoin_DefaultIfEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;

// ReSharper disable ClassNeverInstantiated.Local
// ReSharper disable AccessToDisposedClosure
Expand All @@ -23,6 +24,106 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests
{
public class QueryBugsTest : IClassFixture<SqlServerFixture>
{
private readonly SqlServerFixture _fixture;

public QueryBugsTest(SqlServerFixture fixture, ITestOutputHelper testOutputHelper)
{
_fixture = fixture;

//TestSqlLoggerFactory.CaptureOutput(testOutputHelper);
}

[Fact]
public void Left_outer_join_bug_6091()
{
using (var testStore = SqlServerTestStore.CreateScratch())
{
testStore.ExecuteNonQuery(@"
CREATE TABLE [dbo].[Customers](
[CustomerID] [int] NOT NULL PRIMARY KEY,
[CustomerName] [varchar](120) NULL,
[PostcodeID] [int] NULL);
CREATE TABLE [dbo].[Postcodes](
[PostcodeID] [int] NOT NULL PRIMARY KEY,
[PostcodeValue] [varchar](100) NOT NULL,
[TownName] [varchar](255) NOT NULL);
INSERT [dbo].[Customers] ([CustomerID], [CustomerName], [PostcodeID]) VALUES (1, N'Sam Tippet', 5);
INSERT [dbo].[Customers] ([CustomerID], [CustomerName], [PostcodeID]) VALUES (2, N'William Greig', 2);
INSERT [dbo].[Customers] ([CustomerID], [CustomerName], [PostcodeID]) VALUES (3, N'Steve Jones', 3);
INSERT [dbo].[Customers] ([CustomerID], [CustomerName], [PostcodeID]) VALUES (4, N'Jim Warren', NULL);
INSERT [dbo].[Customers] ([CustomerID], [CustomerName], [PostcodeID]) VALUES (5, N'Andrew Smith', 5);
INSERT [dbo].[Postcodes] ([PostcodeID], [PostcodeValue], [TownName]) VALUES (2, N'1000', N'Town 1');
INSERT [dbo].[Postcodes] ([PostcodeID], [PostcodeValue], [TownName]) VALUES (3, N'2000', N'Town 2');
INSERT [dbo].[Postcodes] ([PostcodeID], [PostcodeValue], [TownName]) VALUES (4, N'3000', N'Town 3');
INSERT [dbo].[Postcodes] ([PostcodeID], [PostcodeValue], [TownName]) VALUES (5, N'4000', N'Town 4');
");
var loggingFactory = new TestSqlLoggerFactory();
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.AddSingleton<ILoggerFactory>(loggingFactory)
.BuildServiceProvider();

using (var context = new Bug6091Context(serviceProvider, testStore.ConnectionString))
{
var customers
= from customer in context.Customers
join postcode in context.Postcodes
on customer.PostcodeID equals postcode.PostcodeID into custPCTmp
from custPC in custPCTmp.DefaultIfEmpty()
select new
{
customer.CustomerID,
customer.CustomerName,
TownName = custPC == null ? string.Empty : custPC.TownName,
PostcodeValue = custPC == null ? string.Empty : custPC.PostcodeValue
};

var results = customers.ToList();

Assert.Equal(5, results.Count);
Assert.True(results[3].CustomerName != results[4].CustomerName);
}
}
}

private class Bug6091Context : DbContext
{
private readonly IServiceProvider _serviceProvider;
private readonly string _connectionString;

public Bug6091Context(IServiceProvider serviceProvider, string connectionString)
{
_serviceProvider = serviceProvider;
_connectionString = connectionString;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInternalServiceProvider(_serviceProvider).UseSqlServer(_connectionString);

protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<Customer>().ToTable("Customers");

public DbSet<Customer> Customers { get; set; }
public DbSet<Postcode> Postcodes { get; set; }

public class Customer
{
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public int? PostcodeID { get; set; }
}

public class Postcode
{
public int PostcodeID { get; set; }
public string PostcodeValue { get; set; }
public string TownName { get; set; }
}
}

[Fact]
public async Task Multiple_optional_navs_should_not_deadlock_bug_5481()
{
Expand Down Expand Up @@ -549,13 +650,6 @@ public class CustomerDetails_1742
public string LastName { get; set; }
}

private readonly SqlServerFixture _fixture;

public QueryBugsTest(SqlServerFixture fixture)
{
_fixture = fixture;
}

[Fact]
public void Customer_collections_materialize_properly_3758()
{
Expand Down

0 comments on commit dabdda9

Please sign in to comment.