Els*_*nov 0 .net c# dapper .net-core
我有两个模型供应商和用户。每个用户可能只有一个供应商,也可能没有。供应商可能有多个用户。
楷模:
public class AppUser
{
public int Id { get; set; }
public string? FullName { get; set; }
public string? Position { get; set; }
public int StatusId { get; set; }
public int VendorId { get; set; }
}
public class Vendor
{
public int VendorId { get; set; }
public string? VendorCode { get; set; }
public string? VendorName { get; set; }
public int StatusId { get; set; }
public List<AppUser> CompanyUsers { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
SQL 表:
我需要选择具有相关用户的所有供应商(如果存在)。我的查询:
SELECT
v.VendorId
,v.VendorCode
,v.VendorName
,v.Status StatusId
,(CASE
WHEN v.Status = 0 THEN 'Draft'
WHEN v.Status = 1 THEN 'Open'
WHEN v.Status = 2 THEN 'Closed'
WHEN v.Status = 3 THEN 'Blacklisted'
END) StatusName
,au.Id
,au.FullName
,au.Position
,au.StatusId
,(CASE
WHEN au.StatusId = 0 THEN 'Draft'
WHEN au.StatusId = 1 THEN 'Open'
WHEN au.StatusId = 2 THEN 'Closed'
END) StatusName
FROM Procurement.Vendors v
LEFT JOIN Config.AppUser au
ON v.VendorId = au.VendorId
Run Code Online (Sandbox Code Playgroud)
由于只有 ID 为 20 的供应商有用户,因此它出现 3 次,这是预期行为。现在我想使用Dapper的splitOn函数来映射供应商20下的所有用户。我按用户的Id列进行分割。
public async Task<IEnumerable<Vendor>?> GetAllVendors(int businessUnitId)
{
var currentUser = await _appUserService.GetCurrentUserAsync();
var p = new DynamicParameters();
p.Add("@UserId", currentUser.Id, DbType.Int32, ParameterDirection.Input);
p.Add("@BusinessUnitId", businessUnitId, DbType.Int32, ParameterDirection.Input);
using IDbConnection cn = new SqlConnection(_sqlDataAccess.ConnectionString);
return await cn.QueryAsync<Vendor, AppUser, Vendor>("dbo.SP_ZZZTest",
(vendor, user) =>
{
if (vendor.CompanyUsers == null && user != null) { vendor.CompanyUsers = new(); };
if (user != null) { vendor.CompanyUsers.Add(user); };
return vendor;
},
param: p,
splitOn: "Id",
commandType: CommandType.StoredProcedure);
}
Run Code Online (Sandbox Code Playgroud)
因此,Dapper 没有将用户映射到单个供应商下。而是将每个用户映射为用户列表,其中单个项目在 3 行上复制供应商的数据。
我做错了什么?
是的,这是一个常见的“问题”。但一旦您了解了 lambda 调用背后的流程,解决起来就非常简单了。
lambda 表达式接收查询创建的三个记录,在调用 lambda 之前,Dapper 在splitOn配置点将每个记录一分为二。在此过程中,将为处理的每一行创建一个新的 Vendor 和新的 AppUser 实例。
因此,第二次/第三次调用时收到的 Vendor 实例与第一次/第二次调用时收到的 Vendor 实例不同。Dapper没有这种处理逻辑(我认为从性能角度避免它是正确的)。因此上面的代码将每个 AppUser 添加到 Vendor 的三个不同实例。
由您来“发现”供应商收到的包含与先前呼叫相同的数据。但如果有某种唯一的密钥来标识一个Vendor(记录的PK),这个问题就很容易解决
因此,这个“问题”可以使用字典来解决,其中键是供应商的 PK,并且您将 Dapper 传递的每个唯一供应商数据存储在该字典键下。然后您可以检查收到的 Vendor 数据是否已在字典中,并使用字典实例添加 AppUser。
Dictionary<int, Vendor> result = new Dictionary<int, Vendor>();
.....
using IDbConnection cn = new SqlConnection(_sqlDataAccess.ConnectionString);
_ = await cn.QueryAsync<Vendor, AppUser, Vendor>("dbo.SP_ZZZTest",
(vendor, user) =>
{
if(!result.ContainsKey(vendor.vendorId))
{
vendor.CompanyUsers = new();
result.Add(vendor.vendorId, vendor);
}
Vendor current = result[vendor.vendorId];
if (user != null)
current.CompanyUsers.Add(user);
return vendor;
},
param: p,
splitOn: "Id",
commandType: CommandType.StoredProcedure);
// A VERY IMPORTANT POINT...
// You want to return the Vendors stored in the Values of the
// Dictionary, not the Vendors returned by the QueryAsync call
return result.Values;
Run Code Online (Sandbox Code Playgroud)