Entity Framework Core 7 keeps creating the same migration for Nullable DateTime field
Have entities with nullable DateTime inside called "UpdatedDate".
var defaultPolicy = new MatchingPolicy()
{
Id = Guid.Parse("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"),
CompanyId = null,
Name = "PASA data Matching convention (DMC)",
Status = MatchingPolicyStatus.Draft,
Type = MatchingPolicyType.Default,
Description = "Bla bla.",
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258),
};
var defaultPolicyVersion = new MatchingPolicyVersion()
{
Id = Guid.Parse("5cf3f8e5-50e1-47c8-aa42-40b1824ea098"),
CreatedByUserName = "System",
MatchingPolicyId = defaultPolicy.Id,
ReviewerUserName = "System",
Status = MatchingPolicyApprovalStatus.Approved,
Version = 1,
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc),
UpdatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc),
};
var defaultGroups = new[]
{
new MatchingPolicyGroup
{
Id = Guid.Parse("1e93b3f9-4ed8-4b90-a859-31b6503ec89f"),
MatchingPolicyVersionId = defaultPolicyVersion.Id,
Group = MatchingPolicyGroupType.Full,
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258),
},
new MatchingPolicyGroup
{
Id = Guid.Parse("b08f004f-a62d-409e-8025-337f0eb73054"),
MatchingPolicyVersionId = defaultPolicyVersion.Id,
Group = MatchingPolicyGroupType.Possible,
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258),
},
And when I'm trying add data to db by using HasData, `
modelBuilder.Entity<MatchingPolicy>().HasData(defaultPolicy);
modelBuilder.Entity<MatchingPolicyVersion>().HasData(defaultPolicyVersion);
entity.HasData(defaultGroups);
modelBuilder.Entity<Scheme>().Property(u => u.MatchingPolicyId).HasDefaultValue(defaultPolicy.Id);`
It always creates the same migration with updating "UpdatedDate" field, even if this migration exist
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DashboardISP.DAL.PostgreSQL.Migrations
{
/// <inheritdoc />
public partial class TestMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "matching_policies",
keyColumn: "id",
keyValue: new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_policy_groups",
keyColumn: "id",
keyValue: new Guid("1e93b3f9-4ed8-4b90-a859-31b6503ec89f"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_policy_groups",
keyColumn: "id",
keyValue: new Guid("b08f004f-a62d-409e-8025-337f0eb73054"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_policy_versions",
keyColumn: "id",
keyValue: new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea098"),
column: "updated_date",
value: new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc));
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("0da103bd-4753-4e01-bc3b-9dd1316861f9"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("2f95ee66-ce3d-4c37-9c76-d89cb5a6114c"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("3b0768e4-c774-4228-8aad-150f9a6aed1b"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("4542bf1e-219f-4e2e-83a1-5064c627c919"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("6811980f-9890-452f-a358-82678cfb34b4"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("6f77d0d9-0c2d-477c-9f0f-87b1e69bbda1"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("868feacb-f89f-4605-887b-ba068a1f05bc"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("93c1fbb6-6a86-4fdf-b298-3a32998045c4"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("9d00f543-07c8-4938-8d2e-7157720c7840"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("b3434fcc-aa01-4630-a620-7219515fb3b7"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("b97737f0-0237-4d95-b8cb-86c706cd9e33"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("ff64a9ed-314a-49ff-821a-085c491bd7c1"),
column: "updated_date",
value: null);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "matching_policies",
keyColumn: "id",
keyValue: new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_policy_groups",
keyColumn: "id",
keyValue: new Guid("1e93b3f9-4ed8-4b90-a859-31b6503ec89f"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_policy_groups",
keyColumn: "id",
keyValue: new Guid("b08f004f-a62d-409e-8025-337f0eb73054"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_policy_versions",
keyColumn: "id",
keyValue: new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea098"),
column: "updated_date",
value: new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc));
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("0da103bd-4753-4e01-bc3b-9dd1316861f9"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("2f95ee66-ce3d-4c37-9c76-d89cb5a6114c"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("3b0768e4-c774-4228-8aad-150f9a6aed1b"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("4542bf1e-219f-4e2e-83a1-5064c627c919"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("6811980f-9890-452f-a358-82678cfb34b4"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("6f77d0d9-0c2d-477c-9f0f-87b1e69bbda1"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("868feacb-f89f-4605-887b-ba068a1f05bc"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("93c1fbb6-6a86-4fdf-b298-3a32998045c4"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("9d00f543-07c8-4938-8d2e-7157720c7840"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("b3434fcc-aa01-4630-a620-7219515fb3b7"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("b97737f0-0237-4d95-b8cb-86c706cd9e33"),
column: "updated_date",
value: null);
migrationBuilder.UpdateData(
table: "matching_rule_templates",
keyColumn: "id",
keyValue: new Guid("ff64a9ed-314a-49ff-821a-085c491bd7c1"),
column: "updated_date",
value: null);
}
}
}
@ruban211097 I am not able to reproduce this--see my code below. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.
public class SomeDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var defaultPolicy = new MatchingPolicy()
{
Id = Guid.Parse("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"),
CompanyId = null,
Name = "PASA data Matching convention (DMC)",
Status = MatchingPolicyStatus.Draft,
Type = MatchingPolicyType.Default,
Description = "Bla bla.",
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258),
};
var defaultPolicyVersion = new MatchingPolicyVersion()
{
Id = Guid.Parse("5cf3f8e5-50e1-47c8-aa42-40b1824ea098"),
CreatedByUserName = "System",
MatchingPolicyId = defaultPolicy.Id,
ReviewerUserName = "System",
Status = MatchingPolicyApprovalStatus.Approved,
Version = 1,
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc),
UpdatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc),
};
var defaultGroups = new[]
{
new MatchingPolicyGroup
{
Id = Guid.Parse("1e93b3f9-4ed8-4b90-a859-31b6503ec89f"),
MatchingPolicyVersionId = defaultPolicyVersion.Id,
Group = MatchingPolicyGroupType.Full,
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258),
},
new MatchingPolicyGroup
{
Id = Guid.Parse("b08f004f-a62d-409e-8025-337f0eb73054"),
MatchingPolicyVersionId = defaultPolicyVersion.Id,
Group = MatchingPolicyGroupType.Possible,
CreatedDate = new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258),
},
};
modelBuilder.Entity<MatchingPolicy>().HasData(defaultPolicy);
modelBuilder.Entity<MatchingPolicyVersion>().HasData(defaultPolicyVersion);
modelBuilder.Entity<MatchingPolicyGroup>().HasData(defaultGroups);
modelBuilder.Entity<Scheme>().Property(u => u.MatchingPolicyId).HasDefaultValue(defaultPolicy.Id);
}
}
public class Scheme
{
public Guid Id { get; set; }
public Guid? MatchingPolicyId { get; set; }
}
public enum MatchingPolicyGroupType
{
Full,
Possible
}
public class MatchingPolicyGroup
{
public Guid Id { get; set; }
public Guid MatchingPolicyVersionId { get; set; }
public MatchingPolicyGroupType Group { get; set; }
public DateTime? CreatedDate { get; set; }
}
public class MatchingPolicyVersion
{
public Guid Id { get; set; }
public MatchingPolicyApprovalStatus Status { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedByUserName { get; set; }
public Guid MatchingPolicyId { get; set; }
public string ReviewerUserName { get; set; }
public int Version { get; set; }
public DateTime? UpdatedDate { get; set; }
}
public enum MatchingPolicyApprovalStatus
{
Approved
}
public enum MatchingPolicyType
{
Default
}
public enum MatchingPolicyStatus
{
Draft
}
public class MatchingPolicy
{
public Guid Id { get; set; }
public Guid? CompanyId { get; set; }
public string Name { get; set; }
public MatchingPolicyStatus Status { get; set; }
public MatchingPolicyType Type { get; set; }
public string Description { get; set; }
public DateTime? CreatedDate { get; set; }
}
public class Program
{
public static async Task Main()
{
using (var context = new SomeDbContext())
{
}
}
}
First migration:
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Migrations
{
/// <inheritdoc />
public partial class One : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "MatchingPolicy",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CompanyId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
Type = table.Column<int>(type: "int", nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: false),
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MatchingPolicy", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MatchingPolicyGroup",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MatchingPolicyVersionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Group = table.Column<int>(type: "int", nullable: false),
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MatchingPolicyGroup", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MatchingPolicyVersion",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedByUserName = table.Column<string>(type: "nvarchar(max)", nullable: false),
MatchingPolicyId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ReviewerUserName = table.Column<string>(type: "nvarchar(max)", nullable: false),
Version = table.Column<int>(type: "int", nullable: false),
UpdatedDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MatchingPolicyVersion", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Scheme",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MatchingPolicyId = table.Column<Guid>(type: "uniqueidentifier", nullable: true, defaultValue: new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"))
},
constraints: table =>
{
table.PrimaryKey("PK_Scheme", x => x.Id);
});
migrationBuilder.InsertData(
table: "MatchingPolicy",
columns: new[] { "Id", "CompanyId", "CreatedDate", "Description", "Name", "Status", "Type" },
values: new object[] { new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"), null, new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258), "Bla bla.", "PASA data Matching convention (DMC)", 0, 0 });
migrationBuilder.InsertData(
table: "MatchingPolicyGroup",
columns: new[] { "Id", "CreatedDate", "Group", "MatchingPolicyVersionId" },
values: new object[,]
{
{ new Guid("1e93b3f9-4ed8-4b90-a859-31b6503ec89f"), new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258), 0, new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea098") },
{ new Guid("b08f004f-a62d-409e-8025-337f0eb73054"), new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc).AddTicks(1258), 1, new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea098") }
});
migrationBuilder.InsertData(
table: "MatchingPolicyVersion",
columns: new[] { "Id", "CreatedByUserName", "CreatedDate", "MatchingPolicyId", "ReviewerUserName", "Status", "UpdatedDate", "Version" },
values: new object[] { new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea098"), "System", new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc), new Guid("5cf3f8e5-50e1-47c8-aa42-40b1824ea099"), "System", 0, new DateTime(2022, 8, 11, 11, 35, 8, 586, DateTimeKind.Utc), 1 });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "MatchingPolicy");
migrationBuilder.DropTable(
name: "MatchingPolicyGroup");
migrationBuilder.DropTable(
name: "MatchingPolicyVersion");
migrationBuilder.DropTable(
name: "Scheme");
}
}
}
Second migration:
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Migrations
{
/// <inheritdoc />
public partial class Two : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
I can confirm this behavior using version 7.0.2 against a SQL server database.
On every migration my nullable datetime fields are updated.
My configuration looks as follows:
b.Property(p => p.RevokedOn)
.HasConversion(
p => p,
p => p != null ? DateTime.SpecifyKind(p.Value, DateTimeKind.Utc) : null);
builder.HasData(new
{
ApiKey = Guid.Parse("9ebcb03af21e43a1af2a036871cc59f5"),
AccountId = Guid.Parse("9e3bbf26bcb346758648f7d18906a138"),
CreatedOn = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc),
Comment = "Initial creation",
IsRevoked = false,
});
And every time I create a new migration the RevokedOn field of any seeded entry is updated:
public partial class Test : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "AccountApiKeys",
keyColumns: new[] { "AccountId", "ApiKey" },
keyValues: new object[] { new Guid("9ebcb03af21e43a1af2a036871cc59f5"), new Guid("9e3bbf26bcb346758648f7d18906a138") },
column: "RevokedOn",
value: null);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "AccountApiKeys",
keyColumns: new[] { "AccountId", "ApiKey" },
keyValues: new object[] { new Guid("9ebcb03af21e43a1af2a036871cc59f5"), new Guid("9e3bbf26bcb346758648f7d18906a138") },
column: "RevokedOn",
value: null);
}
}
I've tried explicitly adding null and (DateTime?)null assignments in my seed data, but the behavior stays the same.
Note for triage: I was able to repro this on 7.0.2, but it does not repro with the 8.0 daily build. However, I'm not sure which issue has the same root cause.
public class Program
{
public static async Task Main()
{
using (var context = new SomeDbContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
}
}
}
public class Account
{
public Guid AccountId { get; set; }
public Guid ApiKey { get; set; }
public DateTime? RevokedOn { get; set; }
public DateTime CreatedOn { get; set; }
public string? Comment { get; set; }
public bool IsRevoked { get; set; }
}
public class SomeDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>(
b =>
{
b.Property(p => p.RevokedOn)
.HasConversion(
p => p,
p => p != null ? DateTime.SpecifyKind(p.Value, DateTimeKind.Utc) : null);
b.HasData(new
{
ApiKey = Guid.Parse("9ebcb03af21e43a1af2a036871cc59f5"),
AccountId = Guid.Parse("9e3bbf26bcb346758648f7d18906a138"),
CreatedOn = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc),
Comment = "Initial creation",
IsRevoked = false,
});
});
}
}
@dotnet/efteam So, this is fixed in 8.0, but not in 7.0.3. I cannot find which change fixed this, but I think we need to do so, since we have had multiple reports and I think we need to consider servicing. Anybody remember changing anything that might have fixed this?
It might be 61be8288a8685bb0581040b6e70ee913a9836915, the fix to https://github.com/dotnet/efcore/issues/29531. The patch version 83e06a3a8c738b94fb2745f07fdb8e8d4f3ffe09 doesn't have the extra changes in the update pipeline. Now that we have a report we can consider patching with the complete fix.
Poaching