【问题标题】:EF Core - Creating Unexpected Incomplete Rows in Sql Server LocalDbEF Core - 在 Sql Server LocalDb 中创建意外的不完整行
【发布时间】:2022-01-05 08:32:02
【问题描述】:

我有一个带有 EFCore 6.0.1 的新 VS2022 解决方案,网址为:https://github.com/RobBowman/BillByTime

我创建了一个数据库上下文来表示以下内容:

我使用了 VS2022 中的 EFCore Power Tools,并将它创建的图表与我的预期架构进行了比较 - 它们匹配!

这里是数据库上下文 C#:

public class BillByTimeContext : DbContext
    {
        public BillByTimeContext(DbContextOptions<BillByTimeContext> options) : base(options)
        {
            RelationalDatabaseCreator databaseCreator =
                        (RelationalDatabaseCreator)this.Database.GetService<IDatabaseCreator>();
            databaseCreator.EnsureCreated();
        }

        public DbSet<ClientOrg>? ClientOrg { get; set; }
        public DbSet<Contract>? Contract { get; set; }
        public DbSet<PurchaseOrder>? PurchaseOrder { get; set; }
        public DbSet<Tenant> Tenant { get; set; }
        public DbSet<TenantManager>? TenantManager { get; set; }
        public DbSet<Timesheet>? Timesheet { get; set; }
        public DbSet<TimesheetHistory>? TimesheetHistory { get; set; }
        public DbSet<Worker>? Worker { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Tenant>()
                .HasIndex(x => x.Name)
                .IsUnique();

            modelBuilder.Entity<TenantManager>()
                .HasIndex(x => x.Email)
                .IsUnique();

            modelBuilder.Entity<Worker>()
                .HasMany(p => p.Contracts)
                .WithOne(t => t.Worker)
                .OnDelete(DeleteBehavior.NoAction);

            modelBuilder.Entity<Timesheet>()
                .HasMany(p => p.TimesheetHistories)
                .WithOne(t => t.Timesheet)
                .OnDelete(DeleteBehavior.NoAction);

            modelBuilder.Entity<Worker>()
                .HasMany(p => p.TimesheetHistories)
                .WithOne(t => t.Worker)
                .OnDelete(DeleteBehavior.NoAction);

            modelBuilder.Entity<Contract>()
                .Property(x => x.UnitCharge)
                .HasPrecision(10, 2);

            modelBuilder.Entity<PurchaseOrder>()
                .Property(x => x.Amount)
                .HasPrecision(10, 2);

            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Monday)
                .HasPrecision(10, 2);
            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Tuesday)
                .HasPrecision(10, 2);
            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Wednesday)
                .HasPrecision(10, 2);
            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Thursday)
                .HasPrecision(10, 2);
            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Friday)
                .HasPrecision(10, 2);
            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Saturday)
                .HasPrecision(10, 2);
            modelBuilder.Entity<Timesheet>()
                .Property(x => x.Sunday)
                .HasPrecision(10, 2);
        }

    }

我有一个调用以下 SeedData 方法的 xunit 测试:

public static void SeedData(BillByTimeContext context)
        {

            context.Database.EnsureCreated();

            var timesheetHistory = new TimesheetHistory
            {
                Timestamp = new DateTime(2022, 1, 7, 14, 30, 0),
                StatusId = TimesheetStatus.PendingApproval
            };

            var timesheet = new Timesheet
            {
                Monday = 1,
                Tuesday = .5M,
                Wednesday = 1,
                Thursday = 0,
                Friday = 1,
                WeekCommencingMonday = new DateTime(2022, 01, 03),
                TimesheetHistories = new List<TimesheetHistory> { timesheetHistory },
            };

            var purchaseOrder = new PurchaseOrder
            {
                DateIssued = DateTime.Now,
                Amount = 5462.5M,
                Timesheets = new List<Timesheet> { timesheet }
            };

            var clientManager = new ClientManager
            {
                FirstName = "Paul",
                LastName = "Arndel",
                Email = "p.arndel@brc.com",
                SmsNumber = "867428764",
                TimesheetHistories = new List<TimesheetHistory> { timesheetHistory }
            };

            var contract = new Contract
            {
                UnitId = ContractUnits.Days,
                UnitCharge = 550,
                Timesheets = new List<Timesheet> { timesheet }
            };

            var clientOrg = new ClientOrg
            {
                Name = "BRC",
                PurchaseOrders = new List<PurchaseOrder> { purchaseOrder },
                ClientManagers = new List<ClientManager> { clientManager },
                Contracts = new List<Contract> { contract },
                Timesheets = new List<Timesheet> { timesheet }
            };

            var tenantManager = new TenantManager
            {
                FirstName = "Rob",
                LastName = "Bowman",
                Email = "rob@biztalkers.com",
                ClientOrgs = new List<ClientOrg> { clientOrg }
            };

            var worker = new Worker
            {
                FirstName = "Fabio",
                LastName = "Capello",
                Email = "f.cap@ital.com",
                Contracts = new List<Contract> { contract },
                TimesheetHistories= new List<TimesheetHistory> { timesheetHistory }
            };

            var tenant = context.Tenant.FirstOrDefault(x => x.Name == "BizTalkers");
            if (tenant == null)
            {
                context.Tenant.Add(new Tenant
                {
                    Name = "BizTalkers",
                    TenantManagers = new List<TenantManager> { tenantManager },
                    Workers = new List<Worker> { worker }
                }); ;
            }

            context.SaveChanges();

如果我清除数据库中的所有数据并运行测试,则会出现以下错误:

---- Microsoft.Data.SqlClient.SqlException : Cannot insert duplicate key row in object 'dbo.Tenant' with unique index 'IX_Tenant_Name'. The duplicate key value is ()

谁能告诉我为什么它试图在“租户”表中创建多条记录?

【问题讨论】:

  • 错误信息是否真的是The duplicate key value is ()?看起来名称也是空白/空...
  • @MartinSmith 是的,似乎它试图在租户表中插入一行,其中 Name 列的值为空
  • 你应该在单元测试中播种你的数据吗?数据是否应该在您的第一次 EF 迁移中播种,然后再也不会?否则你只能运行一次这个单元测试,除非你说,你清除所有数据
  • 虽然它使用了 xunit,但它不是一个单元测试并且永远不会从 CI 管道运行。这只是检查我是否已设置数据库上下文以使用我所追求的数据库架构的便捷方法。
  • 当您清除数据以重新启动时,您是否将其全部删除并重新创建?还是只是手动删除?

标签: c# .net sql-server entity-framework entity-framework-core


【解决方案1】:

哦.. 刚刚注意到您在 github 上链接了您的整个项目 - 这很有用并证实了我的怀疑:

//worker class
public Tenant Tenant { get; set; } = new();


//tenantmanager class
public Tenant Tenant { get; set; } = new();

每次您创建一个新工人时,它都会创建一个新租户。 EF 会将此视为必须保存的对象。您的图表至少包含 1 个工作人员(1 个租户名称为空白)和 1 个租户经理(1 个租户名称为空白),因此看起来像

{Tenant "BizTalkers"} --has-some--> [ {Worker "Fabio"} --has-one--> { Tenant "" } ]
           \
            `--has-some--> [ {TenantManager "Rob"} --has-one--> { Tenant "" } ]

目前我不知道为什么 EFCPT 会以这种方式生成实体(如果它甚至这样做了?) - 我的不知道。我不确定它是您打开的选项,还是数据库端某些东西的结果..如果我发现了什么,我会更新答案,但现在我相信你的问题是由这。从这些道具中删除= new(),或者在初始化程序中将它们设置为null,或者以另一种方式构建图形(将您创建的租户设置为您创建的工人的租户,覆盖它拥有的new() Tenant,而不是而不是将您创建的 Worker 设置为您创建的租户的 Worker 之一)

另外,脚注,通常我会说“避免批发替换整个子实体列表” - 这些集合通常在构造函数/类属性初始化器中作为空 HashSet 的 new'd 是有原因的,你会只需添加它们而不是替换它们。如果您替换它们,更改跟踪器可能会认为您正在删除内容,并且会发生各种疯狂的事情

【讨论】:

  • 澄清一下 - EFCPT 没有生成实体,我只是用它来生成图表。所以,这个问题的错误完全是我和我对 =new(); 的使用造成的。对于导航属性
  • 唷,我在想我必须在这里给 ErikEJ 打电话!顺便说一句,您可以让 EFCPT 生成它们,而且它也做得很好。它缩短了开发周期以弹出打开的 SSMS、制作新的数据库图、绘制表格等,就像你在那里有你的 ERD、拖动关系等等等,然后保存以创建数据库并告诉 EFCPT 对其进行逆向工程一堆课程给你。它完成了所有设置(类似于 Scaffold-DbContext),但还需要进行一些调整和可能
  • 感谢 Caius,我今天才发现 EFCPT,但它看起来会很有帮助
猜你喜欢
  • 2021-04-20
  • 1970-01-01
  • 2020-11-12
  • 2015-01-27
  • 2021-03-13
  • 1970-01-01
  • 1970-01-01
  • 2021-03-01
相关资源
最近更新 更多