使用Dynamic类型的C# Lambda表达式报错:如何在.NET 6中批量复制BaseEntity子类实体?
解决Dynamic仓储调用Where时Lambda表达式的错误问题
你遇到的问题确实是dynamic类型的限制导致的——当调用动态对象的方法时,编译器没办法推断lambda表达式应该被解析成Delegate还是Expression<TDelegate>,而EF Core的Queryable.Where方法需要的是Expression<Func<T, bool>>类型的参数,直接传lambda就会触发这个错误。
下面提供几种可行的解决方案,按推荐程度排序:
方案1:封装泛型辅助方法(最推荐)
我们可以把针对单个实体类型的复制逻辑封装成一个泛型方法,再通过反射调用这个方法。这样在泛型方法内部,因为泛型参数T是明确的,就能正常使用lambda表达式了。
首先在你的Controller(或者单独的服务类)里添加这个泛型方法:
private async Task CopyEntitiesAsync<T>(IRepository<T> repository, int newBaseId, CancellationToken cancellationToken) where T : BaseEntity { var entities = repository.Set().Where(e => e.BaseId == 0).ToList(); foreach (var entity in entities) { entity.Id = 0; entity.BaseId = newBaseId; repository.Set().Add(entity); } await repository.GetContext().SaveChangesAsync(cancellationToken); }
然后修改原来的循环逻辑,通过反射调用这个泛型方法:
var childClasses = Assembly.GetAssembly(typeof(BaseEntity))! .GetTypes() .Where(type => type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(BaseEntity))) .ToList(); foreach (var type in childClasses) { // 获取对应类型的仓储实例 Type repositoryType = typeof(IRepository<>).MakeGenericType(type); var repository = _serviceProvider.GetService(repositoryType)!; // 获取泛型方法并执行 var method = typeof(MyController).GetMethod(nameof(CopyEntitiesAsync), BindingFlags.NonPublic | BindingFlags.Instance)! .MakeGenericMethod(type); await (Task)method.Invoke(this, new object[] { repository, newBaseId, cancellationToken })!; }
这个方案既保留了类型安全,又避开了dynamic的坑,代码的可读性和维护性都更好。
方案2:用反射构建表达式树
如果不想额外封装方法,可以通过反射动态构建e => e.BaseId == 0的表达式树,再传递给Where方法:
foreach (var type in childClasses) { Type repositoryType = typeof(IRepository<>); Type constructed = repositoryType.MakeGenericType(type); dynamic repository = _serviceProvider.GetService(constructed)!; // 构建表达式树:e => e.BaseId == 0 var parameter = Expression.Parameter(type, "e"); var property = Expression.Property(parameter, nameof(BaseEntity.BaseId)); var constant = Expression.Constant(0, typeof(int)); var equalExpr = Expression.Equal(property, constant); var lambda = Expression.Lambda(equalExpr, parameter); // 调用Queryable的Where方法 var set = repository.Set(); var whereMethod = typeof(Queryable).GetMethods() .First(m => m.Name == nameof(Queryable.Where) && m.GetParameters().Length == 2) .MakeGenericMethod(type); var filteredEntities = whereMethod.Invoke(null, new object[] { set, lambda })!; // 转换为List集合 var toListMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.ToList))! .MakeGenericMethod(type); var entities = (IEnumerable)toListMethod.Invoke(null, new object[] { filteredEntities })!; foreach (BaseEntity entity in entities) { entity.Id = 0; entity.BaseId = newBaseId; repository.Set().Add(entity); } await repository.GetContext().SaveChangesAsync(cancellationToken); }
这个方案完全靠反射构建查询,不需要泛型方法,但代码相对繁琐,适合必须用动态方式处理的场景。
方案3:遍历整个Set筛选(简单但性能有限)
正如你想到的,直接遍历整个Set()集合,用if条件筛选符合要求的实体。这个方法代码最简单,但如果数据集很大,性能会很差——因为是在内存中筛选,而非数据库层面过滤:
foreach (var type in childClasses) { Type repositoryType = typeof(IRepository<>); Type constructed = repositoryType.MakeGenericType(type); dynamic repository = _serviceProvider.GetService(constructed)!; var entities = repository.Set().ToList(); foreach (var entity in entities) { if (entity.BaseId == 0) { entity.Id = 0; entity.BaseId = newBaseId; repository.Set().Add(entity); } } await repository.GetContext().SaveChangesAsync(cancellationToken); }
如果你的数据量很小,这个方案可以快速解决问题,但不推荐在大数据集下使用。
内容的提问来源于stack exchange,提问作者Lucas Mello
相关产品推荐
相关产品推荐

