You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:54:54