如何在EF Core中使用ICollection<T>处理枚举类型原始集合?
在EF Core中使用ICollection处理枚举原始集合的问题解决方法
问题场景
定义的实体模型:
public sealed class UserEntity : IdentityUser<Guid> { // other stuff public ICollection<MyEnum> MyEnums { get; set; } = []; }
执行查询代码时抛出异常:
var user = await ApplicationDbContext.Users.AsNoTracking().FirstAsync(x => x.PhoneNumberConfirmed);
异常信息:
System.ArgumentException : Expression of type 'System.Collections.Generic.ICollection`1[MyProject.Persistence.Enumerations.MyEnum]' cannot be used for parameter of type 'System.Collections.Generic.IList`1[MyProject.Persistence.Enumerations.MyEnum]' of method 'System.Collections.Generic.IList`1[MyProject.Persistence.Enumerations.MyEnum] PopulateList[MyEnum](System.Collections.Generic.IList`1[MyProject.Persistence.Enumerations.MyEnum], System.Collections.Generic.IList`1[MyProject.Persistence.Enumerations.MyEnum])' (Parameter 'arg0') at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index) at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1) at Microsoft.EntityFrameworkCore.Query.Internal.EntityMaterializerSource.<AddInitializeExpressions>g__CreateMemberAssignment|14_0(Expression parameter, MemberInfo memberInfo, IPropertyBase property, Expression value) at Microsoft.EntityFrameworkCore.Query.Internal.EntityMaterializerSource.AddInitializeExpressions(HashSet`1 properties, ParameterBindingInfo bindingInfo, Expression instanceVariable, List`1 blockExpressions) at Microsoft.EntityFrameworkCore.Query.Internal.EntityMaterializerSource.CreateMaterializeExpression(List`1 blockExpressions, ParameterExpression instanceVariable, Expression constructorExpression, HashSet`1 properties, ParameterBindingInfo bindingInfo) at Microsoft.EntityFrameworkCore.Query.Internal.EntityMaterializerSource.CreateMaterializeExpression(EntityMaterializerSourceParameters parameters, Expression materializationContextExpression) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.EntityMaterializerInjectingExpressionVisitor.CreateFullMaterializeExpression(ITypeBase concreteTypeBase, ValueTuple`4 materializeExpressionContext) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.EntityMaterializerInjectingExpressionVisitor.MaterializeEntity(StructuralTypeShaperExpression shaper, ParameterExpression materializationContextVariable, ParameterExpression concreteEntityTypeVariable, ParameterExpression instanceVariable, ParameterExpression entryVariable) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.EntityMaterializerInjectingExpressionVisitor.ProcessEntityShaper(StructuralTypeShaperExpression shaper) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.EntityMaterializerInjectingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.EntityMaterializerInjectingExpressionVisitor.Inject(Expression expression) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.InjectEntityMaterializers(Expression expression) at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression) at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, LambdaExpression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstAsync[TSource](IQueryable`1 source, Expression`1 predicate, CancellationToken cancellationToken) at MyProject.UnitTests.Requests.BackOffice.Users.Commands.UpdateUser.UpdateUserCommandHandlerTests.Handle_WhenPhoneNumberChanged_ShouldUpdateUserAndPhoneNumberNotBeConfirmed() in D:\Projekty\MyProject\MyProject.UnitTests\Requests\BackOffice\Users\Commands\UpdateUser\UpdateUserCommandHandlerTests.cs:line 38
更新:将类型改为
IList<T>可解决问题,但业务需求需保留ICollection<T>。测试使用InMemory数据库(已知不推荐该做法)。
解决方案
1. 调整属性默认初始化类型
将ICollection<T>的默认实例改为List<T>(List<T>实现了ICollection<T>接口,完全满足业务对接口的需求),这样EF Core在物化时能匹配内部PopulateList方法的参数要求:
public sealed class UserEntity : IdentityUser<Guid> { // other stuff public ICollection<MyEnum> MyEnums { get; set; } = new List<MyEnum>(); }
2. 通过Fluent API配置集合转换
使用EF Core的Fluent API显式配置集合的转换逻辑,确保存储和查询时的类型兼容:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<UserEntity>() .Property(u => u.MyEnums) .HasConversion( v => v.ToList(), // 存储时转为List<T> v => v as ICollection<MyEnum> ?? new HashSet<MyEnum>(v) // 查询时转为ICollection<T>兼容类型 ); }
3. 自定义实体物化器(进阶)
如果上述方法无法满足需求,可以自定义EntityMaterializerSource替换EF Core默认的集合填充逻辑,适配ICollection<T>类型:
public class CustomEntityMaterializerSource : EntityMaterializerSource { public override Expression CreateMaterializeExpression(EntityMaterializerSourceParameters parameters, Expression materializationContextExpression) { var baseExpression = base.CreateMaterializeExpression(parameters, materializationContextExpression); return new ReplacePopulateListVisitor().Visit(baseExpression); } private class ReplacePopulateListVisitor : ExpressionVisitor { protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.Name == "PopulateList" && node.Method.DeclaringType?.FullName == "Microsoft.EntityFrameworkCore.Query.Internal.EntityMaterializerSource") { var elementType = node.Method.GetGenericArguments()[0]; var customPopulateMethod = typeof(CustomEntityMaterializerSource) .GetMethod(nameof(PopulateCollection), BindingFlags.NonPublic | BindingFlags.Static) .MakeGenericMethod(elementType); return Expression.Call(customPopulateMethod, node.Arguments[0], node.Arguments[1]); } return base.VisitMethodCall(node); } } private static ICollection<T> PopulateCollection<T>(ICollection<T> target, IList<T> source) { foreach (var item in source) { target.Add(item); } return target; } }
在DbContext中注册自定义物化器:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.ReplaceService<IEntityMaterializerSource, CustomEntityMaterializerSource>(); }
注:InMemory数据库对集合类型的支持存在局限性,是触发该问题的核心原因。生产环境使用真实数据库(如SQL Server)时,该问题大概率不会出现。
内容的提问来源于stack exchange,提问作者Morasiu
相关产品推荐
相关产品推荐

