dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.65k stars 3.15k forks source link

IsRowVersion is not working as stated with fluent api #10498

Closed woodronnie closed 1 year ago

woodronnie commented 6 years ago

I am working with EntityFrameworkCore and using fluent api to setup my entities and database. When working with the concurrency property I am setting the property to IsRowVersion() but it is not creating the correct migration script for the creation of the table. It is setting my ConcurrencyId to a byte[] field but making it nullable. If I set the attributes of [Timestamp] and [Required] on the model property it seems to work correctly. Why would the attribute work correctly but not fluent api?

Steps to reproduce

Fluent Api Mapping not working

Fluent Api Mapping -

builder.Property(c => c.ConcurrencyId)
                .HasColumnName("ConcurrencyId")
                .HasColumnType("rowversion")
                .IsRowVersion()
                .ValueGeneratedOnAddOrUpdate()
                .IsRequired();

Model Property -

public byte[] ConcurrencyId { get; set; }

Migration Generated Script -

migrationBuilder.CreateTable(
                name: "Contacts",
                schema: "cms",
                columns: table => new
                {
                    ContactId = table.Column<Guid>(nullable: false),
                    ConcurrencyId = table.Column<byte[]>(nullable: true),
                    CreatedUTC = table.Column<DateTime>(nullable: false),
                    Deleted = table.Column<bool>(nullable: false),
                    DeletedUTC = table.Column<DateTime>(nullable: true),
                    FirstName = table.Column<string>(nullable: true),
                    LastName = table.Column<string>(nullable: true),
                    Mid = table.Column<string>(nullable: true),
                    ModifiedUTC = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Contacts", x => x.ContactId);
                });

Attributes do work

Fluent Api Mapping -

builder.Property(c => c.ConcurrencyId)
                .HasColumnName("ConcurrencyId");

Model Property -

[Timestamp]
        [Required(AllowEmptyStrings = false)]
        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public byte[] ConcurrencyId { get; set; }

Migration Generated Script -

migrationBuilder.CreateTable(
                name: "Contacts",
                schema: "cms",
                columns: table => new
                {
                    ContactId = table.Column<Guid>(nullable: false),
                    ConcurrencyId = table.Column<byte[]>(rowVersion: true, nullable: false),
                    CreatedUTC = table.Column<DateTime>(nullable: false),
                    Deleted = table.Column<bool>(nullable: false),
                    DeletedUTC = table.Column<DateTime>(nullable: true),
                    FirstName = table.Column<string>(nullable: true),
                    LastName = table.Column<string>(nullable: true),
                    Mid = table.Column<string>(nullable: true),
                    ModifiedUTC = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Contacts", x => x.ContactId);
                });

Further technical details

EF Core version: (2.0.1) Database Provider: (Microsoft.EntityFrameworkCore.SqlServer) Operating system: Windows 8 IDE: (e.g. Visual Studio 2017 15.4.5)

ajcvickers commented 6 years ago

@woodronnie I have not been able to reproduce this. Using this code:

public class Contact
{
    public int Id { get; set; }
    public byte[] ConcurrencyId { get; set; }
}

public class TestDbContext : DbContext
{
    public DbSet<Contact> Contacts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Contact>()
            .Property(c => c.ConcurrencyId)
            .HasColumnName("ConcurrencyId")
            .HasColumnType("rowversion")
            .IsRowVersion()
            .ValueGeneratedOnAddOrUpdate()
            .IsRequired();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}

public class Program
{
    public static void Main()
    {
    }
}

results in this migration:

migrationBuilder.CreateTable(
    name: "Contacts",
    columns: table => new
    {
        Id = table.Column<int>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
        ConcurrencyId = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Contacts", x => x.Id);
    });

Using this code:

public class Contact
{
    public int Id { get; set; }

    [Timestamp]
    [Required(AllowEmptyStrings = false)]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public byte[] ConcurrencyId { get; set; }
}

public class TestDbContext : DbContext
{
    public DbSet<Contact> Contacts { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}

public class Program
{
    public static void Main()
    {
    }
}

results in the same migration:

migrationBuilder.CreateTable(
    name: "Contacts",
    columns: table => new
    {
        Id = table.Column<int>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
        ConcurrencyId = table.Column<byte[]>(rowVersion: true, nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Contacts", x => x.Id);
    });

Can you please take a look and see what is different in your code that is resulting in the behavior you are seeing and then update the issue with whatever changes are needed for us to reproduce this?

woodronnie commented 6 years ago

@ajcvickers Thanks for looking into this. It ended up being an issue with the way that I was loading the maps to my entities. I was able to get it working correctly after further research. You can close this ticket.

msd-kharazi commented 4 years ago

@woodronnie Can you please tell us what you did to solve the problem? Because im in the exact same situation.

Matthew-Bright commented 1 year ago

@woodronnie, Can you please tell us what you did to solve the problem? I am also in the exact same situation.

mustafaomar commented 9 months ago

can we know the solution please ?