Hi I'm using Entity framework to set my database.I have one-many relation entity and I want to only delete parent entity without cascading deletion. I'm getting error from deleting from parents but I really want to keep my child as a recording reason. Is there any ways to delete only parents without an error?
This is an old question but I came across this problem today so I'll share the solution that I found.
The long-story-short version is that you need to eager load your child association. Assuming you have your relationships setup correctly and a Foo has many Bars, this code should do exactly what you want:
public void Delete(Guid fooId)
{
using (var context = new MyDbContext())
{
var foo = context.Foos.Include("Bars").FirstOrDefault(foo => foo.Id == fooId);
if (foo != null)
{
context.Foos.Remove(foo);
context.SaveChanges();
}
}
}
The key here is the call to .Include. Without this, the update will fail with a foreign key violation.
Now, when I say I assume you have your relationships setup right, I mean they should look like this.
// Foo.cs
public class Foo
{
public Guid Id { get; set; }
public ICollection<Bar> Bars { get; set; }
}
// Bar.cs
public class Bar
{
public Guid Id { get; set; }
public Guid? FooId { get; set; }
public virtual Foo Foo { get; set; }
}
// MyDbContext
modelBuilder.Entity<Foo>()
.HasMany(e => e.Bars)
.WithRequired(e => e.Foo)
.HasForeignKey(e => e.FooId)
.WillCascadeOnDelete(false);
Related
I am trying to update a database row by reading the entity (using Entity Framework 6), mapping new properties onto that entity (using AutoMapper) and then calling context.SaveChanges().
The problem I am having surrounds the navigational properties within my entity. It appears that during the mapping, a new object is being created and assigned to the navigational property, rather than the existing one's properties simply being updated.
Here's are my domain objects:
public class ParagraphComponent : IReportComponent
{
public Guid ComponentId { get; set; }
public float LineHeight { get; set; }
public ReportTextList TextItems { get; set; } = new ReportTextList();
}
public class ReportTextList : IList<ReportText>
{
private readonly IList<ReportText> _list = new List<ReportText>();
public int Count => _list.Count;
public bool IsReadOnly => _list.IsReadOnly;
// Overrides for IList...
public void Add(ReportText item)
{
_list.Add(item);
}
// ...Remove(), Insert() etc.
}
public class ReportText
{
public Guid Id { get; set; }
public string Content { get; set; } = "";
}
Entity Framework entities:
public partial class ParagraphComponentEntity
{
public System.Guid ComponentId { get; set; } // ComponentId (Primary key)
public double LineHeight { get; set; } // LineHeight
public virtual System.Collections.Generic.ICollection<ReportTextEntity> ReportTexts { get; set; }
}
public partial class ReportTextEntity
{
public System.Guid Id { get; set; } // Id (Primary key)
public string Content { get; set; } // Content
}
What I am doing: I am taking data for a ParagraphComponent from an API endpoint to perform an update. I load the existing component based on ParagraphComponent.Id and then I map the new properties onto the existing entity.
This works fine:
public async Task<bool> EditComponent(IReportComponent editedComponent)
{
var currentParagraphComponentEntity = await Context
.ParagraphComponents
.FirstOrDefaultAsync(x => x.ComponentId == editedComponent.ComponentId)
.ConfigureAwait(false);
Mapper.Map(editedComponent as ParagraphComponent, currentParagraphComponentEntity);
Context.SaveChanges();
}
I can see in debug that the properties are mapped correctly, but when SaveChanges() is called I get the following error:
Violation of PRIMARY KEY constraint 'PK_ReportText'. Cannot insert duplicate key in object 'dbo.ReportText'
It appears that the mapping process is assigning a new object to the ParagraphComponentEntity.ReportTexts property, thus Entity Framework sees it as an "Add" rather than an "Update", so it tries adding a new row to that table which errors because of the primary key enforcing the Id to be unique.
My AutoMapper configuration:
CreateMap<ParagraphComponent, ParagraphComponentEntity>()
.ForMember(dest => dest.LineHeight, src => src.MapFrom(s => s.LineHeight))
.ForMember(dest => dest.ReportTexts, src => src.MapFrom(s => s.TextItems))
.ForMember(dest => dest.ComponentId, src => src.MapFrom(s => s.ComponentId))
.ForAllOtherMembers(src => src.Ignore());
If AutoMapper is creating a new instance for the ReportTexts navigational property is the issue, how do I get around it?
It seems that your code which is fetching data from database is only fetching the main resource ParagraphComponent. Collection of ReportTexts by default will not be fetched into EF context because as far as I know EF is lazy loading - you need to eagerly load referenced entities with .Include(..) for instance.
If I am right - then before mapping your data into entity ReportTexts collection is empty and your Mapping code is really creating new items in that collection. These entities are comming from outside of EF context (read about EF ChangeTracking) so 'it' thinks that these are new entities which needs to be inserted into database. These objects obviously already contains Id set with existing keys - so that's where you get conflicts.
I think if you will eager load your entity, then EF should perform Update instead
I have a set of models representing legal cases. One of the actions a user can do on a case is generate a document. This action is saved as a History entity, with an associated HistoryFile entity that contains the data about the file. Other actions may result in a History entity, with zero or multiple associated HistoryFile entities.
Cut-down versions of these two classes looks like this...
public class History {
public int Id { get; set; }
public ObservableCollection<HistoryFile> HistoryFiles { get; set; }
}
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
}
The next requirement is that a user can pick up on a document that was previously generated and continue working on it. The bit where I'm getting stuck is that the HistoryFile entity needs a reference back to the History entity that held the previous version. This means that I need to add two lines of code to the HistoryFile entity...
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
public int? PreviousHistoryId { get; set; }
public virtual History PreviousHistory { get; set; }
}
This means that there are two links from a HistoryFile to a History, one required one which is the parent History entity (via the History property) and an optional one via the PreviousHistory property.
I can't work out how to set this up for EF Core. As the code stands now, when I try to add a migration, I get the following error...
Cannot create a relationship between 'History.HistoryFiles' and 'HistoryFile.PreviousHistory' because a relationship already exists between 'History.HistoryFiles' and 'HistoryFile.History'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'HistoryFile.PreviousHistory' first in 'OnModelCreating'.
I tried adding the following to my DbContext...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
...but it didn't make any difference.
Anyone able to tell me how I configure this so that EF Core knows that there are two distinct links between the two entities?
I'm using EF Core 5.0.7 in a .NET5 project in case it makes a difference.
Thanks
Got it.
I needed to add the following two lines to the History class...
public virtual ICollection<HistoryFile> HistoryFilesParentHistory { get; set; }
public virtual ICollection<HistoryFile> HistoryFilesPreviousHistory { get; set; }
...and then change the code I added to the DbContext to look like this...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFilesParentHistory)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFilesPreviousHistory)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
This worked fine.
When creating a new parent object, I need to attach a child to it in two places. I receive the error:
Unable to determine valid ordering list and single reference to child
My model looks like:
public class Child()
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
public int FavouriteChildId { get; set; }
public virtual Child FavouriteChild { get; set; }
public void AddChild(string name)
{
var child = new Child { Name = name };
Children.Add(child);
if (Children.Count() == 1)
{
FavouriteChild = child;
}
}
}
I use fluent mapping for the Entity Framework configuration:
public class ParentMap : EntityTypeConfiguration<Parent>
{
public ParentMap()
{
this.HasRequired(t => t.FavouriteChild)
.WithMany()
.HasForeignKey(d => d.FavouriteChildId);
}
}
public class ChildMap : EntityTypeConfiguration<Child>
{
public ChildMap()
{
this.HasRequired(t => t.Parent)
.WithMany(t => t.Children)
.HasForeignKey(d => d.ParentId );
}
}
When inserting into the database, I use:
// Snipped out a standard EF Database context, repositories, generics, etc.
var newParent = new Parent();
newParent.AddChild("Felix");
newParent.AddChild("Naomi");
var parentSet = context.Set<Parent>();
parentSet.Add(newParent);
context.SaveChanges();
SaveChanges() throws the error given above.
I imagine that there is something wrong with the way I have my mapping setup and that Entity Framework cannot work out which way around to insert the Child.
I cannot do this in two steps because I need there to be at least one Favourite Child (thus the FavouriteChildId integer is not nullable). I want to also avoid moving the concept of "Favourite" to the Child entity (by adding a boolean property) because the child should never know that it's the favourite. Favouritism is a facet of the parent.
Thanks in advance.
I feel that you need to re-design it.
We seem to be forcing Entity into the Chicken or the Egg dilemma here.
The Parent needs a Child to be inserted before
it because FavouriteChildId is required for Parent to be inserted.
The Child needs a Parent to be inserted before it because ParentId is needed
for Child to be inserted.
Which one should be inserted first, the Parent or the Child?
I wondered if anyone can advise me on how to resolve a problem with regards to using FluentAPI to map a couple of tables.
I have Parent table that has our key called ID
Then a Child table with two fields idA & idB.
The primary key in the parent table links to either idA or idB, not both.
public Parent()
{
this.ChildA = new HashSet<Child>();
this.ChildA = new HashSet<Child>();
}
public virtual ICollection<Child> ChildA { get; set; }
public virtual ICollection<Child> ChildB{ get; set; }
}
public Child()
public virtual Parent parent { get; set; }
}
There is much I can do about the relationship/table design because it is legacy and cannot be changed. Just need to understand the correct FluentAPI to use to account for this issue. Above example it what I envisaged would be needed along with something like...
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.parent).WithMany(q => q.childs).HasForeignKey(r => r.idA);
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.parent).WithMany(q => q.childs).HasForeignKey(r => r.idB);
I believe I was able to get the correct mapping you are looking for. I added naviation properties to the POCO which allows Entity Framework to know how to use the foreign keys in code.
public class Child
{
public int Id { get; set; }
public virtual Parent ParentA { get; set; }
public virtual Parent ParentB { get; set; }
public Child() { }
}
To map these navigation properties to you already existing foreign key columns, I used the FluentAPI Map method.
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.ParentA).WithMany(q => q.ChildA).Map(m => m.MapKey("idA")).WillCascadeOnDelete(false);
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.ParentB).WithMany(q => q.ChildB).Map(m => m.MapKey("idB")).WillCascadeOnDelete(false);
With this, I have indicated ParentA populates the ChildAcollection, and ParentB populates the ChildB collection. The Map method is what allows me to map to your already existing FKs, and I don't have to include them with the POCO as a property.
Note that each POCO that maps to a table must have a primary key. Does your already existing child table have a PK? If not, you may have some further trouble. I recommend reading this SO post about it. Entity Framework: table without primary key
I'm using fluent nhibernate to map a parent child relationship to the sql database.
Most of the times i let the parent entity save it's child entities,they are inserted in 1 transaction and if i'm not mistaken this wouldn't be possible if i used .Inverse() and sql identity columns.
The problem i have is that in 1 specific case i want to update the child entity and ONLY the child entity.
When i do this with my current setup the child record will lose the relationship to it's parent(if the parent object is null) or will replace my parent object completely(if i insert a dummy parent with just an id).
Does anyone know a way to achieve a single record update without affecting the foreign key?
I can think of a manual sql statement or a stored procedure but i'm hoping there is an nhibernate way.
I have the following setup (simplified for your convenience) :
public ProjectMap()
{
Table("Project");
Id(p=> p.Id);
HasMany(p => p.Risks).Not.LazyLoad();
}
public RiskMap()
{
Table("ProjectRisk");
Id(r=> r.Id);
References(r => r.Project).Column("ProjectId");
Map(r => r.Description);
}
public class Project
{
public virtual int Id { get; set; }
public virtual IList<Risk> Risks { get; set; }
}
public class Risk
{
public virtual int Id { get; set; }
public virtual string Description{ get; set; }
public virtual Project Project { get; set; }
}
As Miroslav suggested i'm using an HQL update statement now.
It's a little messy for my taste but it gets the job done.
Session.CreateQuery("update Risk set Closed = :completed where Id = :id")
.SetInt32("id", id)
.SetBoolean("completed", completed)
.ExecuteUpdate();