Setting default DeleteBehavior for every One-to-Many Relationship in EF Core 6 - c#

I am setting up some custom standards for my Entity Framework Project (EF Core 6).
One thing I want to avoid is using the Fluent API for each and every relationship.
I want One-to-Many relationships to have a default DeleteBehavior of DeleteBehavior.Restrict. If it is an optional relationship it should be DeleteBehavior.ClientSetNull.
Here is an example of how I want to handle One-to-Many Relationships.
I have three Entities: Project, User and Client with the following relationship:
In detail that means:
One Project has exactly one client.
One Client can be associated with 0 or many Projects.
One Project has exactly one Owner (User).
One User can be associated with 0 or Many Projects.
For the DeleteBehavior I am expecting:
A Client cannot be deleted if he is associated with a Project.
A User cannot be deleted if he is associated with a Project.
Now, my classes look like this:
Project.cs
[Table(nameof(Project))]
public class Project
{
[ForeignKey(nameof(Owner))]
public long OwnerId { get; set; }
public virtual User Owner { get; set; }
[ForeignKey(nameof(Client))]
public long ClientId { get; set; }
public virtual Client Client { get; set; }
}
User.cs
[Table(nameof(User))]
public class User
{
public virtual ICollection<User> Projects { get; set; }
}
Client.cs
[Table(nameof(Client))]
public class Client
{
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<TimeRecord> TimeRecords { get; set; }
}
In My CustomDbContext in the OnModelCreating-Method I have added this to set the Default Behavior:
foreach (var relationship in builder.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
foreach (var foreignKeyProperty in relationship.Properties)
{
if (foreignKeyProperty != null && foreignKeyProperty.IsNullable)
{
relationship.DeleteBehavior = DeleteBehavior.ClientSetNull;
}
else
{
Console.WriteLine("DeleteBehavior.Restrict set on: " + foreignKeyProperty.Name);
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
}
}
Now after generating my migration I end up with:
table.ForeignKey(
name: "FK_Project_User_OwnerId",
column: x => x.OwnerId,
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
and:
table.ForeignKey(
name: "FK_Project_Client_ClientId",
column: x => x.ClientId,
principalTable: "Client",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
Why is it setting the onDelete for Client to ReferentialAction.Restrict and the onDelete for Owner to ReferentialAction.Cascade?!
Also is this a good way of setting the default behavior or is it rather critical and you would suggest just using the Fluent API to specify each relationship individually?

The Problem was the ICollection Projects on User. Because each Entity in the DB-Scheme also has a reference to User f.e. CreationUserId, LastModifierUserId and DeleterUserId. Therefore I think EF could not assign the ICollection and resolve the Relationship properly. Removing the ICollection solved the issue and the DeleteBehavior got set to Restrict as expected. However, if I would need the ICollection I would have to specify the relationship via Fluent API.

Related

EF Core migration adds cascade on delete in wrong direction for one to many relationship

I am using asp.net core 3 web api and EF core 3 I have a one to many relationship between 2 tables:
Task and TaskType
Task has one TaskType
TaskType has many Tasks
Here are the 2 entities:
public class TaskType
{
public TaskType() {
Tasks = new HashSet<Task>();
}
[Key]
public Guid TaskTypeID { get;set; }
...
public ICollection<Task> Tasks { get; set; }
}
public class Task
{
public Task() {
}
[Key]
public Guid TaskID {get;set;}
...
public Guid TaskTypeID { get; set; }
[ForeignKey("TaskTypeID")]
public TaskType TaskType { get; set; }
}
When I run the add-migration command it generates this code
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "TaskTypeID",
table: "Task",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.CreateIndex(
name: "IX_Task_TaskTypeID",
table: "Task",
column: "TaskTypeID");
migrationBuilder.AddForeignKey(
name: "FK_Task_TaskType_TaskTypeID",
table: "Task",
column: "TaskTypeID",
principalTable: "TaskType",
principalColumn: "TaskTypeID",
onDelete: ReferentialAction.Delete);// I want to change this to ReferentialAction.NoAction
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Task_TaskType_TaskTypeID",
table: "Task");
migrationBuilder.DropIndex(
name: "IX_Task_TaskTypeID",
table: "Task");
migrationBuilder.DropColumn(
name: "TaskTypeID",
table: "Task");
}
Notice the line onDelete: ReferentialAction.Delete which obviously is an error and should cascade the other way.
To fix this, I want to change it to ReferentialAction.NoAction
and add this code to my onModelCreating in my context
modelBuilder.Entity<TaskType>()
.HasMany(c => c.Tasks)
.WithOne(e => e.TaskType)
.OnDelete(DeleteBehavior.Cascade);
Is this the correct way to add cascade delete for a one to many relationship? Should I be modifying the generated migration code or is there another way to tell EF not to cascade delete the TaskType when I delete a Task?
Edit:
The accepted answer helped me come up with ha solution. I should have been more specific, I needed the orphaned TaskType records. So I set the TaskTypeID FK to a nullable Guid and in my OnDelete() I set the option to ReferentialAction.SetNull
The direction is correct.
If you were to delete a Task by cascading from the delete of a TaskType then you could quite possibly leave a lot of orphaned TaskType records (assuming there was no referential integrity).
The entity TaskType depends on the Task entity, so the only valid scsenario for deleting a Task is to delete all of the associated TaskType records.
One common approach to getting around this is to use a soft delete.
Essentially add a property to the entity
public bool IsDeleted { get; set; }
and you can then know that an entity is deleted, but still maintain the correct references.

Can I use an Interface with a Foreign Key in EF Core and set it as a foreign key using Fluent API?

I am trying to restrict a couple of generic methods to only be allowed Entities that inherit from the IParentOf<TChildEntity> interface, as well as accessing an Entity's Foreign Key (ParentId) Generically.
To demonstrate;
public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee)
where TParentEntity : DataEntity, IParentOf<TChildEntity>
where TChildEntity : DataEntity, IChildOf<TParentEntity>
{
foreach (TChildEntity child in (IParentOf<TChildEntity>)parent.Children)
{
(IChildOf<TParentEntity)child.ParentId = adoptee.Id;
}
}
A child entity class model would look like this,
public class Account : DataEntity, IChildOf<AccountType>, IChildOf<AccountData>
{
public string Name { get; set; }
public string Balance { get; set; }
// Foreign Key and Navigation Property for AccountType
int IChildOf<AccountType>.ParentId{ get; set; }
public virtual AccountType AccountType { get; set; }
// Foreign Key and Navigation Property for AccountData
int IChildOf<AccountData>.ParentId{ get; set; }
public virtual AccountData AccountData { get; set; }
}
First of all, is this possible to do? Or will it breakdown in EF?
Secondly, since the Foreign Keys do not follow convention (and there are multiple) how do I set them via Fluent Api? I can see how to do this in Data Annotations.
I hope this is clear, I have been considering it for a while and trying to work round it, so I can follow my argument, but it may not be clearly conveyed, so please ask for clarification if needed. My reason for wanting to do this is to make the code safe as well as automating a lot of the manual changing of classes necessary to add new associations and entities.
Thanks.
Edit
I decided to create some basic classes to implement this idea and test it, my code is as follows.
public abstract class ChildEntity : DataEntity
{
public T GetParent<T>() where T : ParentEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is T entity)
return entity;
}
return null;
}
}
public abstract class ParentEntity : DataEntity
{
public ICollection<T> GetChildren<T>() where T : ChildEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is ICollection<T> collection)
return collection;
}
return null;
}
}
public interface IParent<TEntity> where TEntity : ChildEntity
{
ICollection<T> GetChildren<T>() where T : ChildEntity;
}
public interface IChild<TEntity> where TEntity : ParentEntity
{
int ForeignKey { get; set; }
T GetParent<T>() where T : ParentEntity;
}
public class ParentOne : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Amount { get; set; }
public virtual ICollection<ChildOne> ChildOnes { get; set; }
}
public class ParentTwo : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Value { get; set; }
public virtual ICollection<ChildOne> ChildOnes { get; set; }
}
public class ChildOne : ChildEntity, IChild<ParentOne>, IChild<ParentTwo>
{
public string Name { get; set; }
public decimal Balance { get; set; }
int IChild<ParentOne>.ForeignKey { get; set; }
public virtual ParentOne ParentOne { get; set; }
int IChild<ParentTwo>.ForeignKey { get; set; }
public virtual ParentTwo ParentTwo { get; set; }
}
Data Entity simply gives each entity an Id property.
I have standard Generic Repositories set up with a Unit of Work class for mediating. The AdoptAll method looks like this in my program.
public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee, UoW uoW)
where TParentEntity : DataEntity, IParent<TChildEntity>
where TChildEntity : DataEntity, IChild<TParentEntity>
{
var currentParent = uoW.GetRepository<TParentEntity>().Get(parent.Id);
foreach (TChildEntity child in currentParent.GetChildren<TChildEntity>())
{
child.ForeignKey = adoptee.Id;
}
}
This seems to work correctly and without faults (minimal testing) are there any major flaws in doing this?
Thanks.
Edit Two
This is the OnModelCreating Method in the DbContext, which sets up the foreign key for each entity. Is this problematic?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
}
According to the updated example, you want to hide the explicit FK from the entity class public interface, and still let it be visible to EF Core and mapped to the FK column in the database.
The first problem is that the explicitly implemented interface member is not directly discoverable by EF. Also it has no good name, so the default conventions don't apply.
For instance, w/o fluent configuration EF Core will correctly create one to many associations between Parent and Child entities, but since it won't discover the int IChild<Parent>.ForeignKey { get; set; } properties, it would maintain the FK property values through ParentOneId / ParentTwoId shadow properties and not through interface explicit properties. In other words, these properties will not be populated by EF Core and also not considered by the change tracker.
To let EF Core use them you need to map both FK property and database column name using respectively HasForeignKey and HasColumnName fluent API method overloads accepting string property name. Note that the string property name must be fully qualified with the namespace. While Type.FullName provides that string for non-generic types, there is no such property/method for generic types like IChild<ParentOne> (the result has to be "Namespace.IChild<Namespace.ParentOne>"), so let first create some helpers for that:
static string ChildForeignKeyPropertyName<TParent>() where TParent : ParentEntity
=> $"{typeof(IChild<>).Namespace}.IChild<{typeof(TParent).FullName}>.{nameof(IChild<TParent>.ForeignKey)}";
static string ChildForeignKeyColumnName<TParent>() where TParent : ParentEntity
=> $"{typeof(TParent).Name}Id";
The next would be creating a helper method for performing the necessary configuration:
static void ConfigureRelationship<TChild, TParent>(ModelBuilder modelBuilder)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
var childEntity = modelBuilder.Entity<TChild>();
var foreignKeyPropertyName = ChildForeignKeyPropertyName<TParent>();
var foreignKeyColumnName = ChildForeignKeyColumnName<TParent>();
var foreignKey = childEntity.Metadata.GetForeignKeys()
.Single(fk => fk.PrincipalEntityType.ClrType == typeof(TParent));
// Configure FK column name
childEntity
.Property<int>(foreignKeyPropertyName)
.HasColumnName(foreignKeyColumnName);
// Configure FK property
childEntity
.HasOne<TParent>(foreignKey.DependentToPrincipal.Name)
.WithMany(foreignKey.PrincipalToDependent.Name)
.HasForeignKey(foreignKeyPropertyName);
}
As you can see, I'm using EF Core provided metadata services to find the names of the corresponding navigation properties.
But this generic method actually shows the limitation of this design. The generic constrains allow us to use
childEntity.Property(c => c.ForeignKey)
which compiles fine, but doesn't work at runtime. It's not only for fluent API methods, but basically any generic method involving expression trees (like LINQ to Entities query). There is no such problem when the interface property is implemented implicitly with public property.
We'll return to this limitation later. To complete the mapping, add the following to your OnModelCreating override:
ConfigureRelationship<ChildOne, ParentOne>(modelBuilder);
ConfigureRelationship<ChildOne, ParentTwo>(modelBuilder);
And now EF Core will correctly load / take into account your explicitly implemented FK properties.
Now back to limitations. There is no problem to use generic object services like your AdoptAll method or LINQ to Objects. But you can't access these properties generically in expressions used to access EF Core metadata or inside LINQ to Entities queries. In the later case you should access it through navigation property, or in both scenarios you should access in by the name returned from the ChildForeignKeyPropertyName<TParent>() method. Actually queries will work, but will be evaluated locally thus causing performance issues or unexpected behaviors.
E.g.
static IEnumerable<TChild> GetChildrenOf<TChild, TParent>(DbContext db, int parentId)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
// Works, but causes client side filter evalution
return db.Set<TChild>().Where(c => c.ForeignKey == parentId);
// This correctly translates to SQL, hence server side evaluation
return db.Set<TChild>().Where(c => EF.Property<int>(c, ChildForeignKeyPropertyName<TParent>()) == parentId);
}
To recap shortly, it's possible, but use with care and make sure it's worth for the limited generic service scenarios it allows. Alternative approaches would not use interfaces, but (combination of) EF Core metadata, reflection or Func<...> / Expression<Func<..>> generic method arguments similar to Queryable extension methods.
Edit: Regarding the second question edit, fluent configuration
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
produces the following migration for ChildOne
migrationBuilder.CreateTable(
name: "ChildOne",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ForeignKey = table.Column<int>(nullable: false),
Name = table.Column<string>(nullable: true),
Balance = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChildOne", x => x.Id);
table.ForeignKey(
name: "FK_ChildOne_ParentOne_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentOne",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ChildOne_ParentTwo_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentTwo",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Note the single ForeignKey column and the attempt to use it as foreign key to both ParentOne and ParentTwo. It suffers the same problems as using a constrained interface property directly, so I would assume it not working.

Mapping One-to-Zero-or-One with EF7

I am currently in the process of cleaning up a fairly large database. Part of the database has a relationship which is a one-to-zero-or-one mapping. Specifically:
User -> UserSettings
Not all users will have user settings, but a user setting cannot exist without the user. Unfortunately, the tables already exist. User has an PK ID. UserSettings has a PK ID and a column, User_Id_Fk which, at this point in time, is not a true FK (there is no relationship defined).
I'm in the process of fixing that and have done so from the DB perspective through SQL and have confirmed with tests. (Added the FK constraint. Added a unique constraint on User_Id_Fk.) This was all done on the UserSettings table. (Note: I am not using EF Migrations here. I have to manually write the SQL at this point in time.)
However, I now need to wire up an existing application to properly handle this new mapping. The application is using ASP.NET Core 1.0 and EF7. Here are (shortened) versions of the existing data models.
public class User
{
public int Id { get; set; }
public virtual UserSettings UserSettings { get; set; }
}
public class UserSettings
{
public int Id { get; set; }
[Column("User_Id_Fk")]
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
}
I have this Fluent Mapping as well:
builder.Entity<UserSettings>()
.HasOne(us => us.User)
.WithOne(u => u.User)
.IsRequired(false);
When I go to run the application and access these items in the database, I get this error followed with a cryptic set of messages that has no information relating directly back to my application.:
ArgumentNullException: Value cannot be null.
Parameter name: navigation
Microsoft.Data.Entity.Utilities.Check.NotNull[T] (Microsoft.Data.Entity.Utilities.T value, System.String parameterName) <0x10d28a650 + 0x00081> in <filename unknown>, line 0
After doing research, someone had mentioned that the ID of the UserSettings class must be the same as the foreign key, like so:
public class UserSettings
{
[Key, ForeignKey("User")]
public int Id { get; set; }
public virtual User User { get; set; }
}
I don't really have this as an option as the DB is being used for other applications I have no control over at this point. So, am I stuck here? Will I just have to maintain a 1:many mapping (which could happen now, though it hasn't) and not have proper constraints for a 1:0..1 mapping?
Update
Looking at octavioccl's answer below, I tried it out without any success. However, I then removed User from the mapping in UserSettings (but I left UserId). Everything appeared to work as far as I can tell. I'm really confused what is going on here, however, and if this is even the right answer, or if I'm just getting lucky.
Remove the data annotations and try with these configurations:
builder.Entity<UserSettings>()
.Property(b => b.UserId)
.HasColumnName("User_Id_Fk");
builder.Entity<User>()
.HasOne(us => us.UserSettings)
.WithOne(u => u.User)
.HasForeignKey<UserSettings>(b => b.UserId);
From EF Core documentation:
When configuring the foreign key you need to specify the dependent
entity type - notice the generic parameter provided to HasForeignKey
in the listing above. In a one-to-many relationship it is clear that
the entity with the reference navigation is the dependent and the one
with the collection is the principal. But this is not so in a
one-to-one relationship - hence the need to explicitly define it.
The example that is presented in the quoted link (Blog-BlogImage) is pretty much the same of what are you trying to achieve.
If the solution that I show above doesn't work, then you should check if User_Id_Fk column allows null. If that is the case, change the FK property type to int?:
public class UserSettings
{
public int Id { get; set; }
public int? UserId { get; set; }
public virtual User User { get; set; }
}

MVC 4 Entity Framework Optional Foreign Key On User Profile

In my User model I need an optional constraint to an active playlist.
public class User
{
//.. Properties
public int? ActivePlaylistID { get; set; }
public virtual Playlist ActivePlaylist { get; set; }
}
In my database i have ActivePlaylistID set to nullable with a relationship established as: 'ActivePlaylistID' is a foreign key on table 'Playlists', column 'ID'.
In my Playlist model i have:
public class Playlist : BaseModel
{
//.. Properties
public int CreatedByUserID { get; set; }
public virtual User CreatedByUser { get; set; }
}
The CreatedByUserID relationship is established in my database and the model property is set in my controller before saving a new Playlist.
I get the following error at this point in the setup:
Unable to determine the principal end of an association between the types 'SyncFinder.Models.Playlist' and 'SyncFinder.Models.User'.
The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
So in my DbEntity i added the following to my model builder:
modelBuilder.Entity<User>()
.HasOptional(x => x.ActivePlaylist)
.WithRequired(x => x.CreatedByUser);
At this point the view loads, but when trying to add a new playlist to my database i get:
A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'ID'.
I am not sure how to resolve this constraint issue and the stacktrace isn't providing much detail except that the operation fails when trying to save the playlists repository.
Only one half of the relation was bound in the model binder. I am uncertain of the full support for one-to-one relations in Entity Framework, but for your example completing the relation should work
modelBuilder.Entity<Playlist>().HasKey(e => e.CreatedByUserID);
modelBuilder.Entity<User>()
.HasOptional(x => x.ActivePlaylist)
.WithRequired(x => x.CreatedByUser);

Many to many relationship identity error

I'm working on a mvc4 app with ef5 codefirst and I cannot solve this error:
The member with identity 'xxxx' does not exist in the metadata collection.
Update:
I saw that I used two different contexts (the navigation object was called thorugh a repository that creates a different DbContext), probably this is a problem. I changed that, but now I get a new error:
Invalid column name 'Brewery_BreweryId'.
In the IntelliTrace I saw that ef tries to
select ..., Brewery_BreweryId from UserProfiles
This column is not present and shouldn't be present, I want a many to many, not a one-to-many.
I think that is something related to a many to many relation.
this is an example of my code
internal class BreweryConfiguration : EntityTypeConfiguration<Brewery>
{
public BreweryConfiguration()
{
// PK
HasKey(e => e.BreweryId);
// FK
HasMany(e => e.UserProfiles)
.WithMany()
.Map(m =>
{
m.MapLeftKey("BreweryId");
m.MapRightKey("UserId");
m.ToTable("BreweryUserProfiles");
});
namespace Project2.DAL.Entities
{
[Table("Breweries")]
public class Brewery : ABrewery
{
public int BreweryId { get; set; }
public ICollection<UserProfile> UserProfiles { get; set; }
}
}
namespace Project1.DAL.Entities
{
[Table("UserProfiles")]
public class UserProfile : IUserProfile
{
[Key]
public int UserId { get; set; }
...
}
}
c.MapLeftKey("ClassB_ID");
c.MapRightKey("ClassA_ID");
should be
c.MapLeftKey("ClassA_ID");
c.MapRightKey("ClassB_ID");
Edit:
You need to define the PK of the ClassB in the configuration as well. In the way you implemented, you may add another derived Configuration for ClassB.

Categories

Resources