使用 EntityFrameworkCore 5 将 C# 9 记录映射为值对象

Mapping C# 9 record as value object with EntityFrameworkCore 5

我试图在 c# class 中使用 record 类型作为值对象,但是当我尝试使用 efcore 5 映射这些类型时它抛出异常。

我的class是:

public record ZipCode
    {
        private static readonly char ZIPCODE_SEPARATOR = '-';
        private static readonly string INVALID_ZIPCODE_MESSAGE = "invalid zip code";
        private static readonly string ZIPCODE_WITH_NOT_ONLY_NUMBERS = "Invalid zip code value, must have only digits";

        private char[] _value;
        public string Value => new string(_value);

        public ZipCode(string zipCode)
        {
            _value = new char[8];
            setZipCodeValue(zipCode);
        }

        public ZipCode(ZipCode zipCode)
        {
            this._value = zipCode.Value.ToCharArray();
        }
        
        private void setZipCodeValue(string valueAsString)
        {
            if (valueAsString.Length < 8 || valueAsString.Length > 9)
            {
                throw new InvalidOperationException(INVALID_ZIPCODE_MESSAGE);
            }

            var zipCodeAsCharArray = valueAsString.ToCharArray();
            int valueArrayIndex = (int) decimal.Zero;

            foreach (var ch in zipCodeAsCharArray)
            {
                if (char.IsDigit(ch) )
                {
                    _value[valueArrayIndex] = ch;
                    valueArrayIndex++;
                }
                else if (ch.Equals(ZIPCODE_SEPARATOR))
                {
                    continue;
                }
                else
                {
                    throw new InvalidOperationException(ZIPCODE_WITH_NOT_ONLY_NUMBERS);
                }
            }
        }
    }

主要对象:

public class DeliveryServicePriceTime
    {
        public Guid Id { get; private set; }

        public ZipCode SourceZipCode { get; private set; }
        public ZipCode DestinationZipCode { get; private set; }

        public DateTime RequestDateTime { get; private set; }

        private IList<ServicePriceTime> _servicesPriceTime;
        public IReadOnlyList<ServicePriceTime> ServicesPriceTimes => _servicesPriceTime.ToList();
        
        private DeliveryServicePriceTime()
        {
            Id = Guid.NewGuid();
        }

        public DeliveryServicePriceTime(ZipCode sourceZipCode, ZipCode destinationZipCode, IList<ServicePriceTime> servicesPriceTime) : this()
        {
            SourceZipCode = sourceZipCode;
            DestinationZipCode = destinationZipCode;
            _servicesPriceTime = servicesPriceTime;
        }

        public DeliveryServicePriceTime(string sourceZipCode, string destinationZipCode, IList<ServicePriceTime> servicesPriceTime) : this()
        {
            RequestDateTime = DateTime.UtcNow;
            this.SourceZipCode = new ZipCode(sourceZipCode);
            this.DestinationZipCode = new ZipCode(destinationZipCode);
            _servicesPriceTime = servicesPriceTime;                
        }
    }

我还有一个record,下面的代码:

public record ServicePriceTime(DeliveryService DeliveryService, decimal Price, int Time);

我正在使用 fluent api 通过以下代码将属性映射到 table 结构:

public class DeliveryServicePriceTimeMappingConfiguration : IEntityTypeConfiguration<DeliveryServicePriceTime>
    {
        public void Configure(EntityTypeBuilder<DeliveryServicePriceTime> builder)
        {
            builder.ToTable("DeliveryCalcEvent");

            builder.HasKey(e => e.Id);
            builder.Property(e => e.RequestDateTime).IsRequired();

            builder.OwnsOne(e => e.SourceZipCode)
                   .Property(e => e.Value)
                   .UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction)
                   .IsRequired();

            builder.OwnsOne(e => e.DestinationZipCode)
                   .Property(e => e.Value)
                   .UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction)
                   .IsRequired();

            builder.OwnsMany(e => e.ServicesPriceTimes, ac =>
            {
                ac.OwnsOne(m => m.DeliveryService, pc =>
                {
                    pc.Property(p => p.ServiceCode).IsRequired();
                    pc.Property(p => p.ServiceName).IsRequired();
                }).UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);
                ac.Property(m => m.Price).IsRequired();
                ac.Property(m => m.Time).IsRequired();
            }).UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);                
        }
    }

我正在使用该方法确保在启动时创建数据库 class,但是当我尝试启动应用程序时 entity framework 无法理解我的记录对象。

这是我的堆栈跟踪:

Microsoft.AspNetCore.Hosting.Diagnostics[6]
      Application startup exception
      System.InvalidOperationException: No suitable constructor was found for entity type 'DeliveryServicePriceTime.DestinationZipCode#ZipCode'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'zipCode' in 'DeliveryServicePriceTime.DestinationZipCode#ZipCode(string zipCode)'; cannot bind 'zipCode' in 'DeliveryServicePriceTime.DestinationZipCode#ZipCode(ZipCode zipCode)'.
         at Microsoft.EntityFrameworkCore.Metadata.Conventions.ConstructorBindingConvention.ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
         at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalizing(IConventionModelBuilder modelBuilder)
         at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalizing(IConventionModelBuilder modelBuilder)
         at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
         at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
         at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
         at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
         at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
         at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
         at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
         at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
         at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
         at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
         at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
         at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetRelationalService[TService](IInfrastructure`1 databaseFacade)
         at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
         at API.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) in /API/Startup.cs:line 52
         at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
         at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
         at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
         at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
         at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
         at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
         at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
         at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
Unhandled exception. System.InvalidOperationException: No suitable constructor was found for entity type 'DeliveryServicePriceTime.DestinationZipCode#ZipCode'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'zipCode' in 'DeliveryServicePriceTime.DestinationZipCode#ZipCode(string zipCode)'; cannot bind 'zipCode' in 'DeliveryServicePriceTime.DestinationZipCode#ZipCode(ZipCode zipCode)'.
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.ConstructorBindingConvention.ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalizing(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalizing(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
   at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetRelationalService[TService](IInfrastructure`1 databaseFacade)
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
   at API.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) in /API/Startup.cs:line 52
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
   at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) 

有人知道如何解决这个问题吗?

However, if EF Core finds a parameterized constructor with parameter names and types that match those of mapped properties, then it will instead call the parameterized constructor with values for those properties and will not set each property explicitly types with constructors Ef core

我好像你映射的 属性 名称是值,你的构造函数参数名称是邮政编码。