SqlBulkTools泛型方法动态指定MatchTargetOn匹配列的实现问询
解决方案
根因说明
SqlBulkTools的MatchTargetOn方法要求传入Expression<Func<T, object>>类型的属性选择表达式,框架内部会直接解析表达式树提取属性名生成SQL匹配条件。你之前直接传入x => x.GetMatchOn()的方法调用表达式,框架无法解析为具体的表字段,因此抛出异常。
方案1:基于现有IModel接口动态构建表达式树
你只需要在泛型方法中,根据GetMatchOn返回的PropertyInfo手动构造符合要求的属性选择表达式即可,修改后的泛型方法代码如下:
public void InsertOrUpdateTableMatchOnString<T>(string tableName, List<T> models) where T : class, IModel, new() { BulkOperations bulk = new BulkOperations(); using (TransactionScope trans = new TransactionScope()) { using (SqlConnection conn = new SqlConnection(MyConString)) { // 1. 获取当前模型的匹配属性 var modelInstance = new T(); PropertyInfo matchProp = modelInstance.GetMatchOn(); // 2. 动态构建属性选择表达式 x => x.匹配属性 ParameterExpression paramExpr = Expression.Parameter(typeof(T), "x"); MemberExpression propAccessExpr = Expression.Property(paramExpr, matchProp); // 转换为object类型匹配方法入参要求 UnaryExpression convertExpr = Expression.Convert(propAccessExpr, typeof(object)); Expression<Func<T, object>> matchLambda = Expression.Lambda<Func<T, object>>(convertExpr, paramExpr); // 3. 构造批量操作配置 var bulkConfig = bulk.Setup<T>() .ForCollection(models) .WithTable(tableName) .AddAllColumns() .BulkInsertOrUpdate() .SetIdentityColumn(x => x.Id) .MatchTargetOn(x => x.AgreementId); // 4. 传入动态构建的匹配表达式 bulkConfig.MatchTargetOn(matchLambda); // 提交操作 bulkConfig.Commit(conn); } trans.Complete(); } }
注意:需要给泛型约束加上
new(),保证T有无参构造函数可以实例化调用GetMatchOn方法。如果你的模型不允许有无参构造,可以把GetMatchOn改为IModel的静态抽象方法(C# 11+支持),避免实例化开销。
方案2:更简洁的特性标记方案(推荐)
你可以去掉IModel里的GetMatchOn方法,改用特性标记匹配列,代码更简洁易维护:
- 首先定义匹配列特性:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class MatchTargetColumnAttribute : Attribute { }
- 修改模型,给对应匹配列加标记:
public class Company : IModel { public long? Id { get; set; } public int AgreementId { get; set; } // 给匹配列加标记 [MatchTargetColumn] public string Name { get; set; } // 其余属性省略 }
- 修改IModel接口,移除
GetMatchOn定义:
public interface IModel { long? Id { get; set; } int AgreementId { get; set; } }
- 泛型方法直接通过反射获取带标记的属性:
public void InsertOrUpdateTableMatchOnString<T>(string tableName, List<T> models) where T : class, IModel { BulkOperations bulk = new BulkOperations(); using (TransactionScope trans = new TransactionScope()) { using (SqlConnection conn = new SqlConnection(MyConString)) { // 直接反射查找带匹配标记的属性 PropertyInfo matchProp = typeof(T).GetProperties() .FirstOrDefault(p => p.GetCustomAttribute<MatchTargetColumnAttribute>() != null); if (matchProp == null) { throw new InvalidOperationException($"模型{typeof(T).Name}未指定匹配列,请给对应属性添加[MatchTargetColumn]标记"); } // 后续构建表达式、调用方法的逻辑和方案1完全一致 ParameterExpression paramExpr = Expression.Parameter(typeof(T), "x"); MemberExpression propAccessExpr = Expression.Property(paramExpr, matchProp); UnaryExpression convertExpr = Expression.Convert(propAccessExpr, typeof(object)); Expression<Func<T, object>> matchLambda = Expression.Lambda<Func<T, object>>(convertExpr, paramExpr); var bulkConfig = bulk.Setup<T>() .ForCollection(models) .WithTable(tableName) .AddAllColumns() .BulkInsertOrUpdate() .SetIdentityColumn(x => x.Id) .MatchTargetOn(x => x.AgreementId); bulkConfig.MatchTargetOn(matchLambda); bulkConfig.Commit(conn); } trans.Complete(); } }
内容的提问来源于stack exchange,提问作者8nette
相关产品推荐
相关产品推荐

