Skip to content

Commit

Permalink
Query: Support ToQuerySql
Browse files Browse the repository at this point in the history
Resolves #17063
  • Loading branch information
smitpatel committed Jul 28, 2020
1 parent e9fae4a commit 1d80a91
Show file tree
Hide file tree
Showing 17 changed files with 134 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ protected override Expression VisitExtension(Expression extensionExpression)

return CreateShapedQueryExpression(entityType, queryExpression);

case QueryRootExpression queryRootExpression
when queryRootExpression.EntityType.GetQuerySql() is string querySql:
return Visit(new FromSqlQueryRootExpression(
queryRootExpression.EntityType, querySql, Expression.Constant(Array.Empty<object>(), typeof(object[]))));


default:
return base.VisitExtension(extensionExpression);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder
.Entity<CustomerQueryWithQueryFilter>()
.HasDiscriminator<string>("Discriminator").HasValue("Customer");

#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder
.Entity<CustomerQuery>().Metadata.SetDefiningQuery(null);
modelBuilder
.Entity<OrderQuery>().Metadata.SetDefiningQuery(null);
modelBuilder
.Entity<ProductQuery>().Metadata.SetDefiningQuery(null);
modelBuilder
.Entity<CustomerQueryWithQueryFilter>().Metadata.SetDefiningQuery(null);
#pragma warning restore CS0618 // Type or member is obsolete
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit.Abstractions;

Expand All @@ -13,5 +14,10 @@ public NorthwindChangeTrackingQueryInMemoryTest(NorthwindQueryInMemoryFixture<No
{
//TestLoggerFactory.TestOutputHelper = testOutputHelper;
}

protected override NorthwindContext CreateNoTrackingContext()
=> new NorthwindInMemoryContext(
new DbContextOptionsBuilder(Fixture.CreateOptions())
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking).Options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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.Linq;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;

namespace Microsoft.EntityFrameworkCore.Query
{
public class NorthwindInMemoryContext : NorthwindContext
{
public NorthwindInMemoryContext(DbContextOptions options)
: base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

InMemoryEntityTypeBuilderExtensions.ToQuery(
modelBuilder.Entity<CustomerQuery>(),
() => Customers.Select(
c => new CustomerQuery
{
Address = c.Address,
City = c.City,
CompanyName = c.CompanyName,
ContactName = c.ContactName,
ContactTitle = c.ContactTitle
}));

InMemoryEntityTypeBuilderExtensions.ToQuery(
modelBuilder.Entity<OrderQuery>(),
() => Orders.Select(o => new OrderQuery { CustomerID = o.CustomerID }));

InMemoryEntityTypeBuilderExtensions.ToQuery(
modelBuilder.Entity<ProductQuery>(),
() => Products.Where(p => !p.Discontinued)
.Select(
p => new ProductQuery
{
ProductID = p.ProductID,
ProductName = p.ProductName,
CategoryName = "Food"
}));

InMemoryEntityTypeBuilderExtensions.ToQuery(
modelBuilder.Entity<CustomerQueryWithQueryFilter>(),
() => Customers.Select(
c => new CustomerQueryWithQueryFilter
{
CompanyName = c.CompanyName,
OrderCount = c.Orders.Count(),
SearchTerm = SearchTerm
}));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities;

Expand All @@ -10,5 +11,7 @@ public class NorthwindQueryInMemoryFixture<TModelCustomizer> : NorthwindQueryFix
where TModelCustomizer : IModelCustomizer, new()
{
protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance;

protected override Type ContextType => typeof(NorthwindInMemoryContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -709,9 +709,8 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<CustomerView19708>().HasNoKey().ToQuery(Build_Customers_Sql_View_InMemory());
#pragma warning restore CS0618 // Type or member is obsolete
InMemoryEntityTypeBuilderExtensions.ToQuery(
modelBuilder.Entity<CustomerView19708>().HasNoKey(), Build_Customers_Sql_View_InMemory());
}

private Expression<Func<IQueryable<CustomerView19708>>> Build_Customers_Sql_View_InMemory()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity<Lilt>().Property(e => e.SugarGrams).HasColumnName("SugarGrams");
modelBuilder.Entity<Tea>().Property(e => e.CaffeineGrams).HasColumnName("CaffeineGrams");

#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<AnimalQuery>().HasNoKey().ToQuery(
() => context.Set<AnimalQuery>().FromSqlRaw("SELECT * FROM Animals"));
#pragma warning restore CS0618 // Type or member is obsolete
modelBuilder.Entity<AnimalQuery>().HasNoKey().ToQuerySql("SELECT * FROM Animals");
modelBuilder.Entity<KiwiQuery>().HasDiscriminator().HasValue("Kiwi");
modelBuilder.Entity<EagleQuery>().HasDiscriminator().HasValue("Eagle");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public NorthwindRelationalContext(DbContextOptions options)
{
}

public string _empty = string.Empty;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expand All @@ -27,22 +26,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<CustomerOrderHistory>().HasKey(coh => coh.ProductName);
modelBuilder.Entity<MostExpensiveProduct>().HasKey(mep => mep.TenMostExpensiveProducts);

#pragma warning disable CS0618 // Type or member is obsolete
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]"
));

modelBuilder
.Entity<OrderQuery>()
.HasNoKey()
.ToQuery(
() => Orders
.FromSqlRaw(@"select * from ""Orders""")
.Select(
o => new OrderQuery { CustomerID = o.CustomerID }));
#pragma warning restore CS0618 // Type or member is obsolete
modelBuilder.Entity<CustomerQuery>().ToQuerySql("SELECT [c].[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]");

modelBuilder.Entity<OrderQuery>().ToQuerySql(@"select * from ""Orders""");
modelBuilder.Entity<ProductView>().HasNoKey().ToView("Alphabetical list of products");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void Model_differ_does_not_detect_views_with_weak_types()
}

[ConditionalFact]
public void Model_differ_does_not_detect_queries()
public void Model_differ_does_not_detect_defining_queries()
{
DbContext context = null;
Execute(
Expand All @@ -72,6 +72,15 @@ public void Model_differ_does_not_detect_queries()
result => Assert.Empty(result));
}

[ConditionalFact]
public void Model_differ_does_not_detect_queries()
{
Execute(
_ => { },
modelBuilder => modelBuilder.Entity<TestKeylessType>().HasNoKey().ToQuerySql("SELECT * FROM Vista"),
result => Assert.Empty(result));
}

[ConditionalFact]
public void Model_differ_detects_adding_store_type()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var results
.Where(cq => cq.OrderCount > 0)
.ToArray();

Assert.Equal(4, results.Length);
Assert.Equal(89, results.Length);
}

[ConditionalTheory]
Expand All @@ -82,7 +82,7 @@ public virtual Task KeylessEntity_with_defining_query(bool async)
ss => ss.Set<OrderQuery>().Where(ov => ov.CustomerID == "ALFKI"));
}

[ConditionalTheory]
[ConditionalTheory(Skip = "Issue#21828")]
[MemberData(nameof(IsAsyncData))]
public virtual Task KeylessEntity_with_defining_query_and_correlated_collection(bool async)
{
Expand Down Expand Up @@ -111,46 +111,30 @@ from o in ss.Set<OrderQuery>().Where(ov => ov.CustomerID == c.CustomerID)

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task KeylessEntity_with_included_nav(bool async)
{
using var ctx = CreateContext();
if (async)
{
await Assert.ThrowsAsync<InvalidOperationException>(
() => (from ov in ctx.Set<OrderQuery>().Include(ov => ov.Customer)
where ov.CustomerID == "ALFKI"
select ov).ToListAsync());
}
else
{
await Assert.ThrowsAsync<InvalidOperationException>(
() => Task.FromResult(
(from ov in ctx.Set<OrderQuery>().Include(ov => ov.Customer)
where ov.CustomerID == "ALFKI"
select ov).ToList()));
}
public virtual Task KeylessEntity_with_included_nav(bool async)
{
return AssertQuery(
async,
ss => from ov in ss.Set<OrderQuery>().Include(ov => ov.Customer)
where ov.CustomerID == "ALFKI"
select ov,
elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude<OrderQuery>(ov => ov.Customer)),
entryCount: 1);
}

[ConditionalTheory]
[ConditionalTheory(Skip = "Issue#21828")]
[MemberData(nameof(IsAsyncData))]
public virtual async Task KeylessEntity_with_included_navs_multi_level(bool async)
{
using var ctx = CreateContext();
if (async)
{
await Assert.ThrowsAsync<InvalidOperationException>(
() => (from ov in ctx.Set<OrderQuery>().Include(ov => ov.Customer.Orders)
where ov.CustomerID == "ALFKI"
select ov).ToListAsync());
}
else
{
await Assert.ThrowsAsync<InvalidOperationException>(
() => Task.FromResult(
(from ov in ctx.Set<OrderQuery>().Include(ov => ov.Customer.Orders)
where ov.CustomerID == "ALFKI"
select ov).ToList()));
}
public virtual Task KeylessEntity_with_included_navs_multi_level(bool async)
{
return AssertQuery(
async,
ss => from ov in ss.Set<OrderQuery>().Include(ov => ov.Customer.Orders)
where ov.CustomerID == "ALFKI"
select ov,
elementAsserter: (e, a) => AssertInclude(e, a,
new ExpectedInclude<OrderQuery>(ov => ov.Customer),
new ExpectedInclude<Customer>(c => c.Orders, "Customer")),
entryCount: 1);
}

[ConditionalTheory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,67 +74,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
od => new { od.OrderID, od.ProductID });
});

#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder
.Entity<CustomerQuery>()
.HasNoKey()
.ToQuery(
() => Customers
.Select(
c => new CustomerQuery
{
Address = c.Address,
City = c.City,
CompanyName = c.CompanyName,
ContactName = c.ContactName,
ContactTitle = c.ContactTitle
}));

modelBuilder
.Entity<OrderQuery>()
.HasNoKey()
.ToQuery(
() => Orders
.Select(
o => new OrderQuery { CustomerID = o.CustomerID }));

modelBuilder
.Entity<ProductQuery>()
.HasNoKey()
.ToQuery(
() => Products
.Where(p => !p.Discontinued)
.Select(
p => new ProductQuery
{
ProductID = p.ProductID,
ProductName = p.ProductName,
CategoryName = "Food"
}));

modelBuilder
.Entity<CustomerQueryWithQueryFilter>()
.HasNoKey()
.HasQueryFilter(cq => cq.CompanyName.StartsWith(_searchTerm))
.ToQuery(
() =>
Customers
.Include(c => c.Orders) // ignored
.Select(
c =>
new CustomerQueryWithQueryFilter
{
CompanyName = c.CompanyName,
OrderCount = c.Orders.Count(),
SearchTerm = _searchTerm
}));
#pragma warning restore CS0618 // Type or member is obsolete
modelBuilder.Entity<CustomerQuery>().HasNoKey();
modelBuilder.Entity<OrderQuery>().HasNoKey();
modelBuilder.Entity<ProductQuery>().HasNoKey();
modelBuilder.Entity<CustomerQueryWithQueryFilter>().HasNoKey();
}

public string TenantPrefix { get; set; } = "B";

private readonly short _quantity = 50;
private readonly string _searchTerm = "A";
public readonly string SearchTerm = "A";

public void ConfigureFilters(ModelBuilder modelBuilder)
{
Expand All @@ -146,6 +95,7 @@ public void ConfigureFilters(ModelBuilder modelBuilder)
modelBuilder.Entity<OrderDetail>().HasQueryFilter(od => EF.Property<short>(od, "Quantity") > _quantity);
modelBuilder.Entity<Employee>().HasQueryFilter(e => e.Address.StartsWith("A"));
modelBuilder.Entity<Product>().HasQueryFilter(p => ClientMethod(p));
modelBuilder.Entity<CustomerQueryWithQueryFilter>().HasQueryFilter(cq => cq.CompanyName.StartsWith(SearchTerm));
}

private static bool ClientMethod(Product product)
Expand Down
Loading

0 comments on commit 1d80a91

Please sign in to comment.