In previous versions of EF I can use following code to implement an identifying relationship:
public class Child
{
[Key, Column(Order = 1)]
public virtual int Id { get; set; }
[Key, Column(Order = 2)]
public virtual int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
It's needed to easily remove a child from collection like this:
var parent = _context.Parents.First();
var child = parent.Children.First();
parent.Children.Remove(child);
_context.SaveChanges();
This approach is described in http://www.kianryan.co.uk/2013/03/orphaned-child/ (the method #2).
But in EF7 this code throws exception when migration is creating:
An exception was thrown while executing a resolve operation. See the
InnerException for details. ---> Entity type 'Child' has composite
primary key defined with data annotations. To set composite primary
key, use fluent API.
I also tried to use FluentAPI as described in How to define nested Identifying Relationships Entity Framework code first in following code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
modelBuilder.Entity<Child>()
.HasKey(c => new {c.Id, c.ParentId});
base.OnModelCreating(modelBuilder);
}
This approach allow generate a migration successfully, but when I tried to remove a child from Children collection I got following exception:
System.InvalidOperationException: The association between entity types
'Parent' and 'Child' has been severed but the foreign key for this
relationship cannot be set to null. If the dependent entity should be
deleted, then setup the relationship to use cascade deletes.
But I wouldn't like to use cascade deletes, I would like to use identifying relationship!
Please, help me understand what I do incorrect. Thank you!
Use cascade on delete instead as this is what it's used for:
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
.WillCascadeOnDelete(true);
https://msdn.microsoft.com/en-gb/data/jj591620.aspx
Just in case someone sees this error, let me tell you how I resolved mine:
When you do an update, on EF you need to first query the database and get the data model, then map the Domain layer model with your changes onto it (basically copying fields onto the data), and finally call the DBContext update method, then save changes.
My problem was that my model (not the data model, the domain model) also had the sub objects on it.
So here's the data layer model (for example):
public class Parent
{
public int ChildId {get; set; }
[ForeignKey("ChildId")]
public virtual Child Child { get; set; }
}
And here's how the domain layer model should be:
public class Parent
{
public int ChildId { get; set; }
//public Child Child { get; set; } // this caused the error, keep reading if you want to know more.
}
When I was seeing the error, I had been using Autofac's runtime mapper to map the domain layer model's properties onto the data layer model. However, the child in the domain layer model was null, so it would nullify the data layer, causing the error:
"The association between entity types 'Parent' and 'Child' has been severed but the foreign key for this relationship cannot be set to null. If the dependent entity should be deleted, then setup the relationship to use cascade deletes."
By the way, in the db context class, I have the following relationship defined:
modelBuilder.Entity<Parent>()
.HasOne(a => a.Child)
.WithMany()
.HasForeignKey(p => p.ChildId)
.OnDelete(DeleteBehavior.Restrict);
It's working.
Related
I have an audit-tracking like system, that contains the following two entities:
The JobCreate entity:
public class JobCreate
{
[Key] public string JobId { get; set; }
public List<AffectedEntity> AffectedEntities { get; set; }
}
And the AffectedEntity entity:
public abstract class AffectedEntity
{
[Required]
public string JobId { get; set; }
public int Id { get; set; }
[CanBeNull] public JobCreate Job { get; set; }
}
So far this is just a normal foreign key relation:
modelBuilder.Entity<JobCreate>()
.HasMany(j => j.AffectedEntities)
.WithOne(a => a.Job)
.HasForeignKey(a => a.JobId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
Entity Framework generates a foreign key for this relationship. My problem with this is that this audit system is event driven, which means it receives the events that creates the AffectedEntity and the event that creates the JobCreate entries out of order. In other words, the JobCreate entity might not yet exist when the AffectedEntity is created. However as far as the domain goes, this is actually fine. So how do I model that in Entity Framework? I want to be able to "navigate" along that connection from JobCreate to AffectedEntity, however the other direction is not necessary.
the JobCreate entity might not yet exist when the AffectedEntity is created. However as far as the domain goes, this is actually fine. So how do I model that in Entity Framework?
Just have the relationship in the EF model, but omit it or set the FK to not be enforced in the back-end. EG in SQL Server you would set the Foreign Key Constraint to NOCHECK.
Just beware that EF may assume that the FK is enforced when it creates queries. EG if you query db.AffectedEntities.Inclue("JobCreate") it may use an INNER JOIN and not return any AffectedEntities without a JobCreate.
And if you need to deal with AffectedEntities with a null JobID, you'd have to change the data type to int?.
I have an MVC application that uses Entity Framework v6. We have a class
public class ChildObject
{
public string Name { get; set; }
....
}
that maps to a table in the database. This table has 6 rows that are never changed. Neither will there ever be any additions. We have a second class defined along the lines of the following:
public class ParentClass
{
public int ChildObjectId { get; set; }
public ChildObject ChildObject { get; set; }
....
}
Whenever a ParentClass object is created or updated the logic only references the ChildObjectId property. The ChildObject property is only referenced when data is pulled back for viewing. However about once per month an extra row appears in the ChildObject table that is a duplicate of an existing row. This obviously causes issues. However I can't see how this could happen seeing as we only ever save using the Id value. Any thoughts on how this could be occurring would be very much appreciated.
The typical culprit for behavior like you describe is when a new child entity is composed based on existing data and attached to the parent rather than the reference associated to the context. An example might be that you load child objects as a set to select from, and send the data to your view. The user wants to change an existing child reference to one of the 6 selections. The call back to the server passes a child object model where there is code something like:
parent.ChildObject = new ChildObject{ Name = model.Name, ... }
rather than:
var child = context.Children.Single(x => x.Id = model.ChildObjectId);
parent.ChildObject = child;
Depending on how your domain is set up you may run into scenarios where the EF context creates a new child entity when a navigation property is set. Check with a FindUsages on the ChildObject property and look for any use of the setter.
In general you should avoid combining the use of FK properties (ChildObjectId) with navigation properties (ChildObject) because you can get confusing behavior between what is set in the navigation reference vs. the FK. Entities should be defined with one or the other. (Though at this time EF Core requires both if Navigation properties are used.)
A couple notables from your example:
Mark the navigation property as virtual - This ensures that EF assigns a proxy and recognizes it.
Option A - Remove the FK child ID property. For the parent either use an EntityTypeConfiguration or initialize the DbContext to map the FK column:
EntityTypeConfiguration:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.Map(x => x.MapKey("ChildObjectId"));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().Map(x => x.MapKey("ChildObjectId")).WillCascadeOnDelete(false);
}
or Option B - Ensure the FK is linked to the reference, and take measures to ensure that the two are always kept in sync:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.HasForeignKey(x => x.ChildObjectId));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().HasForeignKey(x => x.ChildObjectId)).WillCascadeOnDelete(false);
}
Option B is the only one currently available with EF Core to my knowledge, and it may help mitigate your issue but you still have to take care to avoid discrepancies between the navigation property and the FK. I definitely recommend option A, though it will likely require a bit of change if your code is commonly accessing the FK column.
I have a problem with the Entity Framework that I can't figure out.
I have a Module class that links to another Module (a one-to-one relationship) in two ways.
Code:
public class Module {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id {get;set;} /* primary key */
public Guid? OtherModule1 {get;set;}
[ForeignKey("OtherModule1")]
public Module OtherModule {get;set;}
public Guid? OtherModule2 {get;set;}
[ForeignKey("OtherModule2")]
public Module OtherModule2 {get;set;}
}
This gives the error that the principal end of the association cannot be determined.
Unable to determine the principal end of an association between the types 'Module' and 'Module'
I understand what the error means, but here's the thing. The relationship with OtherModule1 has always existed without a problem. This code works:
public class Module {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id {get;set;} /* primary key */
public Guid? OtherModule1 {get;set;}
[ForeignKey("OtherModule1")]
public Module OtherModule {get;set;}
}
There is no Fluent configuration present on this table
Now, if I add a new column, OtherModule2 and link it in the exact same way, this error pops up.
Does anyone have an idea of how to handle this?
TL/DR: one table has two foreign keys to the same table. One foreign key is handled correctly while the other is not.
The working model by convention defines one-to-many unidirectional (with only navigation property at the many side) relationship. It's equivalent of the following fluent configuration:
modelBuilder.Entity<Module>()
.HasOptional(e => e.OtherModule)
.WithMany()
.HasForeignKey(e => e.OtherModule1);
When you add a second FK / navigation property pair (I've renamed the FK property because you can't have 2 properties with the same name in the class):
public class Module
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; } /* primary key */
public Guid? OtherModule1 { get; set; }
[ForeignKey("OtherModule1")]
public Module OtherModule { get; set; }
[Column("OtherModule2")]
public Guid? OtherModule2_Id { get; set; }
[ForeignKey("OtherModule2_Id")]
public Module OtherModule2 { get; set; }
}
EF cannot automatically determine the relationships - it could be 2 unidirectional one-to-many or 1 bidirectional one-to-one, hence the error.
There is no way to specify that with data annotations in this case, so you need to use fluent configuration to either fully configure the relationships, or when combined with data annotations, to simply specify the cardinality and the navigation properties involved.
The following fluent configuration is sufficient to resolve the issue for the above data annotated model:
modelBuilder.Entity<Module>()
.HasOptional(e => e.OtherModule)
.WithMany();
modelBuilder.Entity<Module>()
.HasOptional(e => e.OtherModule2)
.WithMany();
When I am trying to clear a collection (calling .Clear) I get the following exception:
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.
The inner exception is:
A relationship from the 'User_Availability' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'User_Availability_Target' must also in the 'Deleted' state.
User looks like this:
....
ICollection<Availability> Availability { get; set; }
Availability looks like this:
int ID { get; set; }
User User { get; set; }
DateTime Start { get; set;
DateTime End { get; set; }
Configuration is as follows:
HasMany(x => x.Availability).WithRequired(x => x.User);
HasRequired(x => x.User).WithMany(x => x.Availability);
The code causing the problem is:
user.Availability.Clear();
I've looked at other alternatives such as using the DbSet to remove items, but I don't feel my code will be as clean. Is there a way to accomplish this by clearing the collection?
The only way that I'm aware of to make it work is defining the relationship as an identifying relationship. It would required to introduce the foreign key from Availability to User as a foreign key into your model...
public int ID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
...and make it part of the primary key:
modelBuilder.Entity<Availability>()
.HasKey(a => new { a.ID, a.UserID });
You can extend your mapping to include this foreign key (just to be explicit, it isn't required because EF will recognize it by convention):
modelBuilder.Entity<Availability>()
.HasRequired(a => a.User)
.WithMany(u => u.Availability)
.HasForeignKey(a => a.UserID);
(BTW: You need to configure the relationship only from one side. It is not required to have both these mappings in your question.)
Now you can clear the collection with user.Availability.Clear(); and the Availability entities will be deleted from the database.
There is one trick. You can delete entities without using special DbSet:
(this.dataContext as IObjectContextAdapter).ObjectContext.DeleteObject(entity);
Execute this for each item in Availability collection before clearing it. You don't need 'identifying relationships' for this way.
In case someone has the same problem using SQLite:
Unfortunately the accepted answer does not work with SQLite because SQLite does not support auto increment for composite keys.
You can also override the SaveChanges() Method in the Database context to delete the children:
//// Long Version
//var localChilds = this.SubCategories.Local.ToList();
//var deletedChilds = localChilds.Where(w => w.Category == null).ToList();
//foreach(var child in deletedChilds) {
// this.SubCategories.Remove(child);
//}
// Short in LINQ
this.SubCategories.Local
.Where(w => w.Category == null).ToList()
.ForEach(fe => this.SubCategories.Remove(fe));
#endregion
See this great Blogpost as my source (Unfortunately written in german).
I have a Video class and a MediaContent class that are linked by a 1-1, required:required relationship: each Video must have exactly 1 associated MediaContent. Deleting a MediaContent object must result in the deletion of the associated Video object.
Using the fluent API, the relationship can be modeled as follows:
modelBuilder.Entity<Video.Video>()
.HasRequired(v => v.MediaContent).WithRequiredPrincipal(mc => mc.Video)
.WillCascadeOnDelete(true);
When adding a migration to reflect this change in the database, this is how the relationship gets transcribed in terms of foreign keys:
AddForeignKey("MediaContents", "MediaContentId", "Videos", "VideoId", cascadeDelete: true);
Updating the database, I get the following error:
Cascading foreign key 'FK_MediaContents_Videos_MediaContentId' cannot be created where the referencing column 'MediaContents.MediaContentId' is an identity column.
Dropping the WillCascadeOnDelete(true) property removes the error, but I'm not sure I understand why. Shouldn't the error appear whether or not cascading is turned on? The way I understand the problem, the error comes from the fact that the generation of VideoId and MediaContentId is handled by auto-increment (or by whatever the id generation strategy is), potentially contradicting the foreign key constraint. But I can't see what this has to do with delete-cascading...
What am I missing? More generally, how would you go about modeling a cascadable one-to-one, required:required relationship with EF?
I avoid the modelBuilder cruft approach and use simple POCOs and attributes generally - which you can use to accomplish your goals like so:
public class Video
{
public int Id { get; set; }
// Adding this doesn't change the db/schema, but it is enforced in code if
// you try to add a Video without a MediaContent.
[Required]
public MediaContent MediaContent { get; set; }
}
public class MediaContent
{
[ForeignKey("Video")]
public int Id { get; set; }
public Video Video { get; set;}
}