Skip to content

Commit

Permalink
Regression tests for #13381 - NullReferenceException when materializi…
Browse files Browse the repository at this point in the history
…ng a Query type on the right side of a left join

Issue has been fixed in earlier checkin.

Resolves #13381
  • Loading branch information
maumar committed Jun 2, 2020
1 parent f727d7a commit bdb93f5
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,10 @@ public override async Task KeylessEntity_where_simple(bool async)
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""City""] = ""London""))");
}

[ConditionalFact]
[ConditionalFact] // views are not supported
public override void KeylessEntity_by_database_view()
{
base.KeylessEntity_by_database_view();

AssertSql(
@"SELECT c[""ProductID""], c[""ProductName""], ""Food"" AS CategoryName
FROM root c
WHERE ((c[""Discriminator""] = ""Product"") AND NOT(c[""Discontinued""]))");
}

[ConditionalFact(Skip = "See issue#17246")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,14 @@ public NorthwindKeylessEntitiesQueryInMemoryTest(
{
//TestLoggerFactory.TestOutputHelper = testOutputHelper;
}

// mapping to view not supported on InMemory
public override void KeylessEntity_by_database_view()
{
}

public override void Entity_mapped_to_view_on_right_side_of_join()
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<CustomerOrderHistory>().HasKey(coh => coh.ProductName);
modelBuilder.Entity<MostExpensiveProduct>().HasKey(mep => mep.TenMostExpensiveProducts);

modelBuilder.Entity<CustomerView>().HasNoKey().ToQuery(
modelBuilder.Entity<CustomerQuery>().HasNoKey().ToQuery(
() => CustomerQueries.FromSqlInterpolated(
$"SELECT [c].[CustomerID] + {_empty} as [CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c]"
));
Expand All @@ -41,7 +41,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Select(
o => new OrderQuery { CustomerID = o.CustomerID }));

modelBuilder.Entity<ProductQuery>().HasNoKey().ToView("Alphabetical list of products");
modelBuilder.Entity<ProductView>().HasNoKey().ToView("Alphabetical list of products");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1549,7 +1549,7 @@ public virtual Task Array_cast_to_IEnumerable_Contains_with_constant(bool async)
public virtual void Contains_over_keyless_entity_throws()
{
using var context = CreateContext();
Assert.Throws<InvalidOperationException>(() => context.CustomerQueries.Contains(new CustomerView()));
Assert.Throws<InvalidOperationException>(() => context.CustomerQueries.Contains(new CustomerQuery()));
}

[ConditionalTheory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ protected NorthwindAsyncSimpleQueryTestBase(TFixture fixture)
public virtual async Task Query_backed_by_database_view()
{
using var context = CreateContext();
var results = await context.Set<ProductQuery>().ToArrayAsync();
var results = await context.Set<ProductView>().ToArrayAsync();

Assert.Equal(69, results.Length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public virtual Task KeylessEntity_simple(bool async)
{
return AssertQuery(
async,
ss => ss.Set<CustomerView>());
ss => ss.Set<CustomerQuery>());
}

[ConditionalTheory]
Expand All @@ -40,14 +40,14 @@ public virtual Task KeylessEntity_where_simple(bool async)
{
return AssertQuery(
async,
ss => ss.Set<CustomerView>().Where(c => c.City == "London"));
ss => ss.Set<CustomerQuery>().Where(c => c.City == "London"));
}

[ConditionalFact]
public virtual void KeylessEntity_by_database_view()
{
using var context = CreateContext();
var results = context.Set<ProductQuery>().ToArray();
var results = context.Set<ProductView>().ToArray();

Assert.Equal(69, results.Length);
}
Expand All @@ -66,7 +66,7 @@ public virtual void KeylessEntity_with_nav_defining_query()
{
using var context = CreateContext();
var results
= context.Set<CustomerQuery>()
= context.Set<CustomerQueryWithQueryFilter>()
.Where(cq => cq.OrderCount > 0)
.ToArray();

Expand Down Expand Up @@ -181,10 +181,23 @@ public virtual Task KeylesEntity_groupby(bool async)
{
return AssertQuery(
async,
ss => ss.Set<CustomerView>()
ss => ss.Set<CustomerQuery>()
.GroupBy(cv => cv.City)
.Select(g => new { g.Key, Count = g.Count(), Sum = g.Sum(e => e.Address.Length) }),
elementSorter: e => (e.Key, e.Count, e.Sum));
}

[ConditionalFact]
public virtual void Entity_mapped_to_view_on_right_side_of_join()
{
using var context = CreateContext();

var results = (from o in context.Set<Order>()
join pv in context.Set<ProductView>() on o.CustomerID equals pv.CategoryName into grouping
from pv in grouping.DefaultIfEmpty()
select new { Order = o, ProductView = pv }).ToList();

Assert.Equal(830, results.Count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public IReadOnlyDictionary<Type, object> GetEntitySorters()
=> new Dictionary<Type, Func<object, object>>
{
{ typeof(Customer), e => ((Customer)e)?.CustomerID },
{ typeof(CustomerView), e => ((CustomerView)e)?.CompanyName },
{ typeof(CustomerQuery), e => ((CustomerQuery)e)?.CompanyName },
{ typeof(Order), e => ((Order)e)?.OrderID },
{ typeof(OrderQuery), e => ((OrderQuery)e)?.CustomerID },
{ typeof(Employee), e => ((Employee)e)?.EmployeeID },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1678,7 +1678,7 @@ public virtual Task Project_keyless_entity_FirstOrDefault_without_orderby(bool a
{
return AssertQuery(
async,
ss => ss.Set<Customer>().Select(c => ss.Set<CustomerView>().FirstOrDefault(cv => cv.CompanyName == c.CompanyName)));
ss => ss.Set<Customer>().Select(c => ss.Set<CustomerQuery>().FirstOrDefault(cv => cv.CompanyName == c.CompanyName)));
}

[ConditionalTheory]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.ComponentModel.DataAnnotations.Schema;

namespace Microsoft.EntityFrameworkCore.TestModels.Northwind
{
public class CustomerQuery
{
public string CompanyName { get; set; }
public int OrderCount { get; set; }
public string SearchTerm { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }

[NotMapped]
public bool IsLondon => City == "London";

protected bool Equals(CustomerQuery other)
=> string.Equals(CompanyName, other.CompanyName);

public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}

return ReferenceEquals(this, obj)
? true
: obj.GetType() == GetType()
&& Equals((CustomerQuery)obj);
}

public static bool operator ==(CustomerQuery left, CustomerQuery right)
=> Equals(left, right);

public static bool operator !=(CustomerQuery left, CustomerQuery right)
=> !Equals(left, right);

public override int GetHashCode()
// ReSharper disable once NonReadonlyMemberInGetHashCode
=> CompanyName?.GetHashCode() ?? 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.TestModels.Northwind
{
public class CustomerQueryWithQueryFilter
{
public string CompanyName { get; set; }
public int OrderCount { get; set; }
public string SearchTerm { get; set; }
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public NorthwindContext(DbContextOptions options)
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<OrderDetail> OrderDetails { get; set; }
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<CustomerView> CustomerQueries { get; set; }
public virtual DbSet<CustomerQuery> CustomerQueries { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expand Down Expand Up @@ -75,12 +75,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
});

modelBuilder
.Entity<CustomerView>()
.Entity<CustomerQuery>()
.HasNoKey()
.ToQuery(
() => Customers
.Select(
c => new CustomerView
c => new CustomerQuery
{
Address = c.Address,
City = c.City,
Expand Down Expand Up @@ -112,7 +112,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}));

modelBuilder
.Entity<CustomerQuery>()
.Entity<CustomerQueryWithQueryFilter>()
.HasNoKey()
.HasQueryFilter(cq => cq.CompanyName.StartsWith(_searchTerm))
.ToQuery(
Expand All @@ -121,7 +121,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Include(c => c.Orders) // ignored
.Select(
c =>
new CustomerQuery
new CustomerQueryWithQueryFilter
{
CompanyName = c.CompanyName,
OrderCount = c.Orders.Count(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.TestModels.Northwind
public partial class NorthwindData : ISetSource
{
private readonly Customer[] _customers;
private readonly CustomerView[] _customerViews;
private readonly CustomerQuery[] _customerQueries;
private readonly Employee[] _employees;
private readonly Product[] _products;
private readonly Order[] _orders;
Expand All @@ -28,14 +28,15 @@ public NorthwindData()
_orders = CreateOrders();
_orderDetails = CreateOrderDetails();

var customerViews = new List<CustomerView>();
var customerQueries = new List<CustomerQuery>();
var customerQueriesWithQueryFilter = new List<CustomerQueryWithQueryFilter>();

foreach (var customer in _customers)
{
customer.Orders = new List<Order>();

customerViews.Add(
new CustomerView
customerQueries.Add(
new CustomerQuery
{
Address = customer.Address,
City = customer.City,
Expand All @@ -45,7 +46,7 @@ public NorthwindData()
});
}

_customerViews = customerViews.ToArray();
_customerQueries = customerQueries.ToArray();

foreach (var product in _products)
{
Expand Down Expand Up @@ -114,9 +115,9 @@ public IQueryable<TEntity> Set<TEntity>()
return (IQueryable<TEntity>)_products.AsQueryable();
}

if (typeof(TEntity) == typeof(CustomerView))
if (typeof(TEntity) == typeof(CustomerQuery))
{
return (IQueryable<TEntity>)_customerViews.AsQueryable();
return (IQueryable<TEntity>)_customerQueries.AsQueryable();
}

if (typeof(TEntity) == typeof(OrderQuery))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.TestModels.Northwind
{
public class ProductView
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public string CategoryName { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,9 @@ public override void KeylessEntity_by_database_view()
{
base.KeylessEntity_by_database_view();

// See issue#17804
// when we have defining query and ToView, defining query wins
// AssertSql(
// @"SELECT [a].[CategoryName], [a].[ProductID], [a].[ProductName]
//FROM [Alphabetical list of products] AS [a]");
AssertSql(
@"SELECT [p].[ProductID], [p].[ProductName], N'Food' AS [CategoryName]
FROM [Products] AS [p]
WHERE [p].[Discontinued] <> CAST(1 AS bit)");
@"SELECT [a].[CategoryName], [a].[ProductID], [a].[ProductName]
FROM [Alphabetical list of products] AS [a]");
}

public override void KeylessEntity_with_nav_defining_query()
Expand Down Expand Up @@ -164,6 +158,16 @@ public override async Task KeylesEntity_groupby(bool async)
GROUP BY [c].[City]");
}

public override void Entity_mapped_to_view_on_right_side_of_join()
{
base.Entity_mapped_to_view_on_right_side_of_join();

AssertSql(
@"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [a].[CategoryName], [a].[ProductID], [a].[ProductName]
FROM [Orders] AS [o]
LEFT JOIN [Alphabetical list of products] AS [a] ON [o].[CustomerID] = [a].[CategoryName]");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down

0 comments on commit bdb93f5

Please sign in to comment.