简体   繁体   English

EF Core 3.1 Fluent API 未检测到实体上的更改

[英]EF Core 3.1 Fluent API does not detect the changes on entity

I am using EF Core 3.1 with fluent API and configuring the entities in a specific Map objects like below:我正在使用 EF Core 3.1 和流利的 API 并在特定的 Map 对象中配置实体,如下所示:

public class CouponMap: IEntityTypeConfiguration < Coupon > {
    public void Configure(EntityTypeBuilder < Coupon > builder) {
        builder.HasKey(t = >t.Id);
        builder.Property(t = >t.Id).ValueGeneratedOnAdd();
        builder.Property(e = >e.Name).HasMaxLength(120).IsRequired();
        builder.Property(e = >e.Code).HasMaxLength(120).IsRequired();
        builder.Property(e = >e.Value).HasColumnType("decimal(19,4)").IsRequired();

        // Relations...
        builder.HasOne < AppUser > (e = >e.CreatedByUser).WithMany().HasForeignKey(e = >e.DeletedBy).IsRequired();
        builder.HasOne < AppUser > (e = >e.DeletedByUser).WithMany().HasForeignKey(e = >e.CreatedBy).IsRequired();
        builder.HasOne < AppUser > (e = >e.UpdatedByUser).WithMany().HasForeignKey(e = >e.UpdatedBy);

        // Set table name
        builder.ToTable("Coupon", "dbo");
    }
}

Then I noticed on debug console, there are warnings for my decimal fields such as:然后我注意到在调试控制台上,我的十进制字段有警告,例如:

Microsoft.EntityFrameworkCore.Model.Validation[30000] No type was specified for the decimal column 'Value' on entity type 'Coupon'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'HasColumnType()'.

But I actually alread defined that HasColumnType in my map object as you can see above.但实际上我已经在我的 map object 中定义了HasColumnType ,如上所示。 First I thought this might be just a warning because it cannot detect the changes "somehow" but when I check my table in db, I saw that Coupon table Value column was actually using default (18,2) as decimal precision-scale not (19,4).首先,我认为这可能只是一个警告,因为它无法“以某种方式”检测到更改,但是当我在 db 中检查我的表时,我看到优惠券表值列实际上使用默认值 (18,2) 作为十进制精度刻度而不是 ( 19,4)。

Coupon object Value field is defined like below:优惠券 object 值字段定义如下:

public class Coupon : IHasIdColumn<int>
    {         
        public int Id { get; set; }
        public string Name { get; set; }
        .
        .
        .      
        public decimal Value { get; set; } // <== the problem guy!
        .
        .           
   }

在此处输入图像描述

Also if you notice, Code and Name fields were even created as NVARCHAR(MAX) eventhough they have set as HasMaxLength(120) in map object.此外,如果您注意到,代码和名称字段甚至被创建为NVARCHAR(MAX) ,即使它们在 map object 中设置为HasMaxLength(120)

After that I check my model snapshot to make sure how my entity script is generated just to confirm that it ignored my decimal(19,4) and confirmed that it actually created the table script in model snaphot as:之后,我检查了我的 model 快照以确保我的实体脚本是如何生成的,只是为了确认它忽略了我的小数(19,4)并确认它实际上在 model 快照中创建了表脚本:

modelBuilder.Entity("MyProject.Core.DomainModels.Coupon", b = >{
    b.Property < int > ("Id").ValueGeneratedOnAdd().HasColumnType("int").HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
    .
    .
    b.Property < decimal > ("Value").HasColumnType("decimal(18,2)");
    .  
    .
    . goes with other properties
}

Then that got me at that point, so I tried updating other properties in my CouponMap object but none of those changes has been detected.然后这让我明白了,所以我尝试更新我的CouponMap object 中的其他属性,但没有检测到这些更改。 For example I tried changing one my column max length from 120 to 500 like below and add new migration but my up and down functions were empty.例如,我尝试将我的列最大长度从 120 更改为 500,如下所示,并添加新的迁移,但我updown函数是空的。

public class CouponMap: IEntityTypeConfiguration < Coupon > {
    public void Configure(EntityTypeBuilder < Coupon > builder) {
        builder.HasKey(t = >t.Id);
        builder.Property(t = >t.Id).ValueGeneratedOnAdd();
        builder.Property(e = >e.Name).HasMaxLength(500).IsRequired(); // changed this but not detected in migration...
        builder.Property(e = >e.Code).HasMaxLength(500).IsRequired(); // also changed this but not detectedin migration...
        builder.Property(e = >e.Value).HasColumnType("decimal(19,4)").IsRequired(); // this somehow was not being detected anyways so I tried other 2 columns above

        // Relations...
        builder.HasOne < AppUser > (e = >e.CreatedByUser).WithMany().HasForeignKey(e = >e.DeletedBy).IsRequired();
        builder.HasOne < AppUser > (e = >e.DeletedByUser).WithMany().HasForeignKey(e = >e.CreatedBy).IsRequired();
        builder.HasOne < AppUser > (e = >e.UpdatedByUser).WithMany().HasForeignKey(e = >e.UpdatedBy);

        // Set table name
        builder.ToTable("Coupon", "dbo");
    }
}

I know that I can use System.ComponentModel.DataAnnotations.Schema and decorate my property like below:我知道我可以使用System.ComponentModel.DataAnnotations.Schema并像下面这样装饰我的属性:

[Column(TypeName = "decimal(19,4)")]
public decimal Value { get; set; }

If I do this and create new migration, I see that it detects the change right away and my up and down functions looks like below:如果我这样做并创建新的迁移,我会看到它立即检测到更改,并且我updown函数如下所示:

 protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AlterColumn<decimal>(
        name: "Value",
        table: "Coupon",
        type: "decimal(19,4)",
        nullable: false,
        oldClrType: typeof(decimal),
        oldType: "decimal(18,2)");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AlterColumn<decimal>(
        name: "Value",
        table: "Coupon",
        type: "decimal(18,2)",
        nullable: false,
        oldClrType: typeof(decimal),
        oldType: "decimal(19,4)");
}

But I want to keep my domain objects as clean as possible and only use EntityMap classes to define any entity type configuration related to database (otherwise what is the point of using FluentAPI?)但是我想保持我的域对象尽可能干净,并且只使用EntityMap类来定义与数据库相关的任何实体类型配置(否则使用 FluentAPI 有什么意义?)

I was thinking maybe my entity map objects useless, but the relations that I created in entity map objects are working perfectly fine.我在想也许我的实体 map 对象没用,但是我在实体 map 对象中创建的关系工作得很好。

I tried cleaning the solution, rebuilding, restarting VS but none of them worked.我尝试清理解决方案、重建、重新启动 VS,但它们都没有奏效。 Any suggestion or am I missing something here?有什么建议还是我在这里遗漏了什么?

The problem was, those mapping objects Configure functions were not being called at all.问题是,那些映射对象配置函数根本没有被调用。

Solution was simple:解决方案很简单:

protected override void OnModelCreating(ModelBuilder builder)
        { 
            // configuring mappings one by one like below..
            //  builder.ApplyConfiguration(new CouponMap());
            //  builder.ApplyConfiguration(new WebOrderMap());

            // or configuring all the mappings by using assembly..
            builder.ApplyConfigurationsFromAssembly(typeof(CouponMap).Assembly);

            base.OnModelCreating(builder);
        } 

Thanks to @Ivan Stoev who pointed me to throw an exception in those mapping objects to see if they would ever triggered... It fooled me because all my relations were perfectly fine so I thought it is because of my mapping objects worked fine.感谢@Ivan Stoev,他指出我在这些映射对象中抛出异常以查看它们是否会触发......它愚弄了我,因为我所有的关系都非常好,所以我认为这是因为我的映射对象工作正常。 But it was actually just because of the EF Conventions and my object mappings were never run.但这实际上只是因为 EF 约定和我的 object 映射从未运行过。

This means Entity Framework will provide a default precision to the database.这意味着 Entity Framework 将为数据库提供默认精度。 The default is typically (18, 2).默认值通常为 (18, 2)。 That means it will store 18 total digits, with 2 of those digits being to the right of the decimal point.这意味着它将存储总共 18 个数字,其中 2 个数字位于小数点右侧。

If your record has more than 2 decimal points, SQL Server will truncate the extras.如果您的记录有超过 2 个小数点,SQL 服务器将截断额外部分。 If your record has more than 18 total digits, you will get an "out of range" error.如果您的记录的总位数超过 18 位,您将收到“超出范围”错误。 The easiest fix is to use Data Annotations to declare a default on your model.最简单的解决方法是使用数据注释在 model 上声明默认值。 The data annotation looks like: [Column(TypeName = "decimal(18,2)")]数据注释看起来像: [Column(TypeName = "decimal(18,2)")]

public class Product {
    public int ID { get; set; }
    public string Title { get; set; }
    [Column(TypeName = "decimal(18,2)")]
    public decimal Price { get; set; }
}

You can also add it to the OnModelCreating method of your DbContext implementation instead of the above method.您还可以将其添加到 DbContext 实现的 OnModelCreating 方法中,而不是上述方法。 It would look similar to the following:它看起来类似于以下内容:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Product>()
        .Property(p => p.Price)
        .HasColumnType("decimal(18,2)");
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM