I have the following code:
public class Parent
{
public int ParentId {get;set;}
public ICollection<Child> Children {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public Parent Parent {get;set;}
}
It's mapped as one-to-many by EF without any additional efforts. When I replace Children with new collection (with 3 another items) I have old orphan entities in child-table like this:
Id | Parent_Id
1 NULL <-- orphan
2 NULL <-- orphan
3 NULL <-- orphan
4 1 <-- new
5 1 <-- new
6 1 <-- new
I want to get rid of them with identifying relationship explicitly:
modelBuilder.Entity<Child>().HasKey(x => new { x.ChildId, x.ParentId });
modelBuilder.Entity<Parent>().HasMany(x => x.Children).WithRequired().HasForeignKey(x => x.ParentId);
But I don't have ParentId property in children. I have only "Parent" pointing directly to the parent entity. If I try
modelBuilder.Entity<Child>().HasKey(x => new { x.ChildId, x.Parent });
I get an error message:
The property ... cannot be used as a key property on the entity ... because the property type is not a valid key type
Is there any way remove all unnecessary orphans?
As far as I know for identifying relationship you will need the parents primary key contained as part of the primary key of the child. EF will then allow you to remove the child from collection and delete it for you.
Just having the navigation property won't work. Identifying relationships are a DB level concept so needs an exposed Primary key id
See my question here on that subject
In my application I have a similar model. However, instead of removing orphans I do not produce them. If I want to remove some child objects I do it in the following way:
//This line attaches a parent object to the session. EF will start monitoring it.
Entry(parent).State = EntityState.Modified;
//This code is responsible for finding children to delete.
foreach (var child in parent.Children.Where(ch => ch.Deleted))
//This line says EF that given child should be removed.
//This line also causes that an object will be removed from Children collection.
Entry(child).State = EntityState.Deleted;
SaveChanges();
Deleted is a property which I set to true if I want to delete an object.
Related
I have some entities (for sake of simplicity it looks like that):
class DbEntity
{
public Guid Id {get;set;}
}
class BaseEntity: DbEntity
{
public string Name {get;set;}
public ParentEntity Parent {get;set;}
}
class ParentEntity: BaseEntity
{
List<BaseEntity> Children {get;set;}
}
class ChildEntity: BaseEntity
{
}
Now, my fluent mappings look like that:
class DbEntityMap<T>: ClassMap<T> where T: DbEntity
{
public DbEntityMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
}
}
class BaseEntityMap: DbEntityMap<BaseEntity>
{
UseUnionSubclassForInheritanceMapping();
Map(x => x.Name).Not.Nullable();
References(x => x.Parent).Cascade.None();
}
class ParentEntityMap: SubclassMap<ParentEntity>
{
Abstract();
HasMany(x => x.Children)
.ForeignKeyCascadeOnDelete()
.Inverse()
.Not.KeyUpdate()
.Cascade.None()
.LazyLoad();
}
class ChildEntityMap: SubclassMap<ChildEntity>
{
Abstract();
//some other stuff
}
Now, I have client desktop application that sends request to WebAPI.
The first request is to create all entities, like for example:
ParentEntity parent = new ParentEntity();
parent.Name = "New name";
ChildEntity child1 = new ChildEntity();
ChildEntity child2 = new ChildEntity();
parent.Children.Add(child1);
parent.Children.Add(child2);
//of course children know their parent:
//child.Parent = parent;
Now I am building some dto from that (because my model is much more complicated and creating a dto is good idea) to build a json, and send this json to WebAPI. Entities in database are created properly. Great.
But now I have to modify some child entity, for example:
child1.SomeValue = newValue;
Now, while creating DTO I don't add parent entity to that, just parent id and it looks more less like that:
class ChildDto
{
public Guid DbId {get;set;}
public Guid ParentDbId {get;set;}
public int SomeValue {get;set;}
}
Now, when my WebAPI receives such dto it recreates a model like that:
ParentEntity fakeParent = new ParentEntity();
fakeParent.Id = childDto.ParentDbId;
ChildEntity child = new ChildEntity();
//assign other child values and then parent:
child.Parent = fakeParent;
Now when I do Update(just session.Update(obj, id)) my child entity updates properly, BUT parent entity updates also. As I didn't set Name property in parent entity, Name field in database becomes empty.
I thought that setting Cascade to None would prevent NHibernate from updating parent entity. But no Cascade setting works. If I set the parent binding to:
References(x => x.Parent).ReadOnly()
then my ChildEntity is updated without parent id.
I know at least couple of solutions for that like:
1. Transform also whole parent entity in DTO - but this can make other cascade updates and if not - there will be two updates instead of one.
2. Select parent entity just before update - but this creates unnecessary SELECT.
What I would like to achieve is:
- update ONLY childEntity - without updating parent nor any other entity.
How can I acomplish that?
[Some words about actual application]
In reality my ParentEntity could hold BaseEntities. ParentEntity and ChildEntity have also some common properties.
What's more ChildEntity can hold other DbEntities.
Children of child entity also hold other DbEntities, it looks like that:
ParentEntity -> ChildEntity -> EntityA -> EntityB -> EntityC
(like a tree)
(every derives from DbEntity).
And I even managed to solve the problem using session.Merge, but when I try to UPDATE for example EntityA the problem is the same - ChildEntity loses connection with ParentEntity :|
I thought that setting Cascade to None would prevent NHibernate from updating parent entity. But no Cascade setting works
You are expecting that Cascade.None will prevent updating associations. Though this is true, this is not the case in your code. The parent entity is being updated because of your strange inheritance hierarchy. I do not understand need of multi-level inheritance. If you remove BaseEntity, what you are willing to achieve will happen without need to call Load.
The scenario you are working is explained here:
10.4.2. Updating detached objects
Many applications need to retrieve an object in one transaction, send it to the UI layer for manipulation, then save the changes in a new transaction. ........
This approach requires a slightly different programming model to the one described in the last section. NHibernate supports this model by providing the method ISession.Update().
You should also have look at this:
10.10. Lifecycles and object graphs
The precise semantics of cascading operations are as follows:
If a parent is saved, all children are passed to SaveOrUpdate()
If a parent is passed to Update() or SaveOrUpdate(), all children are passed to SaveOrUpdate()
If a transient child becomes referenced by a persistent parent, it is passed to SaveOrUpdate()
If a parent is deleted, all children are passed to Delete()
If a transient child is dereferenced by a persistent parent, nothing special happens (the application should explicitly delete the child if necessary) unless cascade="all-delete-orphan" or cascade="delete-orphan", in which case the "orphaned" child is deleted.
Notice the highlighted point above. Your both ParentEntity and ChildEntity derive from BaseEntity. Further, BaseEntity holds the association property ParentEntity Parent. When you Update child entity, your parent entity is passed to SaveOrUpdate. As you have not specified Name there, it updates the database with empty name.
If I set the parent binding to:
References(x => x.Parent).ReadOnly()
then my ChildEntity is updated without parent id.
This is obvious; you explicitly set it to read only.
You should session.Load existing objects that you don't want to update (do not confuse it with session.Get - it wouldn't issue SELECT statement for lazy entities).
So you need to change your DTO -> Entity conversion logic to something like this:
ParentEntity fakeParent = session.Load<ParentEntity>(childDto.ParentDbId);
ChildEntity child = new ChildEntity();
//assign other child values and then parent:
child.Parent = fakeParent;
Can someone help me work out whats going on? I'm trying to update a foreign key and I'm getting strange behaviour. We have change tracking and lazy loading disabled and are using POCO classes. I am unable to update the foreign key Id without it throwing the following error:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property value(s) of 'Child.ChildId' on one end of a relationship do not match the property value(s) of 'Link.ChildId' on the other end.
var linkTable = await Context.LinkTable
.Include(x => x.Child).FirstAsync(_ => _.LinkId == linkId);
// .... Log details of linkTable.Child
var newChild = await Context.ChildTable.FirstAsync(_ => _.ChildId == newChildId);
linkTable.Child = newChild;
linkTable.ChildId = newChild.ChildId; // (1)
Context.Entry(linkTable).State = EntityState.Modified;
await Context.SaveChangesAsync();
var parent = await Context.ParentTable
.Include(x => x.Links.Select(y => y.Child))
.FirstAsync(x => x.ParentId == parentId); // (2)
If I remove line (1), the error goes away at the SaveChanges call but it doesn't update the ChildId so the change is lost. I have also tried just updating the ChildId on it's own, and also nulling the child object first. Any changes to the ChildId throw this error.
The parent at line (2) loads the link with the new child even though it still shows the old ChildId. The db is not updated so the change is never committed.
The POCO:
[Table("LinkTable")]
public class LinkTable
{
[Key]
public int LinkTableId { get; set; }
//[ForeignKey("Child")] // (1)
public int ChildId { get; set; }
[ForeignKey("ChildId")]
public virtual ChildTable Child { get; set; } // (2)
}
(1) I have tried with both variants of the ForeignKey attribute.
(2) I have tried with and without the virtual key word, I believe without lazy loading we shouldn't need it.
The error makes no sense to me as the ChildId is taken from the child object, how can they not match?
assuming I have the following model structures for an asp.net mvc 5 app using entity framework 6
class Athlete {
int AthleteID {get; set;}
List<YearsAsAthlete> YearsAsAthlete {get;set;}
}
class YearsAsAthlete {
int YearsAsAthleteID {get;set;}
int AthleteID {get;set;}
[ForeignKey("AthleteID")]
Athlete Athlete {get;set;}
List<ContractRevenue> ContractRevenue {get;set;}
List<AdvertisementRevenue> AdvertisementRevenue {get;set;}
}
class ContractRevenue {
int ContractRevenueID {get;set;}
int YearsAsAthleteID {get;set;}
[ForeignKey("YearsAsAthleteID")]
YearsAsAthlete YearsAsAthlete {get;set;}
List<RevenueAmounts> RevenueAmounts {get;set;}
}
class AdvertisementRevenue {get;set;}
int AdvertisementRevenueID {get;set;}
int YearsAsAthleteID {get;set;}
[ForeignKey("YearsAsAthleteID")]
YearsAsAthlete YearsAsAthlete {get;set;}
List<RevenueAmounts> RevenueAmounts {get;set;}
}
class RevenueAmounts {
int RevenueAmountsID {get;set;}
int AmountPaid {get;set;}
date DateOfPayment {get;set;}
}
These models work fine when I have them like this, they have relationships and everything is delicious like a hot fudge sundae. When I run this, the database creates these tables and the RevenueAmounts table get 2 auto-generated foreign key columns for ContracRevenue and AdvertisementRevenue.
However, I don't want these as they're named strangely (ContractRevenue_ContractRevenueID) and I need some way to access the foreginkey id property in my post controller method for adding new values that correlate with the right type of revenue.
When I change the RevenueAmounts model to the following:
class RevenueAmounts {
int RevenueAmountsID {get;set;}
int AmountPaid {get;set;}
date DateOfPayment {get;set;}
// ***NOTE adding foreign keys here
int ContractRevenueID {get;set;}
[ForeginKey("ContractRevenueID")]
ContractRevenue ContractRevenue {get;set;}
int AdvertisementRevenueID {get;set;}
[ForeignKey("AdvertisementRevenueID")]
AdvertisementRevenue AdvertisementRevenue {get;set;}
}
I start getting an exception:
[SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_dbo.AdvertisementRevenue_dbo.YearsAsAthlete_YearsAsAthleteID' on table 'AdvertisementRevenue' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
** EDIT **
I've turned off the cascading delete functionality using the fluent API however now I'm getting a different exception:
Unable to determine the principal end of the 'GIP.DAL.ContractRevenue_RevenueAmounts' relationship. Multiple added entities may have the same primary key.
By the way, the exception is being thrown when I'm trying to seed a bunch of info into the database and then doing context.SaveChanges() at the bottom (only doing it once at the end)
In your edit, you mention the 'Multiple added entities may have the same primary key.' error. Without knowing all of the details of what you are doing here, it sounds like you are creating a relationship with an entity - of which there are two in the context with the same ID. These are probably new entities which have not yet been saved which is where they get an automatically generated ID from the database. If the relationship is based on the ID then there is some ambiguity because Entity Framework is unable to determine which of the new entities the relationship is actually pointing to - they both have the ID that the relationship is pointing to.
There are two potential fixes.
Generate a temporary, unique identifier for entities as they are created in the context. Entity Framework will discard this as the entity is saved but up until that point, it can use it to tell one new entity apart from the other. I have used negative integers for this purpose in the past.
Do not create the relationships using IDs but rather on entity references. If Entity Framework has a direct reference to the entity, then it does not need to go through the process of identifying the entity based on non-unique identifiers and should not have this problem.
This is happening because Entity Framework cannot determine which object within the relationship is the parent when Cascade delete is enabled.
Are you using Code First? When the Migration is generated you will see an option for cascade delete in the table declaration. This should be resolved if you set that to false.
However the bigger issue is that you are creating object relationships that doesn't implement an Aggregate Root which most often would avoid this issue.
I had the same problem but in my case there was another solution.
In a loop i was adding parents
for each parent I was adding children. There is relation between parent and child. Of course I used the FK as a virtual reference column but still the problem appeared.
foreach (var parent in parents)
{
var id = parent.Id;
var parentEntity = new Parent();
this.Context.Set<Parent>().Add(parentEntity);
parentEntity.CreatedOn = DateTime.UtcNow;
...
foreach (var child in parents[id].Children)
{
var childEntity = new Child();
//this.Context.Set<Child>().Add(childEntity);
parent.Children.Add(childEntity);
childEntity.CreatedOn = DateTime.UtcNow;
...
}
}
I had to comment out the line that was adding the child entity to EF context and it fixed the issue.
This error has been asked about A LOT. But none of the cases I think apply to my particular case, or at least not quite.
I am creating a new entity with 2 navigation properties that are collections. Both the entity and the navigation properties are new entities that do not exist in the database. My problem is that whenever I try to attach the entity to the context, if either of the collections has more than 1 element I get to aforementioned exception.
I am getting this error on the Attach() instruction in the following code:
using (var context = new NSModel())
{
context.Notifications.Attach(e);
context.ObjectStateManager.ChangeObjectState(e,
StateHelpers.GetEquivalentEntityState(e.State));
foreach (NavigationProperty1 np in e.NavigationProperty1s)
context.ObjectStateManager.ChangeObjectState(np,
StateHelpers.GetEquivalentEntityState(np.State));
foreach (NavigationProperty2 np in e.NavigationProperty2s)
context.ObjectStateManager.ChangeObjectState(np,
StateHelpers.GetEquivalentEntityState(np.State));
context.SaveChanges();
return e;
}
The code is for a web site so the entities are stateless and the context is created and disposed of with every call...
Any ideas?
Both the entity and the navigation properties are new entities that do
not exist in the database.
Then the question is: Why do you use Attach? By using Attach you tell EF that the entites are in the database and EF will consider the values of the primary key properties as the PK column values in the database. Because those properties must be unique EF will complain as soon as you have two entites with the same key values. It's well possible that you have this situation when you have autogenerated identities as key properties and don't set the values when you create the entities.
Simple example:
public class Parent
{
public Parent
{
Children = new List<Child>();
}
public int Id { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
}
This code ...
using (var ctx = new MyContext())
{
var parent = new Parent();
var child1 = new Child(); // <- Id is 0
var child2 = new Child(); // <- Id is 0 as well
parent.Children.Add(child1);
parent.Children.Add(child2);
ctx.Parents.Attach(parent); // <- exception here
//...
}
... will throw your exception because EF tries to attach two different child instances with the same key 0. When the second child child2 will get attached it says: "An object (namely child1) with the same key (namely 0) already exists in the ObjectStateManager".
If you want to add the whole object graph as new entities to the database you could just call context.Notifications.AddObject(e); instead of Attach.
In a situation where you have a parent class which has one child class, what is the best pattern for mapping the entities.
I have seen a lot of suggestions where the parent has a reference to both the child class, and the child class id. E.g.
public class Parent
{
public int Id
{
get;
set;
}
public int ChildId
{
get;
set;
}
public virtual Child Child
{
get;
set;
}
}
public class Child
{
public int Id
{
get;
set;
}
}
public class ParentMapping : EntityTypeConfiguration<Parent>
{
public ParentMapping()
{
HasKey(x => x.Id);
HasRequired(X => x.Child)
.WithMany()
.Map(x => x.ToTable("Parent")
.MapKey("ChildId"));
}
}
With this pattern, when saving the parent, if you want to swap out the child for a different but existing child, examples I have seen just update the ChildId and not the Child which feels wrong because the object is out of sync with itself.
The code looks neater without the ChildId but with this pattern I am having trouble saving the parent using an existing child because EF is trying to save a new child.
public class Parent
{
public int Id
{
get;
set;
}
public virtual Child Child
{
get;
set;
}
}
What is the best pattern, I would like to know if the ChildId is needed, then how is the Child property kept in sync and will it be lazy loaded from the database or not.
This is difference between foreign key and independent association. When using foreign key association you can really use just key without loading the related object. It makes the reference out of sync if you have it loaded - which is not always the case. If you want to keep the reference in sync you are almost back in the situation which you must solve with independent association.
If you expose foreign key you should use it because it makes a lot of things much more easier. If you use the independent association you should do something like:
var parent = GetUpdatedParentSomehow();
// Dummy object for the old child if the relation is not loaded
parent.Child = new Child { Id = oldChildId };
// Attach the parent
context.Parents.Attach(parent);
// Create dummy for new child (you can also load a child from DB)
var child = new Child { ID = newChildId };
// No attach the child to the context so the context
// doesn't track it as a new child
context.Childs.Attach(child);
// Set a new child
parent.Child = child;
// Set parent as modified
context.Entry(parent).State = EntityState.Modified;
context.SaveChanges();
There is very strange part where I'm creating dummy for the old child. I'm almost sure that if I don't do it before attaching the parent and setting the new child I will get some exception during saving changes (in case of independent association).