From dc5071945f3a50bd0011e9de154b83b1fb5fc5a4 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 26 Sep 2022 12:46:17 -0700 Subject: [PATCH] Query: Generate condition for table splitting when no required properties Resolves #29196 --- .../Query/SqlExpressionFactory.cs | 31 +++++-- .../TableSplittingTestBase.cs | 90 +++++++++++++++++++ 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 28ff4850387..1da3ecfb20c 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -645,12 +645,33 @@ private void AddConditions(SelectExpression selectExpression, IEntityType entity var table = (firstTable as FromSqlExpression)?.Table ?? ((ITableBasedExpression)firstTable).Table; if (table.IsOptional(entityType)) { + SqlExpression? predicate = null; var entityProjectionExpression = GetMappedEntityProjectionExpression(selectExpression); - var predicate = entityType.GetNonPrincipalSharedNonPkProperties(table) - .Where(e => !e.IsNullable) - .Select(e => IsNotNull(e, entityProjectionExpression)) - .Aggregate((l, r) => AndAlso(l, r)); - selectExpression.ApplyPredicate(predicate); + var requiredNonPkProperties = entityType.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()).ToList(); + if (requiredNonPkProperties.Count > 0) + { + predicate = requiredNonPkProperties.Select(e => IsNotNull(e, entityProjectionExpression)) + .Aggregate((l, r) => AndAlso(l, r)); + } + + var allNonSharedNonPkProperties = entityType.GetNonPrincipalSharedNonPkProperties(table); + // We don't need condition for nullable property if there exist at least one required property which is non shared. + if (allNonSharedNonPkProperties.Count != 0 + && allNonSharedNonPkProperties.All(p => p.IsNullable)) + { + var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNonPkProperties + .Select(e => IsNotNull(e, entityProjectionExpression)) + .Aggregate((a, b) => OrElse(a, b)); + + predicate = predicate == null + ? atLeastOneNonNullValueInNullablePropertyCondition + : AndAlso(predicate, atLeastOneNonNullValueInNullablePropertyCondition); + } + + if (predicate != null) + { + selectExpression.ApplyPredicate(predicate); + } } bool HasSiblings(IEntityType entityType) diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index 1d027b23dde..79965575cc3 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -675,6 +675,96 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Optional_dependent_without_required_property(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: e => e.ConfigureWarnings(w => w.Log(RelationalEventId.OptionalDependentWithoutIdentifyingPropertyWarning))); + + using (var context = contextFactory.CreateContext()) + { + var query = context.DetailedOrders.Where(o => o.Status == OrderStatus.Pending); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + } + } + + protected class Context29196 : DbContext + { + public Context29196(DbContextOptions options) + : base(options) + { + } + + public DbSet Orders => Set(); + + public DbSet DetailedOrders => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + dob => + { + dob.ToTable("Orders"); + dob.Property(o => o.Status).HasColumnName("Status"); + dob.Property(o => o.Version).IsRowVersion().HasColumnName("Version"); + }); + + modelBuilder.Entity( + ob => + { + ob.ToTable("Orders"); + ob.Property(o => o.Status).HasColumnName("Status"); + ob.HasOne(o => o.DetailedOrder).WithOne().HasForeignKey(o => o.Id); + ob.Property("Version").IsRowVersion().HasColumnName("Version"); + }); + } + + public void Seed() + { + Add( + new Order + { + Status = OrderStatus.Pending, + DetailedOrder = new DetailedOrder + { + Status = OrderStatus.Pending, + ShippingAddress = "221 B Baker St, London", + BillingAddress = "11 Wall Street, New York" + } + }); + + SaveChanges(); + } + } + + public class DetailedOrder + { + public int Id { get; set; } + public OrderStatus? Status { get; set; } + public string BillingAddress { get; set; } + public string ShippingAddress { get; set; } + public byte[] Version { get; set; } + } + + public class Order + { + public int Id { get; set; } + public OrderStatus? Status { get; set; } + public DetailedOrder DetailedOrder { get; set; } + } + + public enum OrderStatus + { + Pending, + Shipped + } + public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction());