我刚刚为订单创建了一个有效的 Get 方法,结合了两种方法:
此方法获取与其关联的所有订单和数据:
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;
}
}
从我注入到 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 中的一个查询来完成此操作,但这似乎也很慢。
此外,使用两个单独的事务并在第二个事务(针对产品)失败时回滚第一个事务是否会更好?
只执行一批,返回仅两个结果集可能要好得多,而不是进行有效地进行 N+1 次查询(N 是您获得的订单数)。
只需获取所有
Orders
并将它们存储在字典中,然后获取第二个结果集并使用字典将 Products
映射到 Order
来检索相关对象。
您实际上也没有进行任何分页。考虑使用Keyset Pagination,而不是Rowset Pagination。这意味着您传入一个起始键(例如 OrderId)并从那里向前翻页,而不是执行看起来很酷但实际上很慢的
OFFSET...FETCH
。另请参阅是否有更好的选项来应用分页而不在 SQL Server 中应用 OFFSET?我没有为此添加任何代码,但它应该适合您的其他动态过滤器构建。
@""
,以便您可以嵌入换行符。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
聚合的东西。