EF Core - 在 Sql 服务器 LocalDb 中创建意外的不完整行
EF Core - Creating Unexpected Incomplete Rows in Sql Server LocalDb
我有一个带有 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 ()
谁能告诉我为什么要在“租户”table 中创建多个记录?
哦..刚刚注意到你 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
它有,而不是将您创建的工人设置为您创建的租户的工人之一)
此外,脚注,通常我会说“避免批发替换整个子实体列表”——这些集合通常 new
在 constructor/class prop 初始值设定项中作为空 HashSets 用于原因,您只需添加到它们而不是替换它们。如果你替换它们,更改跟踪器可能会认为你正在删除东西,并且会发生各种疯狂的事情
我有一个带有 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 ()
谁能告诉我为什么要在“租户”table 中创建多个记录?
哦..刚刚注意到你 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
它有,而不是将您创建的工人设置为您创建的租户的工人之一)
此外,脚注,通常我会说“避免批发替换整个子实体列表”——这些集合通常 new
在 constructor/class prop 初始值设定项中作为空 HashSets 用于原因,您只需添加到它们而不是替换它们。如果你替换它们,更改跟踪器可能会认为你正在删除东西,并且会发生各种疯狂的事情