I have a 1 to many relationship between LabelLineItem and DespatchPart.
I can't understand why cascade delete is off for this relationship.
There is no relationship defined in the context using the fluent API.
There is no LabelLineItems navigation collection in DespatchPart, so there is no reference back to LabelLineItem.
public class LabelLineItem
{
public int Id { get; set; }
public int DespatchPartId { get; set; }
public int LabelConfigId { get; set; }
public string Content { get; set; }
// Navigation
public virtual LabelConfig LabelConfig { get; set; }
public virtual DespatchPart DespatchPart { get; set; }
}
public class DespatchPart
{
public int Id { get; set; }
public int DespatchId { get; set; }
// Navigation
public virtual Despatch Despatch { get; set; }
//...
}
It's my understanding that one-to-many relationships default to cascade delete on. As demonstrated in the code sample above.
Whereas zero-or-one-to-many relationships default to cascade delete off as would be the case if either:
- DespatchPartId was declared as int?,
- The fluent API declared the relationship as optional i.e. DespatchPart.HasMany(p => p.LabelLineItems).WithOptional(i => i.DespatchPart).
But neither of these are the case which is why I'm confused.
FYI -
I'm certain the cascade is off, because when I tested the cascade delete by removing a despatch part record (in SQLManagementStudio), I received an attempted FK violation in the LableLineItem table as I tried to remove a referenced DespatchPart record. This wouldn't have occurred if it the delete had cascaded to the LabelLineItem table.
Related
I am getting error while trying to run my MVC application
Introducing FOREIGN KEY constraint 'FK_dbo.Passages_dbo.Localizations_ToID' on table 'Passages' 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 or index. See previous errors'
I`ve seen many posts but I couldn't get what should I do now.
There are my models:
public class Passage
{
[Key]
public int ID { get; set; }
public int FromID { get; set; }
[ForeignKey("FromID")]
public Localization FromLocalizaton { get; set; }
public int ToID { get; set; }
[ForeignKey("ToID")]
public Localization ToLocalization { get; set; }
public DateTime DepartureTime { get; set; }
public DateTime ArrivalTime { get; set; }
public DateTime? AdditionalTime { get; set; }
public bool Weekend { get; set; }
public int Seats { get; set; }
}
public class Localization
{
[Key]
public int ID { get; set; }
public string Province { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string StreetAdres { get; set; }
}
Passage has two foreign key refers to Lozalization with one to one relationship
The issue came from this:
Passage has two foreign key refers to Lozalization with one to one relationship
Because by default those two relationships are required in Passage (look at foreign key FromID and ToID there are not Nullable<int> or int?) hence Code First create cascade delete action on those relations. However two cascade deletions will be applied on the same table which is not allowed.
To correct this issue, you have two solutions:
Make one of the foreign key property Nullable<int> which by default not create a cascade delete action on that relationship.
Or you can disable cascade delete action by using Fluent API like this :
// Assuming that you want to disable cascade deletion with ToLocalization
modelBuilder.Entity<Passage>()
.HasRequired(p => p.ToLocalization)
.WithMany()
.WillCascadeOnDelete(false);
I have 2 models:
public class TransactionHistory : IDbEntity {
[Key]
public int ID { get; set; }
public ItemHistory ItemHistory { get; set; }
}
public class ItemHistory : IDbEntity {
[Key]
public int ID { get; set; }
public int TransactionHistoryID { get; set; }
public TransactionHistory TransactionHistory { get; set; }
}
There's a one to one relationship between TransactionHistory and ItemHistory, ItemHistory MUST have a TransactionHistory but TransactionHistory may or may not have an ItemHistory.
I want to be able to do this in code:
var test = db.ItemHistory.Include(x => x.TransactionHistory).ToList();
As well as:
var test2 = db.TransactionHistory.Include(x => x.ItemHistory).ToList();
But I only want a single FK on the ItemHistory table.
With the code I've listed I get this error:
Unable to determine the principal end of an association between the types 'InventoryLibrary.DomainModels.TransactionHistory' and 'InventoryLibrary.DomainModels.ItemHistory'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
How is this achieved in Entity Framework code first data annotations?
Firstly, you have to mark foreign keys by virtual keyword to enable overrides.
public class TransactionHistory : IDbEntity
{
[Key]
public int ID { get; set; }
public virtual ItemHistory ItemHistory { get; set; }
}
public class ItemHistory : IDbEntity
{
[Key]
public int ID { get; set; }
public int TransactionHistoryID { get; set; }
[Required]
public virtual TransactionHistory TransactionHistory { get; set; }
}
If HistoryItem must have Transaction History, add DataAnnotation [Required], which makes it non-nullable.
Finally, wonder, if you want to have one-to-one relationship. I imagine you'd like to have many transaction history entries. Am I right? If not - let me know.
To create one-to-many relationship, use IEnumerable<> type.
I have this class structure:
public class Activity
{
[Key]
public long ActivityId { get; set; }
public string ActivityName { get; set; }
public virtual HashSet<ActivityLogMessage> ActivityLogMessages { get; set; }
public virtual HashSet<FileImportLogMessage> FileImportLogMessages { get; set; }
public virtual HashSet<RowImportLogMessage> RowImportLogMessages { get; set; }
}
public abstract class LogMessage
{
[Required]
public string Message { get; set; }
public DateTimeOffset CreateDate { get; set; }
[Required]
public long ActivityId { get; set; }
public virtual Activity Activity { get; set; }
}
public class ActivityLogMessage : LogMessage
{
public long ActivityLogMessageId { get; set; }
}
public class FileImportLogMessage : ActivityLogMessage
{
public long? StageFileId { get; set; }
}
public class RowImportLogMessage : FileImportLogMessage
{
public long? StageFileRowId { get; set; }
}
Which gives me this, model
Each Message (Activity, File or Row) must have be associated with an Activity. Why does the 2nd and 3rd level not have the same cardinality as ActivityLogMessage ? My attempts at describing the foreign key relationship (fluent via modelbuilder) have also failed.
This is really an academic exercise for me to really understand how EF is mapping to relational, and this confuses me.
Regards,
Richard
EF infers a pair of navigation properties Activity.ActivityLogMessages and ActivityLogMessage.Activity with a foreign key property ActivityLogMessage.ActivityId which is not nullable, hence the relationships is defined as required.
The other two relationships are infered from the collections Activity.FileImportLogMessages and Activity.RowImportLogMessages. They neither have an inverse navigation property on the other side nor a foreign key property which will - by default - lead to optional relationships.
You possibly expect that LogMessage.Activity and LogMessage.ActivityId is used as inverse property for all three collections. But it does not work this way. EF cannot use the same navigation property in multiple relationships. Also your current model means that RowImportLogMessage for example has three relationships to Activity, not only one.
I believe you would be closer to what you want if you remove the collections:
public virtual HashSet<FileImportLogMessage> FileImportLogMessages { get; set; }
public virtual HashSet<RowImportLogMessage> RowImportLogMessages { get; set; }
You can still filter the remaining ActivityLogMessages by the derived types (for example in not mapped properties that have only a getter):
var fileImportLogMessages = ActivityLogMessages.OfType<FileImportLogMessage>();
// fileImportLogMessages will also contain entities of type RowImportLogMessage
var rowImportLogMessage = ActivityLogMessages.OfType<RowImportLogMessage>();
I have one to one relationship with foreign keys but the Cascade Delete is not enabled for some reason. The sample code is below.
public class AppRegistration
{
public int AppRegistrationId { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Username")]
public string UserName { get; set; }
[Required]
[StringLength(100)]
public string Password { get; set; }
[StringLength(20)]
public string StudentOrAgent { get; set; }
// navigation properties
public virtual AppStatus AppStatus { get; set; }
public virtual Agreement Agreement { get; set; }
public virtual AnotherTable AnotherTable { get; set; }
}
The dependent table with a foreign key is below.
public class Agreement
{
[Key]
[ForeignKey("AppRegistration")]
public int AppRegistrationId { get; set; }
public DateTime DateAgreed { get; set; }
public virtual AppRegistration AppRegistration { get; set; }
}
When I try to delete an entry from the generated AppRegistrations table I get a Reference constraint conflict.
I tried putting [Required] on the navigation property in the dependent table but it doesn't do anything - the Update-Database command shows the No pending code-based migrations. message. Any ideas? Thanks.
Update:
I'm getting the following error message:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.AppStatus_dbo.AppRegistrations_AppRegistrationId". The conflict occurred in database "MVCapp", table "dbo.AppStatus", column 'AppRegistrationId'.
I decided to work out the cascade delete problem in a separate sample project. I found the following blog & MSDN pages very useful.
http://blog.bennymichielsen.be/2011/06/02/entity-framework-4-1-one-to-one-mapping/
http://msdn.microsoft.com/en-us/library/gg671256%28v=VS.103%29.aspx
http://msdn.microsoft.com/en-us/library/gg671273%28v=VS.103%29.aspx
Using the Code First approach create the following Model.
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual Book Book { get; set; }
}
public class Book
{
public int CategoryId { get; set; }
public string BookTitle { get; set; }
public string BookAuthor { get; set; }
public string BookISBN { get; set; }
public virtual Category Category { get; set; }
}
(I realize the entity names suggest one-to-many relationship, but I am trying to model 1-to-1 relationship, as in my original question at the top.)
So, in the above model each Category can only have one Book.
In your DbContext-derived class add the following.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Book>()
.HasKey(t => t.CategoryId);
modelBuilder.Entity<Category>()
.HasRequired(t => t.Book)
.WithRequiredPrincipal(t => t.Category)
.WillCascadeOnDelete(true);
}
(The following namespaces are required for the above code: System.Data.Entity, System.Data.Entity.ModelConfiguration.Conventions.)
This properly creates the 1-to-1 relationship. You'll have a primary key in each table and also a foreign key in Book table with ON DELETE CASCADE enabled.
In the above code, on the Category entity I used WithRequiredPrincipal() with t => t.Category argument, where the argument is the foreign key column in the dependent table.
If you use WithRequiredPrincipal() without an argument you'll get an extra column in the Book table and you'll have two foreign keys in the Book table pointing to CategoryId in Category table.
I hope this info helps.
UPDATE
Later on I found answer directly here:
http://msdn.microsoft.com/en-us/data/jj591620#RequiredToRequired
A reason why you're not getting cascading delete is because your relationship is optional.
If you want the relationship required i.e. an AppRegistration has to have one Agreement you can use (cascading delete configured automatically):
public class Agreement
{
...
[Required]
public AppRegistration AppRegistration{ get; set; }
}
If you want the relationship to be optional with cascading delete you can configure this using Fluent API:
modelBuilder.Entity<AppRegistration>()
.HasOptional(a => a.Agreement)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
Using EF Code First i have the following, for example:
public class Blog
{
public int BlogID { get; set; }
public string Content { get; set; }
public virtual User User { get; set; }
public virtual ICollection<BlogMeta> BlogMeta { get; set; }
}
public class BlogMeta
{
public int BlogMetaID { get; set; }
public string Content { get; set; }
public virtual User User { get; set; }
public virtual Blog Blog { get; set; }
}
This successfully generates the tables Blog and BlogMeta and creates the foreign key relationship with the User table. After reading this i changed this to the following:
public class Blog
{
public int BlogID { get; set; }
public string Content { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public virtual ICollection<BlogMeta> BlogMeta { get; set; }
}
public class BlogMeta
{
public int BlogMetaID { get; set; }
public string Content { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public int BlogID { get; set; }
public virtual Blog Blog { get; set; }
}
and now it doesn't work. It generates the tables and then throws the following error when trying to create the relationships:
Introducing FOREIGN KEY constraint 'BlogMeta_User' on table 'BlogMeta' may cause cycles or multiple cascade paths.
So what is the advantage of introducing the public int UserID and why does it fail when doing so?
EDIT:
Ok, so i've come across this answer which outlines the difference between Independent Associations and Foreign Key Associations... which it turns out is what i was talking about. So this leaves the question, why does it throw the above error when using foreign key associations?
As Ladislav mentioned, you are defining multiple cascade paths for BlogMeta entity. You'd have to disable cascade for one of your relationships.
You can add the following method to your context class, to diable cascade for your User-BlogMeta relationship:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogMeta>().HasRequired(bm => bm.User).WithMany().WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
You can indicate the other end of relationship (WithMany(u => u.BlogMetas)) if you have defined a colletion of BlogMeta in your User class.