Table Splitting - Migration Warning - entity-framework-core

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.

Related

EF Core Cascading Referential Integrity with DeleteBehavior.Restrict does not work well

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.

Access underlying DbContext (or run stored procedure) from Entity Framework POCO method

Is it possible to access the underlying DbContext (the DbContext that has populated this object/has this object in its cache/is tracking this object) from inside a model object, and if so, how?
The best answer I have found so far is this blog post which is five years old. Is it still the best solution available?
I’m using the latest version of Entity Framework if that matters.
Here's a sample to clarify my question:
I have a hierarchical tree. Let’s say it is categories that could have sub-categories. The model object would be something like this:
class Category
{
string CategoryId { get; set; }
string Name { get; set; }
virtual Category Parent { get; set; }
virtual ICollection<Category> Children { get; set; }
}
Now, if I want to access all descendants of a category (not just its immediate children) I can use a recursive query like this:
class Category
{
//...
IEnumerable<Category> Descendants
{
get
{
return Children.Union(Children.SelectMany(q => q.Descendants));
}
}
}
which works, but has bad performance (due to multiple database queries it needs to perform).
But suppose I have an optimized query that I can run to find descendent (maybe I store my primary key in a way that already contains path, or maybe I’m using SQL Server data type hierarchyid, etc.). How can I run such a query, which needs access to the whole table/database and not just the records available through model object’s navigational properties?
This can be either done by running a stored procedure/SQL command on the database, or a query like this:
class Category
{
//...
IEnumerable<Category> Descendants
{
get
{
// this won't work, because underlying DbContext is not available in this context!
return myDbContext.Categories.Where(q => q.CategoryId.StartsWith(this.CategoryId));
}
}
}
Is there a way at all to implement such a method?

How do I access a table that has a composite key I can't model using Code First?

I have an existing database that I cannot change, but want to access using EF. For 90% of the database, I have been able to get Code First EF to work, and I am very impressed.
I've run into a case that I am wondering how to model or access the data through a navigation property.
In one case, the tables are like this (this example is totally made up, but represents the problem):
CREATE TABLE Dog (
id INTEGER PRIMARY KEY,
name VARCHAR(50) NULL,
breed_id INTEGER NOT NULL
);
CREATE TABLE Breed (
id INTEGER NOT NULL,
organization_id INTEGER NOT NULL,
description VARCHAR(100) NOT NULL,
Primary key (id, organization)
);
CREATE TABLE Organization (
id INTEGER PRIMARY KEY,
description VARCHAR(100) NOT NULL
);
In Table breed, the organization represents an organization that has defined a breed. A dog can have several breeds, but the program only displays one, the results being 'filtered' by the organization id - which is a value that is configured when the program is set up.
An example of the data that might be present is this:
id organizationID Description
1 1 Basset Hound
2 1 Great Dane
2 2 Grande Dane
Where organization 2 has chosen to call the breed something different than organization 1. The unique primary key is a combination of id and organizationID. A dog has a breed, but does not have a property to define one or more associated organizations. It takes additional information from another table, or a configured value (perhaps an enumerated value) to find the breed of a dog.
In my case, to find a particular dog breed, you have to have a dog id and another piece of information (organization_id) which is related to program configuration.
The dog, breed and organization classes look like this:
public class Dog {
public int id { get; set; };
public string name { get; set; };
public int breedID { get; set; };
public virtual Breed { get; set; }
}
public class Breed {
public int id { get; set; };
public int organizationID { get; set; };
public string description { get; set; };
public virtual Organization { get; set; }
}
public class Organization {
public int id { get; set; };
public string description { get; set; };
}
As seen in the code, I'd like to use a "Navigation" Property on Dog that returns a breed, but don't think I can configure this in code first.
I've tried a few different things (in fluent API, and leaving organization out - since that's easy) and will also document things I don't think work:
1)
modelBuilder.Entity<Dog>().HasKey(t => t.id);
modelBuilder.Entity<Breed>().HasKey(t => t.id);
modelBuilder.Entity<Dog>().HasRequired(d => d.Breed).WithMany().HasForeignKey(d => d.breedID);
Of course, the problem with this is that more than one breed will be returned and entity framework will throw an exception because the breedID itself is insufficient to yield a single value - which the model is calling for.
2)
Change class dog:
Remove:
public virtual Breed { get; set; }
Add:
public virtual ICollection<Breed> Breeds { get; set; }
public Breed Breed {
get {
// Assume 1 is configured organization value
return Breeds.Single(t => t.OrganizationId == 1);
};
set {
Breeds.Add(value);
}
}
Change model:
I don't know how to do this for the given classes. Since it's a Collection, it must look something like
modelBuilder.Entity<Dog>().HasMany(d => d.Breeds)...
but I don't see how to specify that breedID is a foriegn key into the breed table.
If I could get the model to work, the rest will work, but it does seem wierd and inefficient.
3)
Change model to account for composite key (using first set of classes):
modelBuilder.Entity<Breed>().HasKey(b => new { b.id, b.organizationId });
modelBuilder.Entity<Dog>().HasRequired(t => t.Breed).WithMany().HasForeignKey(t => new { t.breedID, configuredValue });
I don't know how to "inject" configuredValue as in the last line, so this doesn't work either.
If none of the above methods work, or if I can't find another way to configure code first properly, then I'd like to specify that when the Breed navigation property getter is called, it should use a query that can get the appropriate breed and return it appropriately.
However, I don't want to dirty my POCO with the Context calls to return the result of the query. In other words, I'd like to have a property on Dog that does NOT look something like this:
public Breed Breed {
get {
return context.Breed.Where(b => b.id == this.id && b.organizationID == 1).Single();
}
}
Ideally, it would work like the Navigation collections do, where EF does it's magic and returns the appropriate results.
Intuitively, it seems like I should be able to either configure this using POCO-like code or use/extend a proxy to extend the configuration to use the particular query I want when the accessor is called. Or - it seems like I ought to be able to populate the property on any read and dirty the POCO on write. I'm just not familiar enough with EF to know how to do this.
Is this possible?
As an addition to the first post, because I desire to keep my POCO classes clean, I think I will probably implement the Repository pattern to encapsulate the complex queries like the ones I've described, as well as support other operations also.
Looking at the EDMX that I've generated from my Code First model, it's not apparent how to implement the model from a database first perspective, either.
Any thoughts are greatly appreciated.
In short, you can't do that. You have illogical database structure (foreign key referencing non-unique column), you'll have to map it somewhere. You can't do that in EF configuration, because it's dynamic, so you'll have to do that inside you entity classes. And I see no ways to do that without direct call to context inside your entity class (or helper class).

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.