6

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)

Abdul Ahmad
  • 9,673
  • 16
  • 64
  • 127
  • possible duplicate of [Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths](http://stackoverflow.com/questions/19373310/introducing-foreign-key-constraint-may-cause-cycles-or-multiple-cascade-paths) – Colin Nov 06 '14 at 17:23

3 Answers3

26

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.

  1. 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.

  2. 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.

Scott Munro
  • 13,369
  • 3
  • 74
  • 80
  • this is actually the initial seed method for the database, I'm adding a bunch of 'stuff' then doing a context.saveChanges all the way at the end. can you elaborate on the first fix you mentioned? How can I have a 'temporary' unique identifier? Would that be a property in the entity classes? – Abdul Ahmad Nov 07 '14 at 12:36
  • The temporary identifiers should be assigned to the property that is designated as the key for the entity. Look for one that is decorated with the KeyAttribute. http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.keyattribute(v=vs.110).aspx – Scott Munro Nov 07 '14 at 12:57
  • I'm sorry for being a pain in the rear, but the way I understand it is, I create a new property (int UniqueID{get;set;}) and decorate that with [keyattribute]? However, if we're looking at my entity classes, would this go on the Contract/advertisementRevenue classes or on the RevenueAmounts class? – Abdul Ahmad Nov 07 '14 at 13:18
  • Ohhhh. Sorry I see, the [Key] attribute.. ok, so I just put this on top of my primary key, but it will then create 2 values for my primary key, one as the primary and another as a unique ID? – Abdul Ahmad Nov 07 '14 at 13:20
  • but I'm still wondering which entity's ID property this goes on, I've tried putting a [key] attribute on the primary IDs of all my entities, as well as only on some, and I'm still getting the same exception – Abdul Ahmad Nov 07 '14 at 13:32
  • It is the parent in the relationship that requires the temporary, unique values - I would probably just put it on all of them. Don't forget to assign these properties the temporary, unique values on any new objects. – Scott Munro Nov 07 '14 at 14:58
  • wait, what do you mean by "Don't forget to assign these properties the temporary, unique values on any new objects." I have to manually assign a unique value? Assuming my primary key is "int MyClassID" (decorated with [Key]), I do something like, "MyClassObject.MyClassID = someNumber"? – Abdul Ahmad Nov 07 '14 at 15:08
  • just want to point out that I've tried taking off the foreign key constraints and using entity references, and EF6 creates the foreign keys automatically without throwing exceptions. However, the only problem is, I need to access these foreign keys so that when I add an item I need to know which entity it belongs to, because the entity is referenced as a List, and I need to know which entity in the list I'm adding to, is there any way to access the auto-generated foreign keys? – Abdul Ahmad Nov 07 '14 at 23:30
  • Hi Abdul. You had two comments - yes to the first one. For the second, I think that you are talking about being able to reference the parent entity from the child. If so, you would probably need to add a navigation property to the child to do so. – Scott Munro Nov 10 '14 at 11:46
  • 1
    first approach did work for me well. My EF Class "Student" has a Guid as an ID (entity key) which is assigned by database on context.SaveChanges(). Setting it up using: ID = Guid.NewGuid() in the code solved the case. This ID won't be used but it'll take away the problem. I like this solution. – Mariusz Mar 29 '16 at 11:50
1

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.

IsakBosman
  • 1,453
  • 12
  • 19
  • can you elaborate on how to make my model implement an aggregate root? also where do I see the option for cascade delete, inside visual studio server explorer? – Abdul Ahmad Nov 06 '14 at 16:58
  • Let me try and find some links for you. Cascade delete can be switched on an off on table level when you generate your migrations. You can also configure this in the Fluent API – IsakBosman Nov 06 '14 at 20:41
  • Thanks for following up, I found out how to turn it off, but 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. – Abdul Ahmad Nov 06 '14 at 23:30
  • You need to remove the Athlete declaration in YearsAsAthlete or make it virtual. EF complains if you declare relationships to objects in both sides of the relationship as it cannot determine the parent object. – IsakBosman Nov 07 '14 at 05:46
  • I see, I'm looking into this right now, will update when I've tested it – Abdul Ahmad Nov 07 '14 at 12:26
0

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.