Skip to content

Commit

Permalink
Query: Generate condition for table splitting when no required proper…
Browse files Browse the repository at this point in the history
…ties

Resolves #29196
  • Loading branch information
smitpatel committed Sep 26, 2022
1 parent ed71bc5 commit dc50719
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 5 deletions.
31 changes: 26 additions & 5 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Context29196>(
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<Order> Orders => Set<Order>();

public DbSet<DetailedOrder> DetailedOrders => Set<DetailedOrder>();

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

modelBuilder.Entity<DetailedOrder>(
dob =>
{
dob.ToTable("Orders");
dob.Property(o => o.Status).HasColumnName("Status");
dob.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
});

modelBuilder.Entity<Order>(
ob =>
{
ob.ToTable("Orders");
ob.Property(o => o.Status).HasColumnName("Status");
ob.HasOne(o => o.DetailedOrder).WithOne().HasForeignKey<DetailedOrder>(o => o.Id);
ob.Property<byte[]>("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());

Expand Down

0 comments on commit dc50719

Please sign in to comment.