Cascading delete with Entity Framework - c#

I have a comments table which contains amongst other things the columns Id - the ID of the comment, Commenter_Id the ID of the user who posted it, and ParentComment_Id a self referencing foreign key. There are only two levels to the commenting, parents and sub comments. If a comment record has a ParentComment_Id of null, then it is a parent comment.
I'm trying to write an expression to delete all the comments for a user, but this is causing problems because of that self reference I mentioned earlier.
Take this records sample as an example of the problem:
User with ID 2 posted a comment, with an ID of 3. Because this was the parent comment it has a ParentComment_Id value of null. Later on, User with ID 1 responds to that comment with a sub-comment, creating comment 7 (there were other comments/subcomments between these two hence the Id increment jump).
I'm not able to delete Comment ID 3 because the sub comment, Comment ID 7, has a foreign key to it.
Currently my Entity Framework statement for trying to delete comments is as follows:
context.Comments.Where(x => x.Commenter.Id == user.Id).Delete();
But this gives me an exception because of the described problem.
I could probably fix this using a few foreach loops, but I was hoping there is an easier way like context.Cascade().Where(.... For those wondering the Delete() method is part of the EntityFramework.Extended package.

If you have an entity like this:
public class Comment
{
public int Id{get;set;}
public int? ParentCommentId{get;set;}
public virtual Comment ParentComment{get;set;}
public virtual ICollection<Comment> Comments{get;set;}
}
You could try with this configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Comment>()
.HasOptional(c=>c.ParentComment)
.WithMany(c=>c.Comments)
.HasForeignKey(c => c.ParentCommentId)
.WillCascadeOnDelete(true);
}
You can configure cascade delete on a relationship by using the WillCascadeOnDelete method.If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null.

You can try adding this to your dbcontext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Comments>()
.HasForeignKey(d => d.ParentCommentId)
.WillCascadeOnDelete(true);
}

Related

Unable to delete entity which has owned entity in EntityFramework Core

I have the following entity:
public class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
public string State { get; set; }
}
And using fluent API I have configured the owned entity as following:
private void ConfigureEmployee(EntityTypeBuilder<Employee> builder)
{
builder.OwnsOne(x => x.Address, w =>
{
w.Property(x => x.City).HasMaxLength(100);
w.Property(x => x.State).HasMaxLength(100);
});
}
And when I try to delete Employee entity :
var employee = dbContext.Employees.AsNoTracking().FirstOrDefault();
dbContext.Entry(employee).State = EntityState.Deleted;
dbContext.SaveChanges();
I got the following exception:
The entity of type 'Employee' is sharing the table 'Employees' with entities of type 'Employee.Address#Address', but there is no entity of this type with the same key value '{Id: 1ad382d7-4064-49a3-87ee-633578175247}' that has been marked as 'Deleted'.
I have tried the workaround specified Here but it didn't work.
I am using EntityFrameworkCore v2.2.4
The problem you are facing is due to the way you load the entity. When you do:
var employee = dbContext.Employees.AsNoTracking().FirstOrDefault();
You're basically saying to EF: Load this Employee for me but forget about it. Treat it as if you never had loaded it in the first place.
Next, you want to delete it. You know that it's not being tracked by DbContext so you do:
dbContext.Entry(employee).State = EntityState.Deleted;
This is the "key" to the issue. This line tells the DbContext: Hey, Please start tracking this entity and mark it as to be deleted.
The issue: The Employee entity owns an address but the DbContext is not aware that you also want to delete it.
The error message you get offers a lot of insight on the actual error, but it might not be that clear at first sight:
The entity of type 'Employee' is sharing the table 'Employees' with entities of type 'Employee.Address#Address', but there is no entity of this type with the same key value '{Id: 1ad382d7-4064-49a3-87ee-633578175247}' that has been marked as 'Deleted'.
This is saying: The entity Employee with id 4 is marked as Deleted but it also has an entity of type Address which was not added to be deleted. Although you don't have Address declared as a DbSet in your context, it is still an actual Entity as far as EF is concerned.
To fix it while keeping the same code as you have you need to also add the Address to be deleted:
context.Entry(employee.Address).State = EntityState.Deleted;
But:
I'm not sure if this is just a sample or if it's your real code. But, I personally (and I see many also against it) try to avoid as much as possible manipulating the entity states manually. This can get nasty pretty quickly and produce, as you already experienced it not so obvious results. Instead, if you have a DbContext which you load the entity you want to delete, you can avoid messing with states and problems by just changing your code to this:
var employee = dbContext.Employees.First();
// .Remove will also mark the related entities to be deleted
// If Employee is not tracked, it will also start tracking it
// So, it's not necessary to do .Attach()
dbContext.Remove(employee);
dbContext.SaveChanges();
This will work and the entity will be deleted as expected. Of course, if your code is not that and you are, in fact working with entities in a disconnected scenario, then you need to manually set it to be deleted as I showed above.
Edit:
As pointed out in the comments by #IvanStoev, the Remove method is what actually can fix the behavior you are facing. The Remove method will mark the entity itself plus the related ones as Deleted and if not previously tracked, will also start tracking them. From the docs: (emphasis from me)
If the entity is already tracked in the Added state then the context will stop tracking the entity (rather than marking it as Deleted) since the entity was previously added to the context and does not exist in the database.
Any other reachable entities that are not already being tracked will be tracked in the same way that they would be if Attach(Object) was called before calling this method. This allows any cascading actions to be applied when SaveChanges() is called.
DbContext.Remove Method
You have to use cascade delete like below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOptional<Standard>(s => s.Address)
.WithMany()
.WillCascadeOnDelete(false);
}

Asp.net MVC5 Identity

I want to ask you some questions about asp.net mvc5. I'm writing right now a mid-advanced (for me for sure :D) application. What i want to get and what is my problem?
I created a basic MVC page with registration and login from asp.net template. Now i want to extend this page with my ideas.
First: I want to add new entities which will have a foreign keys from other tables (for example from AspNetUser). How can I push data into database when i need as foreign key User ID from table AspNetUser? - I think that in my model class i have to add public ApplicationUser user {get;set;} and ICollection myModels {get;set;} in ApplicationUser class, then in controller my Action must get all parameters (included foreign key), but what next? How can i get User id from table AspNetUser? Can you give me, please, a basic example of this idea?
Second: What is better? Code first or Database first?
Please, help me. This is very important for me, because without this i will not graduate..
You have to look inside EntityValidationErrors to find out what exactly went wrong, if I had to guess I then think it has to do with this line in your ApplicationUser class:
public ICollection<TaskModel> TaskModels { get; set; }
First, you should mark this property with the virtual keyword to enabled lazy loading.
Then you need to configure this One-To-Many relationship in Entity Framework. You can do that by overriding the OnModelCreating method in your OtherDbContext, then configuring the relationship like so:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>()
.HasMany(e => e.TaskModel)
.WithRequired(e => e.ApplicationUser)
.WillCascadeOnDelete(false);
}
But, you don't really need this unless you plan on using the TaskModels collection, so you can just remove the collection completely and that should fix your problem.
BTW, I personally prefer to implement my foreign key relationships like this:
[ForeignKey(nameof(User))]
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
This way my foreign key reference is not a magic string, and it's right next to the navigation property. I find it much cleaner and safer.
I haven't tested any of this code but hopefully I got it right :-)
So.. I created new project and did it all over again. It works even without OnModelCreating and virtual property. I dont know what was wrong, but I guess that was something with Database - I mean migrations etc.
for everyone who has the same problem:
Just add virtual properties to our new model class (before this you have to decide what relation you want to use ;)), then in IdentityModel.cs add another virtual properties to class ApplicationUser and remember to use correct DbContext :)
Thanks guys for help and have a nice day!!!

Entity Framework one to zero one association reference key mapping [duplicate]

I am trying to establish a One-to-Zero-or-One relationship between two entities and I want the dependent entity to still contain its own Indentity column, instead of it being a shared key.
I want to do as much as possible following the conventions and not declaring explicitly anything that does not require explicit declaration (so, no unnecessary data annotations or fluent api clauses)
The entites:
public class File
{
public int FileId {get;set;}
//some omitted file properties
public virtual Task Task {get;set;}
}
public class Task
{
public int TaskId {get;set;}
//some omitted task properties
public int FileId {get;set;}
public virtual File File {get;set;}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<File>().HasOptional(f => f.Task).WithRequired(t => t.File);
base.OnModelCreating(modelBuilder);
}
This creates a weird relation where the TaskId is both PK and FK column of the Tasks table. Which, I think means that it should have the same value as the file ID? (That is a question:) )
So, how do I make TaskId hold its own, sequential value and have FileId become a foreign key to the Files table?
Or maybe in case of 1-0..1 I should rather get rid of the TaskId property and make FileId the PK/FK property?
Cheers!
Bidirectional one-to-one relationship with explicit FK property is not supported.
So either continue using what you have now - Shared Primary Key association. Just get rid of one of the TaskId or FileId properties from Task and make the remaining a PK (EF will automatically use it as FK because that's the default EF one-to-one relationship model).
Or get rid of the FieldId property from Task and use the following fluent configuration (all is necessary):
modelBuilder.Entity<File>()
.HasOptional(f => f.Task)
.WithRequired(t => t.File)
.Map(m => m.MapKey("FileId"))
.WillCascadeOnDelete();
But I would recommend using the first approach (if there is no special reason of not doing it like existing database etc.) because it's better supported - the second includes some LEFT OUTER JOINs in SQL queries as you can see from this post EF - WithOptional - Left Outer Join?.

Entity Framework Code First: Custom Mapping

public class User
{
public int Id {get;set;}
public string Name {get;set}
public ICollection<User> Followers {get;set;}
public ICollection<User> Following {get;set;}
}
My Model looks like above, Entity framework automatically creates A table and UserUser with rows User_ID and User_ID1 in DB to map this model. I want to map that table and rows myself.
How can i do that, Thanx!!
From Scott Gu's blog about Many-valued Associations:
Many-to-Many Associations
The association between Category and Item is a many-to-many
association, as can be seen in the above class diagram. a many-to-many
association mapping hides the intermediate association table from the
application, so you don’t end up with an unwanted entity in your
domain model. That said, In a real system, you may not have a
many-to-many association since my experience is that there is almost
always other information that must be attached to each link between
associated instances (such as the date and time when an item was added
to a category) and that the best way to represent this information is
via an intermediate association class (In EF, you can map the
association class as an entity and map two one-to-many associations
for either side.).
In a many-to-many relationship, the join table (or link table, as some
developers call it) has two columns: the foreign keys of the Category
and Item tables. The primary key is a composite of both columns. In EF
Code First, many-to-many associations mappings can be customized with
a fluent API code like this:
class ItemConfiguration : EntityTypeConfiguration<Item> {
internal ItemConfiguration()
{
this.HasMany(i => i.Categories)
.WithMany(c => c.Items)
.Map(mc =>
{
mc.MapLeftKey("ItemId");
mc.MapRightKey("CategoryId");
mc.ToTable("ItemCategory");
});
} }
Register this configuration in your DbContext's (you using the DbContext api right?) like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ItemConfiguration());
}
Good luck, hope this help!
To map an entity to itself, you would do something like this
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(u => u.Followers)
.WithMany().ForeignKey(u => u.FollowerId);
base.OnModelCreating(modelBuilder);
}
its hard to tell without seeing your database model though, and how you actually relate the followers to the user.

A dependent property in a ReferentialConstraint is mapped to a store-generated column

I get this error when writing to the database:
A dependent property in a ReferentialConstraint is mapped to a
store-generated column. Column: 'PaymentId'.
public bool PayForItem(int terminalId, double paymentAmount,
eNums.MasterCategoryEnum mastercategoryEnum, int CategoryId, int CategoryItemId)
{
using (var dbEntities = new DatabaseAccess.Schema.EntityModel())
{
int pinnumber = 0;
long pinid = 1; //getPinId(terminalId,ref pinnumber) ;
var payment = new DatabaseAccess.Schema.Payment();
payment.CategoryId = CategoryId;
payment.ItemCategoryId = CategoryItemId;
payment.PaymentAmount = (decimal)paymentAmount;
payment.TerminalId = terminalId;
payment.PinId = pinid;
payment.HSBCResponseCode = "";
payment.DateActivated = DateTime.Now;
payment.PaymentString = "Payment";
payment.PromotionalOfferId = 1;
payment.PaymentStatusId = (int)eNums.PaymentStatus.Paid;
//payment.PaymentId = 1;
dbEntities.AddToPayments(payment);
dbEntities.SaveChanges();
}
return true;
}
The schema is:
Is it possible that you defined a bad column relation between your tables?
In my case, I had different columns and one was set as autonumeric.
This error says that you are using unsupported relation or you have error in your mapping. Your code is most probably absolutely unrelated to the error.
The error means that you have some relation between entities where foreign key property in dependent entity is defined as store generated. Store generated properties are filled in the database. EF doesn't support store generated properties as foreign keys (as well as computed properties in primary keys).
I had the same problem. Based on the answers provided here I was able to track it and solve it, but I had a strange issue described below - it might help somebody in the future.
On my dependent tables, the foreign Key columns have been set to StoreGeneratedPattern="Identity". I had to change it to "None". Unfortunately, doing so inside designer didn't work at all.
I looked in the designer-generated XML (SSDL) and these properties were still there so I removed them manually. I also had to fix the columns on the database (remove the Identity(1,1) from CREATE TABLE SQL)
After that, the problem went away.
I had the same problem and after some digging in table design in sql server , I found that mistakenly i set table's primary key also as foreign key.
In this image you can see that JobID is table's primary key but also mistakenly foreign key.
My problem was caused by redundant defining of the Primary key in the configuration.
this
.Property(p => p.Id)
.HasColumnName(#"id")
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) // this is redundant when you want to configure a One-to-Zero-or-One relationship
.HasColumnType("int");
Remove this line
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
Example http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx
This is enough to define the relationship
// Configure Student & StudentAddress entity
modelBuilder.Entity<Student>()
.HasOptional(s => s.Address) // Mark Address property optional in Student entity
.WithRequired(ad => ad.Student); // mark Student property as required in StudentAddress entity. Cannot save StudentAddress without Student
Re-check the relationship between Payment and the other tables/entities. Including the ones that shouldn't contain PaymentId because that's where the problem is most likely hiding.
When creating foreign keys in SQL Server Management Studio, the primary key is defaulted, and this default is reverted when the parent table is changed, so be careful to change values in the correct order in the "Tables and Columns" window.
Also, after you've fixed the problematic relationship, there's a good chance that a simple "Refresh" on the model won't correctly remove the erronous relationship from the model and you'll get the same error even after the "fix", so do this yourself in the model before performing a refresh. (I found this out the hard way.)
If you have checked your relationships and are good there.
Delete the table in the edmx and then update from database. This will save you doing the update manually.
For me it was a wrongly placed foreign key in the table but even after altering the table to fix it, it was still not working. You need to update the EDMX files (and not enough to "refresh" the table from the model, you need to remove and add the table again in the model).
In addition to the accepted answer, if you are using EF Reverse POCO generator or some other tool that generates your POCO's, make sure you regenerate them!
In my case Id field wich FK just in Entity Framework the propierty "StoreGeneratedPattern" was set "Itentity" instead of "None"
In my case the problem was caused by having a two-way 1-1 relationship:
class Foo{
[Key]
Id
[ForeignKey]
BarId
...
}
class Bar{
[Key]
Id
[ForeignKey]
FooId
...
}
I had to simply remove one of the two foreign keys (not necessary anyway).
In my case it was simply that I did not have permissions set properly on the database. I had read only set and Entity framework was giving me a ReferentialConstraint error which threw me off. Added additional write permissions and all was well.
In my case, I had a Database Generated property, and a ForeignKey navigation property set up to reference a 1 to 1 related table.
This wasn't something I could remove, I needed to be able to both set the primary key of the entity to be Database Generated AND I needed to be able to reference the 1 to 1 table as a navigation property.
Not sure if this is the same for others, but this problem was only showing up when creating a new entity, reading or editing existing entities did not exhibit the issue, so I got around the issue by creating an inherited version of my Context and using the Fluent method to switch off the navigation property when creating.
So, my original entity looked like this:
public partial class MyEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid id{ get; set; }
// Navigation
[ForeignKey("id")]
public PathEntity Path { get; set; }
}
So I made a special inherited context that looked like this:
private class _navPropInhibitingContext : EF.ApplicationDBContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>()
.Ignore(e => e.Path);
}
}
and then changed the code that created the new entity to make user of the new context type
using (var specialContext = new _navPropInhibitingContext())
{
var dbModel = new MyEntity()
{
...
};
specialContext.MyEntity.Add(dbModel);
await specialContext.SaveChangesAsync();
}
Hope this helps somebody
I have the same issue.
Here is my case, if you are adding a new record and has a primary key but is not auto-incremented, this will trigger an error.
I thought first that it will automatically generate the key for me so I leave the Id as blank.
Example:
Customer cust = new Customer();
//cust.Id - I left it blank
db.Customer.Add(cust);
db.SaveChanges();
But upon quick investigation, I forgot to set it's Identity to true and that would trigger an error once you do SaveChanges on your DbContext.
So make sure if your Identity is true or not.
In my case I was passing auto generated primary key of the same table in foreign key column so entity frame work is throwing an error that it can not set a value of that column which is not generated yet as we can only get autogenerated value after save change
Here BonusRequestId is my primary key which I was doing a mistake

Categories

Resources