EF Core Cascading Referential Integrity with DeleteBehavior.Restrict does not work well - ef-core-3.1

I have one sql server database created with code first. There are two tables that have a one to many relationship. The database works and is created well.
In sql server if I try to delete one of the classification records, I get an error (referencial integrity restriction). This is how I want it to work. But in ef core, if I delete one classification dbset.Remove(classification), the classification is deleted and the classification in the customer is set to null.
I think this is how it should work for DeleteBehavior.ClientSetNull.
There is a note "Changes in EF Core 2.0" in https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete that explains the DeleteBehavior function.
I have the next records:
Classification:
Id Name
1 General
2 Others
Customers:
Id Name IdClassification
1 Customer A 1
2 Customer B 2
3 Customer C <null>
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
...
public int? IdClassification { get; set; }
public Classification Classification { get; set; }
}
public class Classification
{
public int Id { get; set; }
public string Name { get; set; }
...
public ICollection<Customer> Customers { get; set; }
}
public class Context : DbContext
{
public virtual DbSet<Classification> Classifications { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Classification>(
entity =>
{
entity.HasKey(e => e.Id);
});
modelBuilder.Entity<Customer>(
entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.IdClassification);
...
// Claves foráneas
entity.HasOne(c => c.Classification)
.WithMany(x => x.Customers)
.HasForeignKey(x => x.IdClassification)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Customer_Classification");
});
}
}
Is there a way to prevent deletion of classification records in ef core? (I don't want to check if there is any customer record that is linked to the classification because I have to use the classification with more tables).
Thanks in advance.

EF Core 3.0 added several new values to the DeleteBehavior enum - ClientCascade, NoAction, ClientNoAction. Unfortunately the documentation is not updated (except for enum values in API reference), and only the ClientNoAction is mentioned in the 3.0 Breaking Changes - DeleteBehavior.Restrict has cleaner semantics:
Old behavior
Before 3.0, DeleteBehavior.Restrict created foreign keys in the database with Restrict semantics, but also changed internal fixup in a non-obvious way.
New behavior
Starting with 3.0, DeleteBehavior.Restrict ensures that foreign keys are created with Restrict semantics--that is, no cascades; throw on constraint violation--without also impacting EF internal fixup.
Why
This change was made to improve the experience for using DeleteBehavior in an intuitive manner, without unexpected side-effects.
Mitigations
The previous behavior can be restored by using DeleteBehavior.ClientNoAction.
More info is contained in the associated tracking issue - 12661: Update DeleteBehavior to be more consistent and understandable
Honestly even after reading all that, I don't find it cleaner, but even more confusing. Restrict seems to be obsoleted and replaced with NoAction, which regardless of what have been said actually does set loaded related entities navigation property/FK to null, thus causing SET NULL database behavior as you already experienced.
After trying all of them, the only option which does what you expect is the aforementioned ClientNoAction:
Note: it is unusual to use this value. Consider using ClientSetNull instead to match the behavior of EF6 with cascading deletes disabled.
For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are not changed when the related principal entity is deleted. This can result in an inconsistent graph of entities where the values of foreign key properties do not match the relationships in the graph.
If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is to generate an error if a foreign key constraint is violated.
regardless of their note at the beginning.
With all that being said, simply replace Restrict with ClientNoAction and the issue will be solve. No database migration is needed because this change affects only the client behavior.

Well, the classification entity needs correct initialization, suppose to delete restriction rule.
modelBuilder.Entity<Classification>()
.HasKey(e => e.Id)
.HasMany(e => e.Customers)
.WithOne(e => e.Classification)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(true);
Hope this helps.

Related

Table Splitting - Migration Warning

My scenario:
I have a Product that has various properties such a price, size, etc. that are declared in the Product Entity.
Additionally, a Product can have a collection of StockRequirements, i.e. when that Product is used the constituent StockItems can be depleted by the StockRequirement quantity accordingly.
Under one use case I just want the Product so that I can play with the core properties. For another use case I want the Product with its StockRequirements.
This means that when retrieving a Product I may be using it in different contexts. My chosen approach has been to use EF table splitting.
I have one repository for Products and one repository for ProductStockRequirements. They are referring to the same unique Product.
The Product repository will provide a Product Entity with the core details only.
The ProductStockRequirements repository will provide ProductStockRequirements entity which does not have the core details, but does have the list of StockRequirements.
This seemed a reasonable approach so that I am not retrieving 'owned' StockRequirements when I only want to change the price of the product. Similarly, if I'm only interested in playing with the StockRequirements then I don't retrieve the other core details.
Entities
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
}
class ProductStockRequirements
{
public int Id { get; set; }
public List<StockRequirement> StockRequirements { get; set; }
}
Product Mapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.Property(p => p.CoreProperty).IsRequired();
ProductStockRequirementsMapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.OwnsMany<StockRequirement>(p => StockRequirements, b =>
{
b.ToTable("StockRequirements");
b.WithOwner().HasForeignKey("ProductId");
}
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id");
When running a migration, I get the warning:
The entity type 'ProductStockRequirements' is an optional dependent
using table sharing without any required non shared property that
could be used to identify whether the entity exists. If all nullable
properties contain a null value in database then an object instance
won't be created in the query. Add a required property to create
instances with null values for other properties or mark the incoming
navigation as required to always create an instance.
Focusing on the advice:
mark the incoming navigation as required to always create an instance
I have tried:
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id")
.IsRequired();
and
b.HasOne<Product>()
.WithOne()
.IsRequired()
.HasForeignKey<ProductStockRequirements>("Id");
to no avail.
The warning does not appear to result in any bad behaviour. All my tests are passing. But, it seems that I should be able to create a map that removed this warning, but cannot find the way.
This should really just be
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
public List<StockRequirement> StockRequirements { get; set; } = new List<StockRequirement>();
}
As the StockRequiremens are not part of the Product entity, and related data isn't loaded unless you request it.
And the Entity model is simply not the correct layer to define your aggregates. An Aggregate is defined by selecting a single Entity from your entity model along with 0-few related entities. Typically you include the closely-related and weak entities together in an aggregate.
If your entity model is a graph of 23 related entities, you might organize it into 10 separate and partially-overlapping aggregates or sub-graphs.

Is it Possible to Use EF Core in an MVC site Using Just One Table?

Say I have a table in the database with data already populated (imported from excel).
I am building an MVC website using .NET Core and EF Core (both v.1.1.2)
What I'm wanting to do create a series of models, whose data is derived from one original "source data" table. The source data table has 150 columns, and although I don't need them all right now, I do want to retain all columns so that I might be able to use them at a later time if needed.
Using the "dotnet ef dbcontext scaffold" command, I was able to generate a:
Model - with all public get/set properties for each column
DbContext Class - with DbSet & Fluent API statements to reference the table and columns
With this setup, is it possible to setup a sort of "virtual" relational data model with EF Core by simply creating various Model classes (many-to-many relationships) and their properties equal to the properties already defined in the original "source" entity?
What I'm hoping to avoid is having to maintain an actual relational data structure in the DB (independent tables linked by PKs, FKs, join tables, etc.)
Reason being... I'll only be looking to do a nightly update of the source data table from bulk import of Excel worksheet and will not have to persist or track any changes to the data (read only). So, I'd like to not have to deal with the additional overhead/setup/maintenance involved with mapping the source data to relational tables, columns, keys, etc. during the import.
Can I simply create various models, then in the DbContext, override the OnModelCreating method passing an instance of modelBuilder to map those DbSet entities to the columns in the source table?
namespace VTracker.Contexts
{
public partial class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<SourceData> Data { get; set; }
public DbSet<SomeModel1> Model1 { get; set; }
public DbSet<SomeModel2> Model2 { get; set; }
public DbSet<SomeModel3> Model3 { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SourceData>(entity =>
{
// some key must be defined?
entity.HasKey(e => e.SourceIDColumn);
entity.ToTable("SourceDataTable");
entity.Property(e => e.SourceIDColumn)
entity.Property(e => e.SourceColumn1)
.HasColumnName("SourceColumn1")
// ...... continue mapping .....
// Some Model 1 Entity Builder
entity.ToTable("SourceDataTable");
// ...... continue mapping .....
// Some Model 2 Entity Builder
entity.ToTable("SourceDataTable");
// .... and so on for other models......
}
}
}
}
Or would it be better to just use the one entity for everything and just build out SQL / LINQ queries to retrieve/join the data as needed?

EF migration generating duplicated FK with different name

I'm Trying to rename the Default FK, but Code First Migration keep generating a second FK to the same table with a different name, messing up the Table schema.
The FK Model has a PK named Id, I just want to keep the convention required by my client, changing the name for Id(something).
1) Generated migration:
2) Mapping:
What should I do?
If your CorpoGestor entity exposes a property for the foreign key, use HasForeignKey instead of Mapand MapKey.
HasRequired(x => x.Conselho)
.WithMany()
.HasForeignKey(x => x.IdConselho);
Another solution: you can use the ForeignKey attribute in the property, instead of mapping the relation in the CorpoGestorMap class:
public class CorpoGestor
{
...
public int IdConselho { get; set; }
[ForeignKey("IdConselho")]
public virtual Conselho Conselho { get; set; }
}
A warning: using attributes in the entities is conceptually not so cool as implementing the code in the EntityTypeConfiguration mapping classes, because you are polluting your entities with data layer code that should be kept in the EF classes.

One-To-One relationship with fluent api. A Hacky way?

EF 4.3.1. I have defined User and Box entities. Each box may or may not be assigned to a user.
What I'd like to achieve is to have a OwnBox property in User class, and an Owner property in Box class.
in Database, I have defined OwnerId foreignkey in Boxes (Boxes.OwnerId has relation with Users.UserId).
To define the relationship with fluent api, I have defined the following classes:
public partial class User
{
public int UserId {get; set;}
public virtual Box OwnBox { get; set; }
}
public partial class Box
{
public int? OwnerId { get; set; }
public virtual User User { get; set; }
}
Then in my Mapping class for Box, I have defined the relations as follows:
this.HasOptional(t => t.User).WithOptionalDependent(d => d.OwnBox).
Map(m => m.MapKey("OwnerId")).WillCascadeOnDelete(true);
But by firing up the project, I got the error:
Schema specified is not valid. Errors: (56,6) : error 0019: Each
property name in a type must be unique. Property name 'OwnerId' was
already defined.
So I had to tell EF to forget about the OwnerId column first:
this.Ignore(t => t.OwnerId);
Now the project works fine. But I'm still doubtful if this is a good approach and will everything work fine on CRUD operations with foreign key associations.
First of all, this is not one-to-one relationship. In one-to-one relationship the foreign key must be a primary key.
I believe in your scenario the situation can happen:
User = { UserID = 2 }
Box1 = { UserID = 2 }
Box2 = { UserID = 2 }
Nothing stops you from doing that, but which box should be returned when you do that:
User.OwnBox, Box1 or Box2?
EF can deal with that using Independent Association. It will create foreign key, hidden from your POCO class. You can specify the name of the column using MapKey as you did. However, because you also created a property called OnwerID, just as the column used with MapKey, the EF has a problem as two properties are mapped to the same column.
When you use ignore, the POCO OwnerID property is ignored by EF so that fixes the problem of two properties, however, the OwnderID value never gets saved or read to the database. Because EF just ignores it.
Thanks for your question, I have learnt a lot thanks to this.

How do I map a one to zero-or-one relationship in Entity Framework 4.2 when joining properties are of different types?

I am using Entity Framework 4.2 in a class library project. The database already exists, and I cannot modify it in any way whatsoever.
I have two model/domain classes that model two database tables. The tables both expose an Id column value, which I will refer to as ThingsId. Lets call the tables TableOfThings1 and TableOfThings2. Here are my classes:
public class TableOfThings1
{
public string ThingId { get; set; }
public virtual Thing Thing { get; set; }
}
public class TableOfThings2 //qse
{
public Int64? ThingId {get; set;}
public string ThingName { get; set; }
}
The problem is that the TableOfThings1 exposes ThingsId as a nullable varchar(64), while TableOfThings2 exposes ThingsId as a non-nullable bigint.
How can I tell the Entity Framework to join on these two keys? I have tried using this:
HasOptional(things1 => things1.thing).WithMany().HasForeignKey(t => t.ThingId);
in the EntityTypeConfiguration forTableOfThings1.
I have also tried casting in the middle of that statement, which does not work. Using the setup shown above gets me this error message currently:
"The types of all properties in the Dependent Role of a referential
constraint must be the same as the corresponding property types in the
Principal Role".
Does anyone know for sure whether/how this is possible?
This is not possible with EF. You can not even create a foreign key in the database if the column types are different. Possible workaround would be to create a view of TableOfThings1 with ThingId column type matching the TableOfThings2s column type.