Entity Framework 6, cascade delete of mapped join table - entity-framework

I have simple "graph" representation stored in database. There is Node entity which has it's ID, Label and list of adjacent nodes.
public class Node
{
[Key]
public int Id { get; set; }
[StringLength(128)]
public string Label { get; set; }
public virtual ICollection<Node> AdjacentNodes { get; set; }
}
Then in OnModelCreating on database context class, I have:
modelBuilder.Entity<Node>()
.HasMany(n => n.AdjacentNodes)
.WithMany()
.Map(n => n.MapLeftKey("From").MapRightKey("To").ToTable("NodeEdge"));
Now, when I want to delete any node which has already any relation, I get constraint error:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.NodeEdge_dbo.Nodes_From". The conflict occurred in database "master", table "dbo.NodeEdge", column 'From'.
The statement has been terminated.
Join table looks like this:
CREATE TABLE [dbo].[NodeEdge] (
[From] INT NOT NULL,
[To] INT NOT NULL,
CONSTRAINT [PK_dbo.NodeEdge] PRIMARY KEY CLUSTERED ([From] ASC, [To] ASC),
CONSTRAINT [FK_dbo.NodeEdge_dbo.Nodes_From] FOREIGN KEY ([From]) REFERENCES [dbo].[Nodes] ([Id]),
CONSTRAINT [FK_dbo.NodeEdge_dbo.Nodes_To] FOREIGN KEY ([To]) REFERENCES [dbo].[Nodes] ([Id])
);
Is there any way how to add ON DELETE CASCADE on join table constraints - so it will clear up all references when I'm deleting nodes? (... and keeping model simple without having join table entity)

When the many-to-many association is a self reference, EF chooses to create the foreign keys as not cascading. If it would make both keys cascading there would be a circular cascade, which Sql Server doesn't allow. Apparently, EF doesn't want to choose for you which of both keys should be cascading, so it chooses none.
You can however add the ON DELETE CASCADE clause to the FK_dbo.NodeEdge_dbo.Nodes_From foreign key afterwards, or add it in the migration Up method.

Related

EF Core: How to use foreign key in a PostgreSQL array

I have a PostgreSQL database with following tables:
create table tbl1(
id serial primary key,
...
)
create table tbl2(
id serial primary key,
tbl1_ids int[]
)
tbl2.tbl1_ids is a reference to the tbl1.id (I prefer not to declare it as foreign key, but I can do that if it solves my problem).
It works fine, and it's significantly more space efficient than using the traditional N-M relation.
However, I don't know how to declare an EF Core navigation property.
I'd expect something like this to work.
class Tbl2 {
// these two work fine
public int Id { get; set; }
public int[] Tbl1Ids { get; set; }
// I'd like to do this, so I can use it in queries
public Tbl1[] Tbl1s { get; set; }
}
EF apparently only recognizes navigation properties if the foreign key property ends with Id, but it also seems to expect that it has type int, not int[]. I'd expect I'll need to do some Fluent API configuration for this, but I could not figure this out yet.

Entity Framework with MVVM problem. Violation of PRIMARY KEY constraint The duplicate key value is (2, 2)

I've been messing with this for 5 hours and I gave up.
I did basically identical relationship before and worked and now when I'm saving in my MVVM project, I get this error:
SqlException: Violation of PRIMARY KEY constraint 'PK_dbo.LecturerMeeting'. Cannot insert duplicate key in object 'dbo.LecturerMeeting'. The duplicate key value is (2, 2).
And also:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
I have 2 model classes, Meeting and Lecturer. In Lecturer class, I define in constructor:
public Lecturer()
{
Meetings = new Collection<Meeting>();
}
And also
public ICollection<Meeting> Meetings { get; set; }
Same in the Meeting class
public Meeting()
{
Students = new Collection<Student>();
Lecturers= new Collection<Lecturer>();
}
And:
public ICollection<Lecturer> Lecturers { get; set; }
But when I created a many-to-many relationship between meetings and student, EF created a table MeetingStudent.
Now it created LecturerMeeting, don't know if this changes anything
SQL code for LecturerMeeting relationship:
CREATE TABLE [dbo].[LecturerMeeting]
(
[Lecturer_Id] INT NOT NULL,
[Meeting_Id] INT NOT NULL,
CONSTRAINT [PK_dbo.LecturerMeeting] PRIMARY KEY CLUSTERED ([Lecturer_Id] ASC, [Meeting_Id] ASC),
CONSTRAINT [FK_dbo.LecturerMeeting_dbo.Lecturer_Lecturer_Id] FOREIGN KEY ([Lecturer_Id]) REFERENCES [dbo].[Lecturer] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_dbo.LecturerMeeting_dbo.Meeting_Meeting_Id] FOREIGN KEY ([Meeting_Id]) REFERENCES [dbo].[Meeting] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_Lecturer_Id]
ON [dbo].[LecturerMeeting]([Lecturer_Id] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_Meeting_Id]
ON [dbo].[LecturerMeeting]([Meeting_Id] ASC);
Please help.
I managed to find answer. Finally.
I didnt incluced another property when i get my Meeting from context.
I had
public async override Task<Meeting> GetByIdAsync(int id)
{
return await Context.Meetings.Include(m => m.Students).SingleAsync(m => m.Id == id);
Insted of:
public async override Task<Meeting> GetByIdAsync(int id)
{
return await Context.Meetings.Include(m => m.Students).Include(m => m.Lecturers)
.SingleAsync(m => m.Id == id);
}
Too stupid to find that faster.

Does Entity Framework support when deleted a record and set the foreign key to null?

I have a model like this:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Account Parent { get; set; }
}
and I add the following configuration:
this.HasOptional(item => item.Parent)
.WithMany()
.HasForeignKey(item => item.ParentId)
.WillCascadeOnDelete(true);
and then I got following error message:
Assembly Initialization method
UnitTest.Biz.Accounting.TestInitializer.Init threw exception.
System.Data.SqlClient.SqlException:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint
'FK_dbo.acct_Account_dbo.acct_Account_ParentId' on table
'acct_Account' 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.
I had read the documents of the EF, but I don't understand where is wrong in my code...
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
http://msdn.microsoft.com/en-us/data/jj591620#CascadeDelete
Self Referencing Table
SQL server doesn't allow self referencing table to have cascading delete.
As you can see, SQL Server noticed that your cascade operation is
cyclic, and it does not allow this type of cascading. This is true not
only for a self-referencing table, but also for any chain of
relationships between tables in which the cascading operations have a
potential to be cyclical.
Source
That's why in EF you can't set cascading delete on self referencing entity.
.WillCascadeOnDelete(true); // Not allowed.
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship
is about the default convention of EF which you can override by fluent api. If you have following code,
public int? ParentId { get; set; } // <--- nullable
public Account Parent { get; set; }
EF will mark the cascading delete as false by default, even if you don't configure it with WillCascadeOnDelete(false), which actually means that the foreign key cascading delete in the database will be set as ON DELETE NO ACTION.
And currently EF doesn't support ON DELETE SET NULL unless you have custom query to drop and re-add the constraint, check this post.
and when the principal is deleted the foreign key will be set to null.
This has been explained in this post which means only if the children are loaded into the context.

EF 5 DbContext: Delete the undeletable

I'm using EF5 with DbContext and Database-First approach in a WPF application and ran into some logical problems during the deletion of entities and the usage of the required data annotation attribute in the following scenario:
The are two tables referencing each other with foreign keys without cascading delete:
|----A----| |----B----|
|ID int |<-| |ID int |
|---------| |-|A_ID int |
Thus, "A" can't be deleted if some "B" references "A".
The EF 5 model includes the associations and - as no cascading is set - OnDelete is set to "None" to both Ends of the associations. I've decorated both the "A_ID" field and the Navigation property "B.A" with a [Required] attribute - and here my trouble starts when I delete an "A" entity with "B" entities refrenencing this "A" entity:
MyContext.Set<A>().Remove(MyA);
MyContext.SaveChanges();
SaveChanges sets all navigation propoerties of "B" to the removed "A" entity to null. This invalidates "B" as the navigation propoerties have the Required-Attribute throwing an exception that "A" can not be deleted because "B" is invalid - which is somehow a strange reason.
However, after removing the Required-Attribute on the Navigation properties and leaving the Required-Attribute on the B.A_ID property the correct error is thrown.
Finally, after the database exception I end up with an object graph in which all naviagation properties of "A" are set to "null".
I think this is the intended behaviour of the EF but this leads to two problems:
Firstly, the delete Operation is not valid. I haven't found anything about a "Delete"-Validation. Data annotaions consider only property changes.
Secondly, how to recover removed entities after an exception as all navigation properties are set to "null". The EF 5 associations are not as precise as the SQL Server ones which allows "None", "Set to NULL" or "Cascade" on associations. In the case of "None" SQL Server throws an exceptions leaving all entities untouched.
If anybody ran into the same problems of a "CanDelete"-Validation and entity recovery after an database exception and has a solution or can point me to other related threads, please tell me.
Yours,
Marcus
Example Classes:
// Entity to delete
public partial class A {
public A() {
this.Bs = new HashSet<B>();
}
public int ID { get; set; }
public Nullable<int> C_ID { get; set; }
public string Name { get; set; }
public virtual C C { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
// Child entities of A with foreign key constraint
public partial class B {
public int ID { get; set; }
public int A_ID { get; set; }
public string Name { get; set; }
public virtual A A { get; set; }
}
// Example class of an additional entity referencing A
public partial class C {
public C() {
this.As = new HashSet<A>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<A> As { get; set; }
}
Database Schema:
CREATE DATABASE [EFABC]
GO
USE [EFABC]
GO
CREATE TABLE [dbo].[tA](
[ID] [int] IDENTITY(1,1) NOT NULL,
[C_ID] [int] NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_tA] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[tB](
[ID] [int] IDENTITY(1,1) NOT NULL,
[A_ID] [int] NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_tB] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[tC](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_tC] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[tA] WITH CHECK ADD CONSTRAINT [FK_tA_tC] FOREIGN KEY([C_ID])
REFERENCES [dbo].[tC] ([ID])
GO
ALTER TABLE [dbo].[tA] CHECK CONSTRAINT [FK_tA_tC]
GO
ALTER TABLE [dbo].[tB] WITH CHECK ADD CONSTRAINT [FK_tB_tA] FOREIGN KEY([A_ID])
REFERENCES [dbo].[tA] ([ID])
GO
ALTER TABLE [dbo].[tB] CHECK CONSTRAINT [FK_tB_tA]
GO
Simply delete all the B entities before you delete your A entity
[Update]
Since it is database first, the fact that your B Class contains
public int A_ID { get; set; }
makes me think that the relationship is not quite as you describe.
Try looking at in a database diagram.
As this behaviour is intended by the EF I used a workaround for WPF applications using DbContext. The problem occurs because I use a DbContext instance during the lifetime of a window. Thus, the object graph in the context has to be consistent until the window is closed. In the case of a failed delete operation whether because of foreign key constraints or because of a database connection failure the object graph is changed during the remove method.
As a workaround I have implemented the following methods:
Check foreign key constraints before the removal of entities.
As only the loaded object graph is affected only loaded entities have to be checked. This is not completely satisfying as the business logic has to implement this check independently from the EF which already knows all constraints.
Delete Entities in a separate DbContext
This way the original context isn’t changed if the deletion failed. This works fine for situations in which a window is equivalent to an deleted entity. In this case the window is closed if the delete operation succeded and held open with its original context if the delete operation fails. However, the situation in which the deletion of sub-entities fails isn’t covered with this method.
Finally, you could refresh the whole context after the failure of a delete operation but in this case you’re losing all changes in entities set before.

Entity Framework 5 recursive relations

CREATE TABLE ConfigurationItem
(
OID BIGINT NOT NULL
,ParentItemOID BIGINT
);
ALTER TABLE ConfigurationItem ADD CONSTRAINT PK_CONFIGURATIONITEM PRIMARY KEY (OID);
ALTER TABLE ConfigurationItem ADD CONSTRAINT FK_CONFIGURATIONITEM_PARENTITEMOID FOREIGN KEY (ParentItemOID ) REFERENCES CONFIGURATIONITEM(OID);
Every time fetch data ConfigurationItem I would like to get
parent ConfigurationItem
and List of child ConfigurationItems
and no recursion.
This was the entity created
[Table("ConfigurationItem", Schema = "dbo")]
public partial class ConfigurationItem : TaggableItem
{
public Int64 OID { get; set; }
public Int64? ParentItemOID { get; set; }
[ForeignKey("ParentItemOID")]
public ConfigurationItem Parent;
[InverseProperty("ParentItemOID")]
//Not a virtual because it is need to be marshalled via WCF
public List<ConfigurationItem> Children { get; set; }
}
I can't make this to work.
Example following errors happen:
InnerException: System.Data.SqlClient.SqlException
HResult=-2146232060
Message=Invalid column name 'ConfigurationItem_OID'.
Source=.Net SqlClient Data Provider
ErrorCode=-2146232060
Class=16
LineNumber=32
Number=207
Procedure=""
Server=localhost
State=1
What would be the correct way to make this work in Entity framework?
I think your original exception stems from the default naming convention used by EF. Either rename the OID property in the ConfigurationItem class to Configuration_OID or use the Column annotation on the OID property to indicate that you want to override the default naming convention for that column, e.g. [Column("OID")].
I have a schema with a self-referential table like this and I haven't found it necessary to use the InverseProperty annotation, so you may be able to just get rid of it.