diff --git a/src/VirtoCommerce.OrdersModule.Data/Repositories/OrderRepository.cs b/src/VirtoCommerce.OrdersModule.Data/Repositories/OrderRepository.cs index 45033d5ad..3b848356b 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Repositories/OrderRepository.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Repositories/OrderRepository.cs @@ -14,7 +14,6 @@ namespace VirtoCommerce.OrdersModule.Data.Repositories { public class OrderRepository : DbContextRepositoryBase, IOrderRepository { -#pragma warning disable S109 public OrderRepository(OrderDbContext dbContext, IUnitOfWork unitOfWork = null) : base(dbContext, unitOfWork) { @@ -50,202 +49,231 @@ public virtual async Task> GetCustomerOrdersByIdsAsyn return Array.Empty(); } - var result = await CustomerOrders.Where(x => ids.Contains(x.Id)).ToArrayAsync(); + var customerOrderResponseGroup = EnumUtility.SafeParseFlags(responseGroup, CustomerOrderResponseGroup.Full); + + // Load CustomerOrder + the child collections that hang directly off it in a single + // round-trip. EF Core wires up navigations on InPayments/Items/Shipments that are + // loaded below by foreign-key fix-up. + var rootQuery = CustomerOrders + .Include(x => x.Discounts) + .Include(x => x.TaxDetails) + .Include(x => x.FeeDetails) + .AsQueryable(); - if (!result.Any()) + if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithAddresses)) { - return Array.Empty(); + rootQuery = rootQuery.Include(x => x.Addresses); } - ids = result.Select(x => x.Id).ToArray(); - - await Discounts.Where(x => ids.Contains(x.CustomerOrderId)).LoadAsync(); - await TaxDetails.Where(x => ids.Contains(x.CustomerOrderId)).LoadAsync(); - await FeeDetails.Where(x => ids.Contains(x.CustomerOrderId)).LoadAsync(); - - var customerOrderResponseGroup = EnumUtility.SafeParseFlags(responseGroup, CustomerOrderResponseGroup.Full); - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) { - await OrderDynamicPropertyObjectValues.Where(x => ids.Contains(x.CustomerOrderId)).LoadAsync(); + rootQuery = rootQuery.Include(x => x.DynamicPropertyObjectValues); } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithAddresses)) + var result = await rootQuery + .Where(x => ids.Contains(x.Id)) + .AsSplitQuery() + .ToArrayAsync(); + + if (result.Length == 0) { - await Addresses.Where(x => ids.Contains(x.CustomerOrderId)).LoadAsync(); + return Array.Empty(); } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithInPayments)) - { - var payments = await InPayments.Where(x => ids.Contains(x.CustomerOrderId)).ToArrayAsync(); + ids = result.Select(x => x.Id).ToArray(); - if (payments.Any()) - { - var paymentIds = payments.Select(x => x.Id).ToArray(); - - await Discounts.Where(x => paymentIds.Contains(x.PaymentInId)).LoadAsync(); - await TaxDetails.Where(x => paymentIds.Contains(x.PaymentInId)).LoadAsync(); - await FeeDetails.Where(x => paymentIds.Contains(x.PaymentInId)).LoadAsync(); - await Addresses.Where(x => paymentIds.Contains(x.PaymentInId)).LoadAsync(); - await Transactions.Where(x => paymentIds.Contains(x.PaymentInId)).LoadAsync(); - - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) - { - await OrderDynamicPropertyObjectValues.Where(x => paymentIds.Contains(x.PaymentInId)).LoadAsync(); - } - } - } + await LoadInPayments(ids, customerOrderResponseGroup); + await LoadLineItems(ids, customerOrderResponseGroup); + await LoadShipments(ids, customerOrderResponseGroup); + await LoadRefunds(ids, customerOrderResponseGroup); + await LoadCaptures(ids, customerOrderResponseGroup); - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithItems)) + if (!customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithPrices)) { - var lineItems = await LineItems - .Where(x => ids.Contains(x.CustomerOrderId)) - .ToArrayAsync(); - - if (lineItems.Length > 0) + foreach (var customerOrder in result) { - var lineItemIds = lineItems.Select(x => x.Id).ToArray(); - - await Discounts.Where(x => lineItemIds.Contains(x.LineItemId)).LoadAsync(); - await TaxDetails.Where(x => lineItemIds.Contains(x.LineItemId)).LoadAsync(); - await FeeDetails.Where(x => lineItemIds.Contains(x.LineItemId)).LoadAsync(); - - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) - { - await OrderDynamicPropertyObjectValues.Where(x => lineItemIds.Contains(x.LineItemId)).LoadAsync(); - } - - var configurationItemIds = lineItems.Where(x => x.IsConfigured).Select(x => x.Id).ToArray(); - if (configurationItemIds.Length > 0) - { - await ConfigurationItems - .Where(x => configurationItemIds.Contains(x.LineItemId)) - .Include(x => x.Files) - .AsSplitQuery() - .LoadAsync(); - } + customerOrder.ResetPrices(); } } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithShipments)) + return result; + } + + public virtual async Task> GetPaymentsByIdsAsync(IList ids) + { + if (ids.IsNullOrEmpty()) { - var shipments = await Shipments.Where(x => ids.Contains(x.CustomerOrderId)).ToArrayAsync(); + return Array.Empty(); + } - if (shipments.Any()) - { - var shipmentIds = shipments.Select(x => x.Id).ToArray(); - - await Discounts.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - await TaxDetails.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - await FeeDetails.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - await Addresses.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - await ShipmentItems.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - await ShipmentPackagesPackages.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) - { - await OrderDynamicPropertyObjectValues.Where(x => shipmentIds.Contains(x.ShipmentId)).LoadAsync(); - } - } + var result = await InPayments + .Include(x => x.Discounts) + .Include(x => x.TaxDetails) + .Include(x => x.FeeDetails) + .Include(x => x.Addresses) + .Include(x => x.Transactions) + .Include(x => x.DynamicPropertyObjectValues) + .Where(x => ids.Contains(x.Id)) + .AsSplitQuery() + .ToArrayAsync(); + + return result.Length != 0 ? result : Array.Empty(); + } + + public virtual async Task> GetShipmentsByIdsAsync(IList ids) + { + if (ids.IsNullOrEmpty()) + { + return Array.Empty(); } + var result = await Shipments + .Include(x => x.Discounts) + .Include(x => x.TaxDetails) + .Include(x => x.FeeDetails) + .Include(x => x.Addresses) + .Include(x => x.Items) + .Include(x => x.Packages) + .Include(x => x.DynamicPropertyObjectValues) + .Where(x => ids.Contains(x.Id)) + .AsSplitQuery() + .ToArrayAsync(); + + return result.Length != 0 ? result : Array.Empty(); + } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithRefunds)) + protected virtual async Task LoadInPayments(IList orderIds, CustomerOrderResponseGroup responseGroup) + { + if (!responseGroup.HasFlag(CustomerOrderResponseGroup.WithInPayments)) { - var refunds = await Refunds.Where(x => ids.Contains(x.CustomerOrderId)).ToArrayAsync(); + return; + } - if (refunds.Any()) - { - var refundIds = refunds.Select(x => x.Id).ToArray(); + var query = InPayments + .Include(x => x.Discounts) + .Include(x => x.TaxDetails) + .Include(x => x.FeeDetails) + .Include(x => x.Addresses) + .Include(x => x.Transactions) + .AsQueryable(); - await RefundItems.Where(x => refundIds.Contains(x.RefundId)).LoadAsync(); + if (responseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) + { + query = query.Include(x => x.DynamicPropertyObjectValues); + } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) - { - await OrderDynamicPropertyObjectValues.Where(x => refundIds.Contains(x.RefundId)).LoadAsync(); - } - } + await query + .Where(x => orderIds.Contains(x.CustomerOrderId)) + .AsSplitQuery() + .LoadAsync(); + } + + protected virtual async Task LoadLineItems(IList orderIds, CustomerOrderResponseGroup responseGroup) + { + if (!responseGroup.HasFlag(CustomerOrderResponseGroup.WithItems)) + { + return; } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithCaptures)) + var query = LineItems + .Include(x => x.Discounts) + .Include(x => x.TaxDetails) + .Include(x => x.FeeDetails) + .AsQueryable(); + + if (responseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) { - var captures = await Captures.Where(x => ids.Contains(x.CustomerOrderId)).ToArrayAsync(); + query = query.Include(x => x.DynamicPropertyObjectValues); + } - if (captures.Any()) - { - var captureIds = captures.Select(x => x.Id).ToArray(); + var lineItems = await query + .Where(x => orderIds.Contains(x.CustomerOrderId)) + .AsSplitQuery() + .ToArrayAsync(); - await CaptureItems.Where(x => captureIds.Contains(x.CaptureId)).LoadAsync(); + // ConfigurationItem.Files is two levels deeper than LineItem; keeping it as a + // dedicated query avoids growing the LineItem join tree (and the cartesian + // explosion under AsSingleQuery semantics). + var configurationItemIds = lineItems.Where(x => x.IsConfigured).Select(x => x.Id).ToArray(); + if (configurationItemIds.Length > 0) + { + await ConfigurationItems + .Include(x => x.Files) + .Where(x => configurationItemIds.Contains(x.LineItemId)) + .AsSplitQuery() + .LoadAsync(); + } + } - if (customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) - { - await OrderDynamicPropertyObjectValues.Where(x => captureIds.Contains(x.CaptureId)).LoadAsync(); - } - } + protected virtual async Task LoadShipments(IList orderIds, CustomerOrderResponseGroup responseGroup) + { + if (!responseGroup.HasFlag(CustomerOrderResponseGroup.WithShipments)) + { + return; } - if (!customerOrderResponseGroup.HasFlag(CustomerOrderResponseGroup.WithPrices)) + var query = Shipments + .Include(x => x.Discounts) + .Include(x => x.TaxDetails) + .Include(x => x.FeeDetails) + .Include(x => x.Addresses) + .Include(x => x.Items) + .Include(x => x.Packages) + .AsQueryable(); + + if (responseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) { - foreach (var customerOrder in result) - { - customerOrder.ResetPrices(); - } + query = query.Include(x => x.DynamicPropertyObjectValues); } - return result; + await query + .Where(x => orderIds.Contains(x.CustomerOrderId)) + .AsSplitQuery() + .LoadAsync(); } - public virtual async Task> GetPaymentsByIdsAsync(IList ids) + protected virtual async Task LoadRefunds(IList orderIds, CustomerOrderResponseGroup responseGroup) { - if (ids.IsNullOrEmpty()) + if (!responseGroup.HasFlag(CustomerOrderResponseGroup.WithRefunds)) { - return Array.Empty(); + return; } - var result = await InPayments.Where(x => ids.Contains(x.Id)).ToArrayAsync(); + var query = Refunds + .Include(x => x.Items) + .AsQueryable(); - if (!result.Any()) + if (responseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) { - return Array.Empty(); + query = query.Include(x => x.DynamicPropertyObjectValues); } - ids = result.Select(x => x.Id).ToArray(); - - await Discounts.Where(x => ids.Contains(x.PaymentInId)).LoadAsync(); - await TaxDetails.Where(x => ids.Contains(x.PaymentInId)).LoadAsync(); - await FeeDetails.Where(x => ids.Contains(x.PaymentInId)).LoadAsync(); - await Addresses.Where(x => ids.Contains(x.PaymentInId)).LoadAsync(); - await Transactions.Where(x => ids.Contains(x.PaymentInId)).LoadAsync(); - await OrderDynamicPropertyObjectValues.Where(x => ids.Contains(x.PaymentInId)).LoadAsync(); - - return result; + await query + .Where(x => orderIds.Contains(x.CustomerOrderId)) + .AsSplitQuery() + .LoadAsync(); } - public virtual async Task> GetShipmentsByIdsAsync(IList ids) + protected virtual async Task LoadCaptures(IList orderIds, CustomerOrderResponseGroup responseGroup) { - if (ids.IsNullOrEmpty()) + if (!responseGroup.HasFlag(CustomerOrderResponseGroup.WithCaptures)) { - return Array.Empty(); + return; } - var result = await Shipments.Where(x => ids.Contains(x.Id)).ToArrayAsync(); + var query = Captures + .Include(x => x.Items) + .AsQueryable(); - if (!result.Any()) + if (responseGroup.HasFlag(CustomerOrderResponseGroup.WithDynamicProperties)) { - return Array.Empty(); + query = query.Include(x => x.DynamicPropertyObjectValues); } - ids = result.Select(x => x.Id).ToArray(); - - await Discounts.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - await TaxDetails.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - await FeeDetails.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - await Addresses.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - await ShipmentItems.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - await ShipmentPackagesPackages.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - await OrderDynamicPropertyObjectValues.Where(x => ids.Contains(x.ShipmentId)).LoadAsync(); - - return result; + await query + .Where(x => orderIds.Contains(x.CustomerOrderId)) + .AsSplitQuery() + .LoadAsync(); } public void PatchRowVersion(CustomerOrderEntity entity, byte[] rowVersion) @@ -264,7 +292,6 @@ public virtual async Task RemoveOrdersByIdsAsync(IList ids) Remove(order); } } -#pragma warning restore S109 protected override void Dispose(bool disposing) {