问题
I have a logical table structure where Table A
has relationship to multiple other tables (Table B
& Table C
). But functionally Table A can only ever have the FK for
Bor
C` populated.
Valid Table A
Record:
|------|--------|---------|---------|
| ID | Name | FK_B | FK_C |
|------|--------|---------|---------|
| 1 | Record | -null- | 3 |
|------|--------|---------|---------|
Invalid Table A
Record:
|------|--------|---------|---------|
| ID | Name | FK_B | FK_C |
|------|--------|---------|---------|
| 1 | Record | 16 | 3 |
|------|--------|---------|---------|
I am wondering how to properly define this constraint properly as so the API code interfacing with the DB is not the only gate on this.
So far I have the following, but it does not restrict the single record -null- requirement of the relationship and allows both examples above.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TableA>()
.HasIndex(p => new { p.FK_B, p.FK_C });
}
How do I define this constraint to require that only one of these FK columns are populated and the others are null?
All the code for the DB is EF Core Code First using Attributes and the Fluent APIs. Raw SQL solutions, while acceptable, are going to be harder to manage in this project. So I am looking for a solution that fits into this restraint.
回答1:
There are several ways you can incorporate validation into your project:
SQL server constraint
Read this thread dba.stackexchange.com/q/5278/571 how to create a constraint.
Pros: the most strict way to guarantee consistency of your data
Cons: requires custom SQL in the migration script; slows down DB
Recommendation: Use this option if DB is integration point; you don't have control over the code which writes to it.
Application level
SaveChanges validation
EF has a method ValidateEntity which you can override and check if both FK are not set.
EF Core does not have this API. However you can override SaveChanges and use ChangeTracker
to find entities and validate them.
Pros: fast and simple solution
Cons: does not guarantee consistency on the persistence level (DB)
Inheritance
SaveChanges solution is simple, but it still allows user to do something stupid and get a runtime validation exception. To prevent that, you may enforce your solution with compile type checks by introducing a hierarchy of classes: A{Id, Name}, B:Base {FK_B}, C:Base {FK_C}.
Both EF and EF Core supports table-per-hierarchy, so all your entities will be stored in a single table. In your code you'll be able to add to the table only either B
(with FK_B
property) or C
(with FK_C
).
Pros: restricts you to set only 1 FK on the compile time
Cons: More complex solution, some operations might be complicated (convert entity from C to B for example); queries are less straightforward.
Recommendation: if you work on Persistence level and expose entities as-is to other teams, have a small control on what is being saved.
回答2:
After spending a day researching this and through the proposed answers and comments I have found the answer I was looking for. Seems that the latest version of EFCore answers the question!
Code First Solution
Requires EFCore 3.0
In EFCore 3.0 HasCHeckConstraint
was introduced to provide a Code First solution to generating 'Check Constraints'. The following example illustrates the SQL Check Syntax Interface(s). There is a syntax that takes a bool flag to interpret DataAnnotations as constraints but the documentation is limited here. Documentation
This solution is much better than having to manage a SQL script or a Manual Migration (below). Though it still relies on a raw SQL statement. Ultimately, if the Fluent API for this gets a CodeGen method version so that you can provide a C# function instead of SQL that would provide for better handling when a field name in the constrain is changed.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TableA>()
.HasCheckConstraint("CHK_OnlyOneForeignKey", "
( CASE WHEN FK_B IS NULL THEN 0 ELSE 1 END
+ CASE WHEN FK_C IS NULL THEN 0 ELSE 1 END
) = 1
");
}
EFCore Migration editing
In all versions of EFCore you can edit a generated Migration File and utilize the Sql(...)
method on MigrationBuilder
to apply any SQL commands.
Note: It is common to create an empty Migration for this purpose.
public partial class CustomCheckConstraint : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("<SQL to alter target table and add a constraint>");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Code to remove Constraint added in Up method
}
}
Thank you @fenixil and @Ian Mercer for pointing me towards solutions that helped me refine my research.
来源:https://stackoverflow.com/questions/58313800/only-1-of-multiple-fk-relationships-constraint