Entity Framework POCO - Possible to set Cascade? - c#

I have your basic recursive categories that are linked between each other. When I try to delete a category that has children I get your usual error.
What I always did is made a functions to recursively delete all children but I wonder can I just somehow set CASCADE ON DELETE somehow to my POCO class that is using EF so I would not need implement my own deletion mechanics?
Error
The DELETE statement conflicted with the SAME TABLE REFERENCE
constraint "FK_dbo.Categories_dbo.Categories_RootCategoryId". The
conflict occurred in database "Website", table "dbo.Categories",
column 'RootCategoryId'.
Model
public class Category
{
public int Id { get; set; }
public int? RootCategoryId { get; set; }
public virtual Category RootCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}
What I have now
Currently I delete relation before deleting a category. But what if I want to delete all child-categories recursively? Only cascading them will accomplish that.
public ActionResult Delete(int id)
{
var category = _db.Categories.Single(x => x.Id == id);
if (category.RootCategoryId == null)
{
category.ChildCategories.ToList().ForEach(x => x.RootCategoryId = null);
}
else
{
category.ChildCategories.ToList().ForEach(x => x.RootCategoryId = category.RootCategoryId);
}
_db.Categories.Remove(category);
_db.SaveChanges();
return RedirectToAction("Index", "Category");
}

The way I would approach this issue would be to use the OnModelCreating Fluent api.
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasMany(u => u.ProjectAuthorizations)
.WithRequired(a => a.UserProfile)
.WillCascadeOnDelete(true);
}

Related

Entity Framework Core: Check if an entity part of a One-to-Many relationship exists on Add

I am very new to Entity Framework Core (Entity Framework in General), and I have watched several tutorial videos and done a Pluralsight course.
I am using ASP.NET Web API, and I want to add a new entity, that has a One-to-Many relationship. The models are as follows:
"Parent" Class:
public partial class VerificationVoltage
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string Type { get; set; }
public int VerificationVoltageSerialId { get; set; }
public VerificationVoltageSerial Serial { get; set; }
}
"Child" Class:
public class VerificationVoltageSerial
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(20)]
public string Serial { get; set; }
public List<VerificationVoltage> VerificationVoltage { get; set; }
}
I would like the VerificationVoltageSerial.Serial to be unique, and that the database will first check whether or not the serial exists, before adding the newly added serial (The serials themselves are unique).
Is there a way to do the following: If a row with the same serial exist, as what needs to be added, entity framework then automatically selects the Serial and SerialId and populates the Entity that is being added, with the serial selected from the database.
This is what I am doing currently, I feel that this is a very manual check, an maybe there is something more automatic: (i.e I want it to wrok, even if I remove the IF Statement and the exisitngEntry query in the controller)
[HttpPost]
public async Task<VerificationVoltage> AddVerificationVoltage(VerificationVoltage verificationVoltage)
{
var exisitngEntry = await repository.GetBySerialNoTracking(verificationVoltage.Serial.Serial)
.ConfigureAwait(false);
if (exisitngEntry != null)
{
var existingSerial = exisitngEntry.Serial;
verificationVoltage.Serial.Id = existingSerial.Id;
verificationVoltage.Serial = new VerificationVoltageSerial()
{
Id = existingSerial.Id,
Serial = existingSerial.Serial,
};
}
var addedEntry = repository.Add(verificationVoltage);
await repository.SaveChanges().ConfigureAwait(false);
return addedEntry;
}
public async Task<VerificationVoltage> GetBySerialNoTracking(string serialNumber)
{
return await DbSet.Include(a => a.Serial)
.Where(a => a.Serial.Serial.Equals(serialNumber))
.OrderBy(a => a.VerificationVoltageSerialId)
.AsNoTracking()
.FirstOrDefaultAsync()
.ConfigureAwait(false);
}
My protected override void OnModelCreating(ModelBuilder modelBuilder) method, only repeats the attributes:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ValidationLabAmbientMeasurements>()
.HasOne(a => a.AmbientMeasurementsIdentifier)
.WithMany();
modelBuilder.Entity<VerificationVoltage>()
.Property(a => a.Id)
.ValueGeneratedOnAdd();
modelBuilder.Entity<VerificationVoltage>()
.HasOne(a => a.Serial)
.WithMany(nameof(VerificationVoltage));
modelBuilder.Entity<VerificationVoltage>()
.HasMany(a => a.VerificationVoltageMeasurements)
.WithOne(nameof(VerificationVoltage));
}
I have tried searching for answers, but my search queries do not get results on this specific issue.

Foreign key not populating in Entity Framework

I cannot get a table to update correctly that should be linking two of my entities. To explain in more detail...I have two entities, Class and Teacher, with a relationship in the form of:
Teacher can be assigned to many classes
Class can only have one teacher.
Below are these two entities.
public class Teacher
{
[Required, Key]
public Guid Id { get; private set; }
[StringLength(50)]
public string Name { get; set; }
public string Email { get; set; }
public List<Class> Classes = new List<Class>();
public Teacher()
{
Id = new Guid();
}
public Teacher(Guid id)
{
Id = id;
}
public void AssignClass(Class newClass)
{
Classes.Add(newClass);
}
}
public class Class
{
[Required, Key]
public Guid Id { get; private set; }
[Required, StringLength(20)]
public string Name { get; set; }
[Required, Range(5, 30)]
public int Capacity { get; set; }
public Teacher Teacher { get; set; }
public IEnumerable<StudentClass> StudentClasses { get; set; }
public Class()
{
Id = new Guid();
}
public Class(Guid id)
{
Id = id;
}
}
When I generate my migrations I get a foreign key of TeacherId in the Classes table as expected. Here is the SQL:
CREATE TABLE [dbo].[Classes] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (20) NOT NULL,
[Capacity] INT NOT NULL,
[TeacherId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Classes] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Classes_Teachers_TeacherId] FOREIGN KEY ([TeacherId]) REFERENCES [dbo].[Teachers] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_Classes_TeacherId]
ON [dbo].[Classes]([TeacherId] ASC);
My class derived of DBContext looks like:
public class SchoolDatabaseContext : DbContext
{
public DbSet<Student> Students { get; private set; }
public DbSet<Class> Classes { get; private set; }
public DbSet<Teacher> Teachers { get; private set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public SchoolDatabaseContext(DbContextOptions<SchoolDatabaseContext> options) : base(options)
{
}
}
No configuration for those entities yet. I use DI to serve the DbContext to the controller and that all seems fine.
I have aimed for a DDD type structure, but to make this issue easier to debug I have stripped everything all the way back to the controller so it is basically... controller => DbContext.
Here is my code in the controller:
[HttpPost]
[Route("assign-teacher-to-class")]
public async Task<IActionResult> AssignClass([FromBody] AssignTeacherToClass assignTeacherToClass)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var teacher = await schoolDatabaseContext.Teachers.FindAsync(assignTeacherToClass.TeacherId);
var classToAssign = await schoolDatabaseContext.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
schoolDatabaseContext.Entry(teacher).State = EntityState.Modified;
await schoolDatabaseContext.SaveChangesAsync();
return Ok(teacher);
}
When I debug through the ids are fine from the post body, they are assigned correctly to the DTO AssignClass and the calls to the DbContext to find the data for each type (teacher and class) are fine. I then call a method in my teacher type to add the class to the List Classes property (see teachers entity code at beginning for reference), I then Save the changes with the DbContext method and Problem Defined Here: at no stage does the TeacherId in the database update whilst debugging/completing. I have tried all I can think of like instantiating collections in different ways, changing collection types, looking for config that might help map these entities in this way, stripping out all extra layers, changing accessibility of properties and classes and few more.
Any help would really be appreciated as I am getting a bit defeated on this one and I feel like this relationship should be fairly straight forward. I actually was able to get my many to many working with a bridge class so I was surprised to get stuck on this one :(
Thanks
try this:
var teacher = await schoolDatabaseContext.Teachers.Include(x => x.Classes).SingleOrDefaultAsync(x => x.Id == assignTeacherToClass.TeacherId);
I don't think teacher.Classes gets tracked by DbContext otherwise.
There are multiple ways to accomplish this with EF Core. It is easiest to find if you call it what the docs call it "Related Data".
Here is the parent doc: Related Data
Specifically as #Y Stroli has illustrated the Eager Loading method.
The below example is shown on the eager loading reference to load multiple levels of related data:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.ToList();
}
As of EF Core 5.0 you can also do filtered includes:
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToList();
}
As the suggestion from lvan, you should change public List<Class> Classes = new List<Class>(); to public List<Class> Classes { get; set; } = new List<Class>();.
For your current code, it seems you want to add Class and return the teacher, if so, you need to include the exsiting classes to teacher like below, otherwise, it will only return the new adding class.
public async Task<IActionResult> AssignClass()
{
var assignTeacherToClass = new AssignTeacherToClass {
TeacherId = new Guid("52abe5e0-bcd4-4827-893a-26b24ca7b1c4"),
ClassId =new Guid("50354c76-c9e8-4fc3-a7c9-7644d47a6854")
};
var teacher = await _context.Teachers.Include(t => t.Classes).FirstOrDefaultAsync(t => t.Id == assignTeacherToClass.TeacherId);
var classToAssign = await _context.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
_context.Entry(teacher).State = EntityState.Modified;
await _context.SaveChangesAsync();
return Ok(teacher);
}
One more note, you need to configure SerializerSettings.ReferenceLoopHandling like
services.AddMvc()
.AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You need to define the connection between Teacher and Class.
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Class>()
.HasOne<Teacher>(p => p.Teacher)
.WithMany(q => q.Classes)
.HasForeignKey(r => r.TeacherId);
}
Also add TeacherId prop to Class.

Conflict with reference constraint in TPT code-first Entity Framework

I am using TPT code-first in Entity Framework 6 and have the following setup:
public abstract class Product
{
[Key]
public string ProductID { get; set; }
// a bunch of trivial properties like dates and floats
}
[Table("SpecialProducts")]
public class SpecialProduct : Product
{
// more trivial properties
public List<Property> MyProperties { get; set; }
}
public class Property
{
[Key]
public int ID { get; set; }
[Required]
public SpecialProduct Product { get; set; }
// property data
}
public class MyDbContext : DbContext
{
public DbSet<Product> AllProducts { get; set; }
public MyDbContext()
: base("MyDataBase")
{}
public RemoveSomeProducts()
{
var products = from product in AllProducts where /* some condition */ select product;
AllProducts.RemoveRange(products);
SaveChanges();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// I know I don't need both statements, and my guess is I need the first, but at this point I don't know anything anymore
modelBuilder.Entity<Property>()
.HasRequired(property => property.Product)
.WithMany(product => product.MyProperties)
.WillCascadeOnDelete(true);
modelBuilder.Entity<SpecialProduct>()
.HasMany(product => product.MyProperties)
.WithRequired(property => property.Product)
.WillCascadeOnDelete(true);
}
}
When calling RemoveSomeProducts() I get the following Exception:
SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.Properties_dbo.SpecialProducts_Product_ProductID". The conflict occurred in database "MyDataBase", table "dbo.Properties", column 'Product_ProductID'.
To me this sounds like the Properties belonging to the deleted SpecialProducts are not being deleted. I have little experience with databases, but from my understanding this should be fixed using cascade delete, but I seem to fail to configure this.
So my question is obviously: how can I fix this?
Potential duplicates that did not seem to help in my case, but might be useful for someone else:
EF6 Cascade Delete
Code First Cascade Delete
TPT Cascade Delete
First of all you must include your navigation property in query explicitly. Than for some reason RemoveRange doesn't work as expected with cascade delete, but if you iterate and remove one by one it works.
var products = Set<SpecialProduct>().Include(p => p.MyProperties).ToList();
products.ForEach(p => AllProducts.Remove(p));
SaveChanges();

SQLite EF6 Cascade delete on Many to Many

I have the following two classes:
[Table("Products")]
public class Product
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public virtual ICollection<Process> Processes { get; set; }
public Product()
{
this.Processes = new HashSet<Process>();
}
}
and
[Table("Processes")]
public class Process
{
public int Id { get; set; }
public string Name { get; set; }
public string MachineName { get; set; }
//list of all products
public virtual ICollection<Product> Products { get; set; }
public Process()
{
this.Products = new HashSet<Product>();
}
}
As you can see, One product can have multiple processes and one process can be bound to multiple products. (e.g. product Chair consists of the following processes: Cutting, Drilling, Screwing, etc...
Under my OnModelCreating method in my DataContext I have specified Many to Many relationships as follows:
modelBuilder.Entity<Process>()
.HasMany<Product>(s => s.Products)
.WithMany(c => c.Processes)
.Map(cs =>
{
cs.MapLeftKey("Process_ProcessId");
cs.MapRightKey("Product_ProductId");
cs.ToTable("ProcessProducts");
});
Which creates a new table named ProcessProducts where many to many relationships are being stored.
My Problem now is, that when I remove e.g. Product from my database, I would like to automatically remove all rows from ProcessProducts table, where the particular ProductId has been used. I wanted to configure CascadeDelete under my modelBuilder, but it does not allow me to do so.
Maybe it's the way, how I am removing the item which is wrong?
public void Delete(TEntity entity)
{
if (entity == null) throw new ArgumentNullException("entity");
_context.Set<TEntity>().Attach(entity);
_context.Set<TEntity>().Remove(entity);
_context.SaveChanges();
}
Any help in regards to this matter would be highly appreciated.
Do delete in cascade mode you can use SQL or EF.
Each case need to be configured how deep the cascade will go. So the question is: How deep you want to go:
If you delete a Product should delete de Process(es) connected with it and if this Process(es) has other Product(s) should cascade keep this chain of deletion?
Perhaps by cascade delete, you only meant delete the connection (many-to-many relationship) between Product and Process. If this is the case you only need to do:
public void DeleteProduct(Product entity)
{
if (entity == null) throw new ArgumentNullException("entity");
entity = _context.Set<Product>().First(f => f.Id == entity.Id);
_context.Set<Product>().Remove(entity);
_context.SaveChanges();
}
This should delete your Product and all ProcessProducts registries connected with your Product.

Deleting parent Entities without deleting related child

Hi I'm using Entity framework to set my database.I have one-many relation entity and I want to only delete parent entity without cascading deletion. I'm getting error from deleting from parents but I really want to keep my child as a recording reason. Is there any ways to delete only parents without an error?
This is an old question but I came across this problem today so I'll share the solution that I found.
The long-story-short version is that you need to eager load your child association. Assuming you have your relationships setup correctly and a Foo has many Bars, this code should do exactly what you want:
public void Delete(Guid fooId)
{
using (var context = new MyDbContext())
{
var foo = context.Foos.Include("Bars").FirstOrDefault(foo => foo.Id == fooId);
if (foo != null)
{
context.Foos.Remove(foo);
context.SaveChanges();
}
}
}
The key here is the call to .Include. Without this, the update will fail with a foreign key violation.
Now, when I say I assume you have your relationships setup right, I mean they should look like this.
// Foo.cs
public class Foo
{
public Guid Id { get; set; }
public ICollection<Bar> Bars { get; set; }
}
// Bar.cs
public class Bar
{
public Guid Id { get; set; }
public Guid? FooId { get; set; }
public virtual Foo Foo { get; set; }
}
// MyDbContext
modelBuilder.Entity<Foo>()
.HasMany(e => e.Bars)
.WithRequired(e => e.Foo)
.HasForeignKey(e => e.FooId)
.WillCascadeOnDelete(false);

Categories