I have a table which I need to delete from, but it has many foreign keys referencing it. I've never before used ON DELETE CASCADE - we have a kind of rule to soft delete records, or if a hard delete is absolutely required, to manually clean up any linked tables before/after deletion.
Is there a way, or a helper function somewhere, that will take a particular table (and record(s) in that table), iterate through every table/record in the database that is linked to it, and perform deletes. I want to believe for now that this isn't TOO bad a practice.
So, e.g. schema:
Organisations (OrganisationID, Name)
Users (UserID, FirstName, LastName, OrganisationID)
Projects (ProjectID, Name, OrganisationID)
Organisations_Markets (OrganisationID, MarketID)
Tasks (TaskID, Description, UserID)
If I wanted to delete where OrganisationID=3...
I know there's a way to list all the FKs in SQL - EXEC sp_fkeys 'Organisations'. How could I do this in EF, and do it for any nested FKs. So in the example above, the first deletes will be on the Tasks table (any records that are linked to Users where OrganisationID=3), and so on and so on.
edit: performance is not an issue - there'll never be more than 100 rows in total - so I'm happy to use EF for delete
In your model, it seems there are standard one-to-many relationships between Organization and Users and between Organization and Projects.
So every Organization has zero or more Users; every User belongs to exactly one Organization
Similarly every Organization has zero or more 'Projects, everyProjectbelongs to exactly oneOrganization`.
You also mention Organisation_Markets. You don't specify this, but it looks like a junction table for a many-to-many relation between Organization and Market: Every Organization refers to zero or more Markets; every Market refers to zero or more Organizations.
If you'd set-up the one-to-many and many-to-many relationships following the entity framework coding conventions you could safely remove an Organization.
Entity framework will automatically remove all Users and Projects from this Organization. All Markets that refer to this Organization won't refer to the deleted Organization anymore. But as it is allowed for a Market to refer to no Organization at all, Markets without Organizations won't be removed.
class Organization
{
public int Id {get; set;} // Primary key
// an Organization has zero or more Users (one-to-many):
public virtual ICollection<User> Users {get; set;}
// an Organization has zero or more Projects (one-to-many):
public virtual ICollection<Project> Projects {get; set;
// an Organization refers to zero or more Markets (many-to-many)
public virtual ICollection<Market> Markets {get; set;}
}
class User
{
public int Id {get; set;} // Primary key
// every User belongs to one Organization using foreign key
public int OrganizationId {get; set;}
public Organization Organization {get; set;}
}
class Project
{
public int Id {get; set;} // Primary key
// every Project belongs to one Organization using foreign key
public int OrganizationId {get; set;}
public Organization Organization {get; set;}
}
class Market
{
public int Id {get; set;} // Primary key
// every Market refers to zero or more Organizations (many-to-many)
public virtual ICollection<Organization> Organizations {get; set;}
}
class MyDbContext : DbContext
{
public DbSet<Organization> Organizations {get; set;}
public DbSet<User> Users{get; set;}
public DbSet<Project> Projects{get; set;}
public DbSet<Market> Markets{get; set;}
}
That's all. Because I followed the entity framework coding conventions, entity framework will recognize the one-to-many and many-to-many relationships and create the proper foreign keys. It will even create the junction table needed for the many-to-many relation, even though I didn't mention it.
By the way, I don't need the junction table. If I want all Markets belonging to an Organization I use property Organization.Markets. Entity framework will know that a join with the junction table is needed and will create the proper SQL code for it.
If you don't want the defaults, for instance different table names, consider using fluent-API or data annotations for this.
Now to remove an Organization do something like this:
using (var dbContext = new MyDbContext())
{
Organization organizationToRemove = dbContext.Organizations
.Where(organization => ...)
... etc.
dbContext.Organizations.Remove(organizationToRemove);
dbContext.SaveChanges();
}
Entity framework will automatically update other tables
All Users of the Organization are removed
All Projects of the Organization are removed
All references to the Organization in all Markets are removed.
So you don't need a special function to neatly clean up all references to the Organization you removed. Entity Framework will do this correctly for you.
Related
Given the following JPA model:
#Entity
public class Customer {
#Id
private long id;
#OneToOne(mappedBy="customer")
private ShippingAddress shippingAddress;
//...
}
#Entity
public class ShippingAddress {
#Id
private long id;
#OneToOne
private Customer customer;
//...
}
This will create a foreign key to the customer table in the shipping_address table i.e. shipping_address.customer_id.
From a database point-of-view, one would say that customer is the parent table and shipping_address is the child table, because
An address cannot exist without a customer
If a customer is deleted, their address must also be deleted
However, according to JPA, the owner of this relationship is ShippingAddress because the JavaDoc for mappedBy states
This element is only specified on the inverse (non-owning) side of the association.
I find it extremely confusing that the owner of the relationship according to JPA is what (from the database point-of-view) would be considered the child table. Is there a way to reconcile this apparent contradiction?
In short: as posted before here and here, the owning side of the relation is the one that holds the FK in the DB table.
You can read this excellent answer for a full explanation why.
I have 2 tables in a plain old 1-n relationship: Invoice and WorkOrder. An Invoice can have many WorkOrders and a WorkOrder can have a single Invoice. Earth-shaking, I know.
Here's my Invoice:
public class Invoice
{
public Guid Id { get; set; }
// some fields....
public virtual IList<WorkOrder> WorkOrders { get; set; }
}
Here's my WorkOrder:
public class WorkOrder
{
public Guid Id { get; set; }
public Guid InvoiceId { get; set; }
public virtual Invoice Invoice { get; set; }
}
When I go to query for Invoices, I get this error:
Invalid column name 'Invoice_Id2'.
When I look at the WorkOrder table in SSMS I find not one, but 3 columns referencing Invoice: InvoiceId, Invoice_Id, and Invoice_Id1.
Obviously something has gone very wrong in EF figuring out what I want it to do.
I did manage to find an FK for Invoice_Id2, which I deleted, but I'm still having the problem.
In my experience this type of error happens when EF is trying to match a relationship based on faulty navigational properties. For instance if you had:
public int InvoiceId { get; set; }
public virtual Invoice Invice { get; set; }
Notice the misspelling above. EF will expect there to be a column in the database called Invice_Id. I'd check your related entities for misnamed navigational properties.
So it turned out that I had made some mistakes not elaborated above (bc I didn't think they were relevant, natch). Namely I had a bunch of getter methods in my Invoice entity that returned IList<WorkOrder> and EF was mistakenly picking up on those as navigation properties.
As far as I can tell, once you've created a table that's been jacked up that way, you're better off dropping the offending tables, adding [NotMapped] attributes to the troublesome properties, and recreating the table. Hopefully you don't have important production data in those tables already. I lucked out there.
I was really surprised those getters would be picked up by the ORM as Navigation properties, so beware that in the future. Perhaps being less clever and doing straight methods in the future would be smarter.
I am new to Visual Studio so to start learning it I first of all downloaded a sample available at https://code.msdn.microsoft.com/ADPNET-Entity-Framework-2d1160cb and started working around it. Since I have fairly good knowledge of VB6 and SQL it did not take much time for me to understand the whole pattern the sample is based on. Had Microsoft given a detail explanation or a walk through of the sample it would have been much easier to understand the basics. However, I somehow managed to work around it and have build a small desktop application in wpf using Entity Framework and MVVM. But a point has come where I have got completely stuck up finding no way out. The problem is as under:
I have two tables. 1 Advocate and 2 Party. Table Advocate would contain names of advocates and would have a primary key. Similarly Party would have names and their respective primary keys.
Then I have another two tables 1. Case and 2 CaseDetail. Table Case would simply hold three columns: 1. CaseId 2. CaseNo and 3. Year. Table CaseDetail would have CaseDetailId as a primary key the CaseId as a Foreign Key. Now what I need is that a particular case could have multiple advocates and multiple petitioners. So the table CaseDetail would have two columns to hold advocateId and PartyId as a Foreign Keys.
If you look at the sample referred above you would not find how to deal with such a case. When I follow the pattern of the sample I get host of design time and runtime errors.
Anyways, after number of trials I have somehow manged to set the EF right but I doubt if it would serve any purpose since I need to have multiple instances of Petitioners and Advocates.
Here is the link to my edmx:
https://www.dropbox.com/s/rkarzod1lezdnqs/EDMX.png?dl=0
From the image it can be seen that I have four different foreign keys fldPetitioner, fldRespondent, fldAdvocate and fldSrAdvocate for which I too have navigation property to back track them which have multiplicity 0 or 1. Therefore, in such a scenario would I be able to have multiple instances on these columns?
Therefore, please suggest what strategy should be adopted in a scenario described above while developing WPF application using Entity Framework and MVVM.
I think it sounds like you're trying to use one-to-one relationships where there should be one-to-manys. Take a closer look at the relationship between Department and Employee from your linked MS sample. It results in '1 Department to many Employees'. This puts the DepartmentId against an entry in the Employee table, not the other way round, which is what I think you have at the moment. The analogous element from your question would be '1 CaseDetail to many Advocates'.
public class CaseDetail
{
//CaseDetail ID number
public int CaseDetailId { get; set; }
//...
//Any other properties go here
//...
//Navigation properties
public ICollection<Advocate> Advocates { get; set; }
/* Other collections would be executed similarly:
*
* public ICollection<Party> Petitioners { get; set; }
* public ICollection<Party> Respondents { get; set; }
*/
}
public class Advocate
{
//Advocate ID number
public int AdvocateId { get; set; }
//...
//Any other properties go here
//...
//Navigation properties
public int CaseDetailId { get; set; }
public CaseDetail CaseDetail { get; set; }
}
public class AdvocateConfiguration : EntityTypeConfiguration<Advocate>
{
public AdvocateConfiguration()
{
HasRequired(a => a.CaseDetail)
.WithMany(cd => cd.Advocates);
}
}
In the MS sample, you must have noticed that while entering data we assign Department to an employee although department to employee relationship is 1 to many. In my case the issue is that I want assign advocates to casedetail although the relationship casedetail to advocate is 1 to many. Here simply imagine a simple billing application where table invoice can be a master table having details in table invoicedetails where we can have multiple products as a foreign key. Considering the scenario please tell me whether or not there can be a derived entity with a navigation property to back track the products?
In my db model I got a table of Jobs and a table of JobResults.
The model definitions look the following:
public class Job
{
public int Id { get; set; }
public virtual JobResult Result { get; set; }
}
public class JobResult
{
public int JobId { get; set; }
public virtual Job Job { get; set; }
}
And the fluent API configuring the relationships is the following:
modelBuilder.Entity<Job>()
.HasRequired(x => x.Result)
.WithRequiredPrincipal(x => x.Job)
.WillCascadeOnDelete(true);
modelBuilder.Entity<JobResult>()
.HasKey(x => x.JobId);
As you see, it's a required-to-required relationship where they both share the Id of Job as primary key.
When a Job is deleted I obviously want the JobResult to be deleted as well (which is why I added the WillCascadeOnDelete()).
However when I update my database with the definitions above I get the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.JobResults_dbo.Jobs_JobId'
on table 'JobResults' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints. Could not create constraint. See previous
errors.
This exclusively happens when I include the WillCascadeOnDelete().
Why is this happening?
This problem is caused by a possible cyclic cascading delete. This can happen in many forms, but it comes down to a record being deleted by two or more cascading delete rules in one time, so I ussume that you have another relationship where the Job entity is involved, and when you delete a record from the Job table, it is possible this delete will end trying to delete for both side the same record in another Table.
I suggest you take a look to this post and check if you don't have a situation like the example that is showed in the #KristofClaes' answer.
You can avoid such ambiguous delete paths by either disabling cascading delete using Fluent API or by defining some of the relationships as optional (with a nullable foreign key).
I have been using EF5 via Code First successfully so far to build out my database from my models. However, I recently ran into a (fairly) common issue of cycles/multiple cascade paths. I understand what the problem is and normally, I fix it by writing rules against my entities to disable CascadeOnDelete down one side of the branch. The difference with this scenario and my current one, is that I typically am creating the middle "join" table in a many-to-many relationship.
So, for example, I may have: Users => UserLeagues <= Leagues
And then I do this:
modelBuilder.Entity<UserLeagues>()
.HasRequired(u => u.League)
.WithMany()
.HasForeignKey(l => l.LeagueId)
.WillCascadeOnDelete(false);
Where I have created the UserLeague table (it requires some additional information so this makes sense). In my most recent case, I just needed to create a many-to-many relationship. So, I didn't bother to create this middle table. Instead, I let EF auto-generate it.
As a result, I am unsure of how to stop the cascade delete down the one side because I don't have access to the UserLeagues table directly like I do if I manually created that many-to-many table. Any advice? Here are my models...
public User {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<League> Leagues { get; set; }
}
public League {
public int Id { get; set; }
public int Score { get; set; }
public virtual ICollection<User> Users { get; set; }
}
When you let EF auto-generate (many-to-many relationship and the support table) - you have no way of manually deleting the actual records in the join table, once the relationship is removed (since you don't have that table mapped to an entity).
Hence the cascade deletes need to be 'on' by default. That's 'by convention'.
You could remove that convention all together (for all many to many - and their fk-s involved)...
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
Another way to do that on a case by case basis - would be to change the migration scripts
(providing you're using migrations).
When migrations generate the pseudo code - it has something like
.ForeignKey("dbo.Leagues", t => t.League_Id, cascadeDelete: true)
Just remove the , cascadeDelete: true parameter.
But then you'll end up with phantom records (i.e. you'll need to resort to manual SQL or occasional cleanup to remove the junk records).