简体   繁体   中英

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:

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. 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).

Coupon object Value field is defined like below:

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.

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:

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. 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.

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:

[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:

 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?)

I was thinking maybe my entity map objects useless, but the relations that I created in entity map objects are working perfectly fine.

I tried cleaning the solution, rebuilding, restarting VS but none of them worked. 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. But it was actually just because of the EF Conventions and my object mappings were never run.

This means Entity Framework will provide a default precision to the database. The default is typically (18, 2). That means it will store 18 total digits, with 2 of those digits being to the right of the decimal point.

If your record has more than 2 decimal points, SQL Server will truncate the extras. If your record has more than 18 total digits, you will get an "out of range" error. The easiest fix is to use Data Annotations to declare a default on your model. The data annotation looks like: [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. 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)");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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