我希望使用 Entity Framework Core 创建以下数据库。我将 Entity Framework Core 与 SQLite 结合使用,将 WinUi 3 与 MVVM 工具包结合使用。
我在使数据库的地址部分正常工作时遇到问题。我希望拥有它,以便创建的任何地址都可以共享/重用。我不知道如何解决这个问题,因为我收到了有关外键等的各种错误。
例如,人员的地址也可以用作工人轮班表中开始/结束位置的一部分以及路线的一部分。
我目前正在为我的数据库使用“每种类型表”设置。
public partial class Address: BaseModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AddressId { get; protected set; }
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "Name is Required")]
private string _name;
[ObservableProperty]
private string? _unitNum;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "Street Number is Required")]
private string _streetNum;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "Street Name is Required")]
private string _streetName;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "Street Type is Required")]
private string _streetType;
public int SuburbId { get; set; }
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "Suburb is Required")]
public Suburb _suburb;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "City is Required")]
private string _city;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "State is Required")]
private string _state;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "Post Code is Required")]
private string _postCode;
public int? ClientId { get; set; }
public Client? Client { get; set; }
public Address() { }
public Address(string name, string? unitNum, string streetNum, string streetName, string streetType, Suburb suburb, string city, string state, string postCode)
{
Name = name;
UnitNum = unitNum;
StreetNum = streetNum;
StreetName = streetName;
StreetType = streetType;
Suburb = suburb;
City = city;
State = state;
PostCode = postCode;
}
}
public partial class Shift: BaseModel
{
public int ShiftId { get; protected set; }
[ObservableProperty]
private string _day =string.Empty;
[ObservableProperty]
private string _startTime =string.Empty;
[ObservableProperty]
private string _endTime = string.Empty;
[ObservableProperty]
private Address _startLocation;
[ObservableProperty]
private Address _endLocation;
[ObservableProperty]
private ObservableCollection<ShiftWorker> _shiftWorkers;
[ObservableProperty]
private ObservableCollection<Route> _routes;
public int ClientId { get; set; }
public Client Client { get; set; } = null;
public Shift() { }
public Shift(string day, string startTime, string endTime, ShiftAddress startLocation, ShiftAddress endLocation, Client client)
{
Day = day;
StartTime = startTime;
EndTime = endTime;
StartLocation = startLocation;
EndLocation = endLocation;
Client = client;
}
}
public partial class Route: BaseModel
{
public int RouteId { get; protected set; }
[ObservableProperty]
private string _name =string.Empty;
[ObservableProperty]
private Address _startAddress;
[ObservableProperty]
private Address _endAddress;
[ObservableProperty]
private int _distance;
}
public partial class Person: BaseModel
{
public int PersonId { get; protected set; }
[ObservableProperty]
private string _firstName =string.Empty;
[ObservableProperty]
private string _lastName =string.Empty;
[ObservableProperty]
private Address _address;
}
根据您的架构,这应该没问题,但地址位于 Person 表上,而不是 Client 表上,并且 FK(AddressId)位于 Person 上,Address 上不会有 ClientId。 类似地,班次和路线可以通过分别在这些表上具有开始/结束的 AddressId 来引用相同的地址
这里涵盖了您需要了解的有关关系数据库中实体之间关系的所有信息:https://learn.microsoft.com/en-us/ef/core/modeling/relationships
有多种口味可以满足您可能需要的任何场景。有时您可能会想到一些并不真正适合的东西(例如,使用 OwnerId 和 OwnerType 在多个“所有者”之间“共享”表,以避免创建具有相同列的多个相似表),但这非常重要不可取,因为它破坏了旨在拥有高性能和可靠数据模式的引用完整性规则。
地址实体可以有对人员(客户端或工作人员)的引用,但不包含 PersonId/ClientId。
因此,在您的 Person 基础实体中,您应该具有:
[ForeignKey("AddressId")] // for a shadow property to the Address ID FK
public virtual Address Address { get; set; }
或
public int AddressId { get; set; }
[ForeignKey(nameof(AddressId))]
public virtual Address Address { get; set; }
如果地址想要引用回客户端或工作人员,请将以下属性添加到 Person.Address:
[InverseProperty("Person")]
...并将 Person 属性添加到 Address:
public virtual Person Person { get; set; }
或者,如果您在 OnModelCreating 中进行配置或使用具有流畅配置的实体类型配置类:
modelBuilder.Entity<Person>()
.HasOne(p => p.Address)
.WithOne(a => a.Person)
.HasForeignKey<Person>("AddressId"); // Person has the AddressID, Address does not have a PersonId
类似地,具有起始和结束地址引用的类将在指向地址记录的那些实体上设置 FK,但没有反向属性。这些不是
.HasOne().WithOne()
,而是用 .HasOne().WithMany()
设置,表示许多路线或班次等将引用相同的地址行。
modelBuilder.Entity<Route>()
.HasOne(r => r.StartAddress)
.WithMany() // No inverse property on address
.HasForeignKey("StartAddressId"); // or (r => r.StartAddressId) if you have a FK property on Route
默认情况下,如果您在实体上设置导航属性,EF 会将其视为引用类型(另一侧为“许多”),因此实体中属性的上述替代方案将在 Route 实体中:
[ForeignKey("StartAddressId")] // Shadow FK
public virtual Address StartAddress { get; set; }
[ForeignKey("EndAddressId")] // Shadow FK
public virtual Address EndAddress { get; set; }
当一个类包含对同一实体类型的多个引用时,重要的细节是显式指定 FK。一个常见的陷阱是对 EF 如何解决 FK 的错误假设。例如,查看 Person 类,如果您有:
public int AddressId { get; set; }
public virtual Address Address { get; set; }
...没有任何属性或流畅的配置,EF 会解决这个问题并将其连接起来。然而,Route 中的类似代码不起作用:
public int StartAddressId { get; set; }
public virtual Address StartAddress { get; set; }
EF 会抱怨缺少 AddressId FK,或者尝试创建一个。原因是 EF 解析 FK 的约定是基于实体类型,而不是属性名称。因此,作为导航属性的一般规则,无论您有 FK 属性还是使用 FK 的影子属性,always 都会显式指定 FK。 (推荐)