使用一个事务和两个存储库的多个选择语句

问题描述 投票:0回答:1

我刚刚为订单创建了一个有效的 Get 方法,结合了两种方法:

  1. 此方法获取与其关联的所有订单和数据:

        private async Task<(List<Order>, int)> GetOrdersWithDataAsync(NpgsqlConnection connection, NpgsqlTransaction transaction, OrderFilter filtering, Sorting sorting)
    {
        List<Order> orders = new List<Order>();
        int totalCount = 0;
    
        StringBuilder cmdBuilder = new StringBuilder("select o.\"Id\", o.\"ShippingAddressId\", o.\"BillingAddressId\", o.\"PersonId\", o.\"TotalPrice\", " +
                        " pe.\"Id\" as \"PersonId\", pe.\"FirstName\", pe.\"LastName\", pe.\"Email\", sa.\"Id\" as \"ShippingAddressId\" sa.\"StreetName\" as \"ShippingStreetName\", " +
                        "sa.\"StreetNumber\" as \"ShippingStreetNumber\",sa.\"City\" as \"ShippingCity\", sa.\"Zipcode\" as \"ShippingZipcode\", " +
                        "ba.\"Id\" as \"BillingAddressId\", ba.\"StreetName\" as \"BillingStreetName\", ba.\"StreetNumber\" as \"BillingStreetNumber\", ba.\"City\" as \"BillingCity\", " +
                        "ba.\"Zipcode\" as \"BillingZipcode\", Count(*) Over() as \"TotalCount\" from \"Order\" o " +
                        "inner join \"Person\" pe on o.\"PersonId\" = pe.\"Id\" inner join \"Address\" sa on sa.\"Id\" = o.\"ShippingAddressId\" inner join \"Address\" ba on ba.\"Id\" = " +
                        "o.\"BillingAddressId\"");
    
        NpgsqlCommand cmd = new NpgsqlCommand("" , connection);
    
    
        cmdBuilder.Append(" WHERE 1 = 1");
        if(!string.IsNullOrEmpty(filtering.SearchQuery))
        {
            cmdBuilder.Append(" AND pe.\"FirstName\" LIKE \'%@SearchQuery%\' OR pe.\"LastName\" LIKE \'%@SearchQuery%\'");
            cmd.Parameters.AddWithValue("@SearchQuery", filtering.SearchQuery);
        } 
        if(filtering.MinPrice.HasValue && filtering.MinPrice.Value != 0)
        {
            cmdBuilder.Append(" AND o.\"TotalPrice\" > @MinPrice");
            cmd.Parameters.AddWithValue("@MinPrice", filtering.MinPrice);
        }
        if (filtering.MaxPrice.HasValue && filtering.MaxPrice.Value != 0)
        {
            cmdBuilder.Append(" AND o.\"TotalPrice\" < @MaxPrice");
            cmd.Parameters.AddWithValue("@MaxPrice", filtering.MaxPrice);
        }
    
        cmd.CommandText = cmdBuilder.ToString();
        cmd.Transaction = transaction;
        cmd.Connection = connection;
    
        NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
        try
        {
            using (reader)
            {
                if (reader.HasRows)
                {
                    while (reader.Read())
                    {
                        Guid orderId = (Guid)reader["Id"];
    
                        Order order = new Order();
                        order.ShippingAddress = new Address();
                        order.BillingAddress = new Address();
                        order.Products = new List<Product>();
    
                        order.Id = (Guid)reader["Id"];
                        order.TotalPrice = (decimal)reader["TotalPrice"];
                        order.Person = new Person();
                        order.Person.Id = (Guid)reader["PersonId"];
                        order.Person.FirstName = (string)reader["FirstName"];
                        order.Person.LastName = (string)reader["LastName"];
                        order.Person.Email = (string)reader["Email"];
                        order.PersonId = (Guid)reader["PersonId"];
                        order.ShippingAddress.Id = (Guid)reader["ShippingAddressId"];
                        order.ShippingAddress.StreetName = (string)reader["ShippingStreetName"];
                        order.ShippingAddress.StreetNumber = (string)reader["ShippingStreetNumber"];
                        order.ShippingAddress.City = (string)reader["ShippingCity"];
                        order.ShippingAddress.ZipCode = (int)reader["ShippingZipcode"];
                        order.BillingAddress.Id = (Guid)reader["BillingAddressId"];
                        order.BillingAddress.StreetName = (string)reader["BillingStreetName"];
                        order.BillingAddress.StreetNumber = (string)reader["BillingStreetNumber"];
                        order.BillingAddress.City = (string)reader["BillingCity"];
                        order.BillingAddress.ZipCode = (int)reader["BillingZipcode"];
    
                        orders.Add(order);
                    }
                }
                return (orders, totalCount);
            }
        }
        catch(Exception ex)
        {
            throw ex;
        }
    
    }
    
  2. 从我注入到 OrderRepository 的产品存储库获取每个订单的产品的方法:

        public async Task<List<Order>> GetProductsByOrderIdAsync(List<Order> orders, NpgsqlConnection connection, NpgsqlTransaction transaction)
    {
        foreach (var order in orders)
        {
            List<Product> products = new List<Product>();
    
            try
            {
                NpgsqlCommand cmd = new NpgsqlCommand("SELECT p.\"Id\", p.\"Name\", p.\"Price\", po.\"ProductQty\" as \"Quantity\" FROM \"Product\" as p INNER JOIN \"ProductOrder\" as po ON p.\"Id\" = po.\"ProductId\" WHERE po.\"OrderId\" = @id", connection);
                cmd.Parameters.AddWithValue("id", order.Id);
                cmd.Transaction = transaction;
                cmd.Connection = connection;
    
                NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
                if (reader.HasRows) 
                {
                    while (reader.Read())
                    {
                        Product product = new Product();
                        product.Id = (Guid)reader["Id"];
                        product.Name = (string)reader["Name"];
                        product.Price = (decimal)reader["Price"];
                        product.Quantity = (int)reader["Quantity"];
                        products.Add(product);
                    }
                }
                reader.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            order.Products = products;
        }
    
        return orders;
    }
    

这两件事都是通过这个完成的:

        public async Task<PagedList<Order>> GetOrdersAsync(Paginate paginate, OrderFilter filtering, Sorting sorting)
    {
        NpgsqlConnection connection = new NpgsqlConnection(_connectionProvider.GetConnectionString());
        await connection.OpenAsync();

        try
        {
            using (connection)
            {
                NpgsqlTransaction transaction = connection.BeginTransaction();

                try
                {
                    (List<Order> orders, int totalCount) = await GetOrdersWithDataAsync(connection, transaction, filtering, sorting);

                    List<Order> ordersWithProducts = await _productRepository.GetProductsByOrderIdAsync(orders, connection, transaction);

                    PagedList<Order> pagedOrders = new PagedList<Order>()
                    {
                        Results = ordersWithProducts,
                        CurrentPage = paginate.PageNumber,
                        PageSize = paginate.PageSize,
                        TotalCount = orders.Count
                    };

                    if (pagedOrders != null && pagedOrders.TotalCount > 0)
                    {
                        transaction.Commit();
                        return pagedOrders;
                    }
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    throw ex;
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }

        return null;
    }

让我担心的是GetProductsByOrderId方法中每个订单的select语句,有没有更好的方法来做到这一点?我已经通过检查现有订单并向其中添加产品,仅使用 OrderRepository 中的一个查询来完成此操作,但这似乎也很慢。

此外,使用两个单独的事务并在第二个事务(针对产品)失败时回滚第一个事务是否会更好?

c# sql .net repository npgsql
1个回答
0
投票

只执行一批,返回仅两个结果集可能要好得多,而不是进行有效地进行 N+1 次查询(N 是您获得的订单数)。

只需获取所有

Orders
并将它们存储在字典中,然后获取第二个结果集并使用字典将
Products
映射到
Order
来检索相关对象。

您实际上也没有进行任何分页。考虑使用Keyset Pagination,而不是Rowset Pagination。这意味着您传入一个起始键(例如 OrderId)并从那里向前翻页,而不是执行看起来很酷但实际上很慢的

OFFSET...FETCH
。另请参阅是否有更好的选项来应用分页而不在 SQL Server 中应用 OFFSET?我没有为此添加任何代码,但它应该适合您的其他动态过滤器构建。

  • 提高代码的可读性:
    • 使用逐字字符串
      @""
      ,以便您可以嵌入换行符。
    • 使用对象初始值设定项语法。
    • 正确格式化您的 SQL。
    • 除非必要,否则不要引用列名称。
    • 考虑使用 Dapper 等映射器将每一行实际转换为对象。
  • catch ... throw ex;
    完全是错误的。无论如何,仅捕获重新遍历都是浪费时间,并且
    throw ex;
    将擦除堆栈跟踪。至少要做
    throw;
    。或者如果您不想处理它,最好根本不要抓住它。
  • 明确指定参数的类型。
  • if (reader.HasRows)
    是不必要的,如果没有行,循环根本不会运行。
  • 您没有设置
    totalCount
    ,但您无论如何都不需要它,因为列表本身有一个
    Count
  • 使用异步版本的
    await reader.ReadAsync()
    。还可以考虑传递取消令牌。
private async Task<ICollection<Order>> GetOrdersWithDataAsync(OrderFilter filtering, Sorting sorting)
{
    using NpgsqlConnection connection = new NpgsqlConnection(_connectionProvider.GetConnectionString());
    using NpgsqlCommand cmd = new NpgsqlCommand("" , connection);

    var conditions = "WHERE 1 = 1";
    if(!string.IsNullOrEmpty(filtering.SearchQuery))
    {
        conditions += @"
  AND pe.FirstName LIKE '%' + @SearchQuery+ '%' OR pe.LastName LIKE '%' + @SearchQuery+ '%'");
        cmd.Parameters.Add("@SearchQuery", NpgsqlDbType.Text).Value = filtering.SearchQuery;
    } 
    if(filtering.MinPrice != 0)
    {
        conditions += @"
  AND o.TotalPrice > @MinPrice");
        cmd.Parameters.Add("@MinPrice", NpgsqlDbType.Numeric).Value = filtering.MinPrice.Value;
    }
    if (filtering.MaxPrice != 0)
    {
        conditions += @"
  AND o.TotalPrice < @MaxPrice");
        cmd.Parameters.Add("@MaxPrice", NpgsqlDbType.Numeric).Value filtering.MaxPrice;
    }

    cmd.CommandText = @$"
select
  o.Id,
  o.ShippingAddressId,
  o.BillingAddressId,
  o.PersonId,
  o.TotalPrice,
  pe.Id as PersonId,
  pe.FirstName,
  pe.LastName,
  pe.Email,
  sa.Id as ShippingAddressId,
  sa.StreetName as ShippingStreetName,
  sa.StreetNumber as ShippingStreetNumber,
  sa.City as ShippingCity,
  sa.Zipcode as ShippingZipcode,
  ba.Id as BillingAddressId,
  ba.StreetName as BillingStreetName,
  ba.StreetNumber as BillingStreetNumber,
  ba.City as BillingCity,
  ba.Zipcode as BillingZipcode
from ""Order"" o
inner join Person pe on o.PersonId = pe.Id
inner join Address sa on sa.Id = o.ShippingAddressId
inner join Address ba on ba.Id = o.BillingAddressId
{conditions}
;

SELECT
  p.Id,
  p.Name,
  p.Price,
  po.ProductQty as Quantity,
  po.OrderId
FROM Product as p
JOIN ProductOrder as po ON p.Id = po.ProductId
JOIN ""Order"" o ON o.Id = po.OrderId
inner join Person pe on o.PersonId = pe.Id
{conditions}
";

    var orders = new Dictionary<Guid, Order>();

    await connection.OpenAsync();
    using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();

    while (await reader.ReadAsync())
    {
        Order order = new Order {
            Products = new List<Product>(),
            Id = (Guid)reader["Id"],
            TotalPrice = (decimal)reader["TotalPrice"],
            Person = new Person {
                Id = (Guid)reader["PersonId"],
                FirstName = (string)reader["FirstName"],
                LastName = (string)reader["LastName"],
                Email = (string)reader["Email"],
            },
            PersonId = (Guid)reader["PersonId"],
            ShippingAddress = new Address( {
                Id = (Guid)reader["ShippingAddressId"],
                StreetName = (string)reader["ShippingStreetName"],
                StreetNumber = (string)reader["ShippingStreetNumber"],
                City = (string)reader["ShippingCity"],
                ZipCode = (int)reader["ShippingZipcode"],
            },
            BillingAddress = new Address {
                Id = (Guid)reader["BillingAddressId"],
                StreetName = (string)reader["BillingStreetName"],
                StreetNumber = (string)reader["BillingStreetNumber"],
                City = (string)reader["BillingCity"],
                ZipCode = (int)reader["BillingZipcode"],
            },
        };

        orders.Add(order.Id, order);
    }

    await reader.NextResultAsync();

    while (await reader.ReadAsync())
    {
        Product product = new Product {
            Id = (Guid)reader["Id"],
            Name = (string)reader["Name"],
            Price = (decimal)reader["Price"],
            Quantity = (int)reader["Quantity"],
        };
        if(orders.TryGetValue((Guid)reader["Id"], order)
            order.Products.Add(product);
    }

    return orders.Values;
}

类似的选项可能是使用 Postgres 的 JSON 功能来构造

Products
的 JSON 数组。这意味着您可以一次性完成所有操作。我不确定使用 NpgSql 从中获取数据的确切方法,但 Postgres 语法会在子查询中使用类似
JSON_AGG
聚合的东西。

© www.soinside.com 2019 - 2024. All rights reserved.