diff --git a/src/EFCore.Cosmos/Query/Internal/FromSqlQueryRootExpression.cs b/src/EFCore.Cosmos/Query/Internal/FromSqlQueryRootExpression.cs index 2ac394e4c4a..2d8a289516b 100644 --- a/src/EFCore.Cosmos/Query/Internal/FromSqlQueryRootExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/FromSqlQueryRootExpression.cs @@ -3,6 +3,7 @@ using System; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Utilities; @@ -81,6 +82,18 @@ public FromSqlQueryRootExpression( public override Expression DetachQueryProvider() => new FromSqlQueryRootExpression(EntityType, Sql, Argument); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new FromSqlQueryRootExpression(entityType, Sql, Argument); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs b/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs index ca7dc4cae04..0e55b6e88bf 100644 --- a/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs +++ b/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs @@ -3,6 +3,7 @@ using System; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; @@ -80,6 +81,18 @@ public FromSqlQueryRootExpression( public override Expression DetachQueryProvider() => new FromSqlQueryRootExpression(EntityType, Sql, Argument); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new FromSqlQueryRootExpression(entityType, Sql, Argument); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs b/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs index cbda431c941..f4db217eae5 100644 --- a/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs +++ b/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; @@ -76,6 +77,18 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) : this; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new TableValuedFunctionQueryRootExpression(entityType, Function, Arguments); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Query/Internal/TemporalAllQueryRootExpression.cs b/src/EFCore.SqlServer/Query/Internal/TemporalAllQueryRootExpression.cs index 55b9577cc0b..065567e33ae 100644 --- a/src/EFCore.SqlServer/Query/Internal/TemporalAllQueryRootExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/TemporalAllQueryRootExpression.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; @@ -46,6 +47,18 @@ public TemporalAllQueryRootExpression(IAsyncQueryProvider queryProvider, IEntity public override Expression DetachQueryProvider() => new TemporalAllQueryRootExpression(EntityType); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new TemporalAllQueryRootExpression(entityType); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Query/Internal/TemporalAsOfQueryRootExpression.cs b/src/EFCore.SqlServer/Query/Internal/TemporalAsOfQueryRootExpression.cs index 3bb596cf117..1c9224de48d 100644 --- a/src/EFCore.SqlServer/Query/Internal/TemporalAsOfQueryRootExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/TemporalAsOfQueryRootExpression.cs @@ -3,6 +3,7 @@ using System; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; @@ -58,6 +59,18 @@ public TemporalAsOfQueryRootExpression( public override Expression DetachQueryProvider() => new TemporalAsOfQueryRootExpression(EntityType, PointInTime); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new TemporalAsOfQueryRootExpression(entityType, PointInTime); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Query/Internal/TemporalBetweenQueryRootExpression.cs b/src/EFCore.SqlServer/Query/Internal/TemporalBetweenQueryRootExpression.cs index f31147e58eb..7d133461c5c 100644 --- a/src/EFCore.SqlServer/Query/Internal/TemporalBetweenQueryRootExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/TemporalBetweenQueryRootExpression.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; @@ -53,6 +54,18 @@ public TemporalBetweenQueryRootExpression( public override Expression DetachQueryProvider() => new TemporalBetweenQueryRootExpression(EntityType, From, To); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new TemporalBetweenQueryRootExpression(entityType, From, To); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Query/Internal/TemporalContainedInQueryRootExpression.cs b/src/EFCore.SqlServer/Query/Internal/TemporalContainedInQueryRootExpression.cs index e10e9562f1a..fd2e049fcec 100644 --- a/src/EFCore.SqlServer/Query/Internal/TemporalContainedInQueryRootExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/TemporalContainedInQueryRootExpression.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; @@ -53,6 +54,18 @@ public TemporalContainedInQueryRootExpression( public override Expression DetachQueryProvider() => new TemporalContainedInQueryRootExpression(EntityType, From, To); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new TemporalContainedInQueryRootExpression(entityType, From, To); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Query/Internal/TemporalFromToQueryRootExpression.cs b/src/EFCore.SqlServer/Query/Internal/TemporalFromToQueryRootExpression.cs index 6a53fb9c50b..e04cd5698ca 100644 --- a/src/EFCore.SqlServer/Query/Internal/TemporalFromToQueryRootExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/TemporalFromToQueryRootExpression.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; @@ -53,6 +54,18 @@ public TemporalFromToQueryRootExpression( public override Expression DetachQueryProvider() => new TemporalFromToQueryRootExpression(EntityType, From, To); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new TemporalFromToQueryRootExpression(entityType, From, To); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Conventions/QueryFilterRewritingConvention.cs b/src/EFCore/Metadata/Conventions/QueryFilterRewritingConvention.cs index 2bf808df950..4164b28e229 100644 --- a/src/EFCore/Metadata/Conventions/QueryFilterRewritingConvention.cs +++ b/src/EFCore/Metadata/Conventions/QueryFilterRewritingConvention.cs @@ -3,8 +3,11 @@ using System; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Utilities; @@ -111,7 +114,55 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp && methodCallExpression.Type.GetGenericTypeDefinition() == typeof(DbSet<>) && _model != null) { - return new QueryRootExpression(FindEntityType(methodCallExpression.Type)!); + IEntityType? entityType; + var returnType = methodCallExpression.Type.GetGenericArguments()[0]; + if (methodCallExpression.Arguments.Count == 1) + { + // STET Set method + var entityTypeName = methodCallExpression.Arguments[0].GetConstantValue(); + entityType = (IEntityType?)_model.FindEntityType(entityTypeName); + } + else + { + entityType = FindEntityType(returnType); + } + + if (entityType == null) + { + if (_model.IsShared(returnType)) + { + throw new InvalidOperationException(CoreStrings.InvalidSetSharedType(returnType.ShortDisplayName())); + } + + var findSameTypeName = ((IModel)_model).FindSameTypeNameWithDifferentNamespace(returnType); + //if the same name exists in your entity types we will show you the full namespace of the type + if (!string.IsNullOrEmpty(findSameTypeName)) + { + throw new InvalidOperationException(CoreStrings.InvalidSetSameTypeWithDifferentNamespace(returnType.DisplayName(), findSameTypeName)); + } + else + { + throw new InvalidOperationException(CoreStrings.InvalidSetType(returnType.ShortDisplayName())); + } + } + + if (entityType.IsOwned()) + { + var message = CoreStrings.InvalidSetTypeOwned( + entityType.DisplayName(), entityType.FindOwnership()!.PrincipalEntityType.DisplayName()); + + throw new InvalidOperationException(message); + } + + if (entityType.ClrType != returnType) + { + var message = CoreStrings.DbSetIncorrectGenericType( + entityType.ShortName(), entityType.ClrType.ShortDisplayName(), returnType.ShortDisplayName()); + + throw new InvalidOperationException(message); + } + + return new QueryRootExpression(entityType); } return base.VisitMethodCall(methodCallExpression); diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 263ef038889..8cfd6310e95 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -610,7 +610,7 @@ public Expression Rewrite(Expression expression) /// protected override Expression VisitExtension(Expression extensionExpression) => extensionExpression is QueryRootExpression queryRootExpression - ? new QueryRootExpression(_model.FindEntityType(queryRootExpression.EntityType.Name)!) + ? queryRootExpression.UpdateEntityType(_model.FindEntityType(queryRootExpression.EntityType.Name)!) : base.VisitExtension(extensionExpression); } } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 4ea9c8c5d18..4ca089e9d27 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2289,6 +2289,14 @@ public static string QueryInvalidMaterializationType(object? projection, object? GetString("QueryInvalidMaterializationType", nameof(projection), nameof(queryableType)), projection, queryableType); + /// + /// The replacement entity type: {entityType} does not have same name and CLR type as entity type this query root represents. + /// + public static string QueryRootDifferentEntityType(object? entityType) + => string.Format( + GetString("QueryRootDifferentEntityType", nameof(entityType)), + entityType); + /// /// Translation of '{expression}' failed. Either the query source is not an entity type, or the specified property does not exist on the entity type. /// @@ -2847,7 +2855,7 @@ public static string ValueGenWithConversion(object? entityType, object? property => string.Format( GetString("ValueGenWithConversion", nameof(entityType), nameof(property), nameof(converter)), entityType, property, converter); - + /// /// Calling '{visitMethodName}' is not allowed. Visit the expression manually for the relevant part in the visitor. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index d12f4d5378f..4511a3a4dc0 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -972,7 +972,7 @@ The foreign key property '{entityType}.{property}' was created in shadow state because a conflicting property with the simple name '{baseName}' exists in the entity type, but is either not mapped, is already used for another relationship, or is incompatible with the associated primary key type. See https://aka.ms/efcore-relationships for information on mapping relationships in EF Core. - Warning CoreEventId.ShadowForeignKeyPropertyCreated string string + Warning CoreEventId.ShadowForeignKeyPropertyCreated string string string The property '{entityType}.{property}' was created in shadow state because there are no eligible CLR members with a matching name. @@ -1310,6 +1310,9 @@ The query contains a projection '{projection}' of type '{queryableType}'. Collections in the final projection must be an 'IEnumerable<T>' type such as 'List<T>'. Consider using 'ToList' or some other mechanism to convert the 'IQueryable<T>' or 'IOrderedEnumerable<T>' into an 'IEnumerable<T>'. + + The replacement entity type: {entityType} does not have same name and CLR type as entity type this query root represents. + Translation of '{expression}' failed. Either the query source is not an entity type, or the specified property does not exist on the entity type. diff --git a/src/EFCore/Query/QueryRootExpression.cs b/src/EFCore/Query/QueryRootExpression.cs index 09a497c0615..43b1edf6ac7 100644 --- a/src/EFCore/Query/QueryRootExpression.cs +++ b/src/EFCore/Query/QueryRootExpression.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; @@ -66,6 +67,17 @@ public QueryRootExpression(IEntityType entityType) public virtual Expression DetachQueryProvider() => new QueryRootExpression(EntityType); + /// + /// Updates entity type associated with this query root with equivalent optimized version. + /// + /// The entity type to replace with. + /// New query root containing given entity type. + public virtual QueryRootExpression UpdateEntityType(IEntityType entityType) + => entityType.ClrType != EntityType.ClrType + || entityType.Name != EntityType.Name + ? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName())) + : new QueryRootExpression(entityType); + /// public override ExpressionType NodeType => ExpressionType.Extension; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs new file mode 100644 index 00000000000..7a0204edbd0 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class SharedTypeQueryInMemoryTest : SharedTypeQueryTestBase + { + protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Can_use_shared_type_entity_type_in_ToInMemoryQuery(bool async) + { + var contextFactory = await InitializeAsync( + seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + + var data = context.Set(); + + Assert.Equal("Maumar", Assert.Single(data).Value); + } + + private class MyContextInMemory24601 : MyContext24601 + { + public MyContextInMemory24601(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.SharedTypeEntity>("STET", + b => + { + b.IndexerProperty("Id"); + b.IndexerProperty("Value"); + }); + + modelBuilder.Entity().HasNoKey() + .ToInMemoryQuery(() => Set>("STET").Select(e => new ViewQuery24601 { Value = (string)e["Value"] })); + } + } + } +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs new file mode 100644 index 00000000000..cdf9a09433e --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public abstract class SharedTypeQueryRelationalTestBase : SharedTypeQueryTestBase + { + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected void ClearLog() => TestSqlLoggerFactory.Clear(); + + protected void AssertSql(params string[] expected) => TestSqlLoggerFactory.AssertBaseline(expected); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Can_use_shared_type_entity_type_in_query_filter_with_from_sql(bool async) + { + var contextFactory = await InitializeAsync( + seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Empty(result); + } + + protected class MyContextRelational24601 : MyContext24601 + { + public MyContextRelational24601(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity() + .HasQueryFilter(e => Set>("STET") + .FromSqlRaw("Select * from STET").Select(i => (string)i["Value"]).Contains(e.Value)); + } + } + } +} diff --git a/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs new file mode 100644 index 00000000000..d07bac3a8f0 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class SharedTypeQueryTestBase : NonSharedModelTestBase + { + public static IEnumerable IsAsyncData = new[] { new object[] { false }, new object[] { true } }; + + protected override string StoreName => "SharedTypeQueryTests"; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Can_use_shared_type_entity_type_in_query_filter(bool async) + { + var contextFactory = await InitializeAsync( + seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Empty(result); + } + + protected class MyContext24601 : DbContext + { + public MyContext24601(DbContextOptions options) + : base(options) + { + } + + public void Seed() + { + Set>("STET").Add(new Dictionary + { + ["Value"] = "Maumar" + }); + + SaveChanges(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.SharedTypeEntity>("STET", + b => + { + b.IndexerProperty("Id"); + b.IndexerProperty("Value"); + }); + + modelBuilder.Entity().HasNoKey() + .HasQueryFilter(e => Set>("STET").Select(i => (string)i["Value"]).Contains(e.Value)); + } + } + + protected class ViewQuery24601 + { + public string Value { get; set; } + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 27bffb56ff4..b591b079372 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -10159,7 +10159,7 @@ public class JsonResult #endregion - #region Issue24569 + #region Issue25400 [ConditionalTheory] [InlineData(true)] @@ -10171,14 +10171,14 @@ public virtual async Task NoTracking_split_query_creates_only_required_instances using (var context = contextFactory.CreateContext()) { - Test24569.ConstructorCallCount = 0; + Test25400.ConstructorCallCount = 0; - var query = context.Set().AsNoTracking().OrderBy(e => e.Id); + var query = context.Set().AsNoTracking().OrderBy(e => e.Id); var test = async ? await query.FirstOrDefaultAsync() : query.FirstOrDefault(); - Assert.Equal(1, Test24569.ConstructorCallCount); + Assert.Equal(1, Test25400.ConstructorCallCount); AssertSql( @"SELECT TOP(1) [t].[Id], [t].[Value] @@ -10189,7 +10189,7 @@ FROM [Tests] AS [t] protected class MyContext25400 : DbContext { - public DbSet Tests { get; set; } + public DbSet Tests { get; set; } public MyContext25400(DbContextOptions options) : base(options) @@ -10198,27 +10198,27 @@ public MyContext25400(DbContextOptions options) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().HasKey(e => e.Id); + modelBuilder.Entity().HasKey(e => e.Id); } public void Seed() { - Tests.Add(new Test24569(15)); + Tests.Add(new Test25400(15)); SaveChanges(); } } - protected class Test24569 + protected class Test25400 { public static int ConstructorCallCount = 0; - public Test24569() + public Test25400() { ++ConstructorCallCount; } - public Test24569(int value) + public Test25400(int value) { Value = value; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs new file mode 100644 index 00000000000..3d4497c9729 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class SharedTypeQuerySqlServerTest : SharedTypeQueryRelationalTestBase + { + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + + public override async Task Can_use_shared_type_entity_type_in_query_filter(bool async) + { + await base.Can_use_shared_type_entity_type_in_query_filter(async); + + AssertSql( + @"SELECT [v].[Value] +FROM [ViewQuery24601] AS [v] +WHERE EXISTS ( + SELECT 1 + FROM [STET] AS [s] + WHERE ([s].[Value] = [v].[Value]) OR ([s].[Value] IS NULL AND [v].[Value] IS NULL))"); + } + + public override async Task Can_use_shared_type_entity_type_in_query_filter_with_from_sql(bool async) + { + await base.Can_use_shared_type_entity_type_in_query_filter_with_from_sql(async); + + AssertSql( + @"SELECT [v].[Value] +FROM [ViewQuery24601] AS [v] +WHERE EXISTS ( + SELECT 1 + FROM ( + Select * from STET + ) AS [s] + WHERE ([s].[Value] = [v].[Value]) OR ([s].[Value] IS NULL AND [v].[Value] IS NULL))"); + } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs new file mode 100644 index 00000000000..cf34bec702d --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class SharedTypeQuerySqliteTest : SharedTypeQueryRelationalTestBase + { + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + } +}