Error in Entity Framework Saving with Child Entity - entity-framework

I have the following model:
public class Job
{
[Key]
public int JobID { get; set; }
public string Status { get; set; }
public DateTime JobDate { get; set; }
public string JobTitle { get; set; }
public int? Cleaner { get; set; }
public int? Client { get; set; }
public int EstTime { get; set; }
public virtual Client ClientInfo { get; set; }
public virtual Valeter ValeterInfo { get; set; }
}
This in OnModelCreating:
// Relationship Job -> Valeter
modelBuilder.Entity<Job>()
.HasOptional<Valeter>(u => u.ValeterInfo)
.WithMany()
.HasForeignKey(e => e.Cleaner);
(NOTE: it is using an existing database). When I try to perform the following:
if (ModelState.IsValid)
{
db.Entry(job).State = EntityState.Modified;
db.SaveChanges();
}
It generally works fine UNLESS I change the Cleaner value to something else and then I get the error:
A referential integrity constraint violation occurred: The property
values that define the referential constraints are not consistent
between principal and dependent objects in the relationship.

This exception usually occurs if job.ValeterInfo != null and job.ValeterInfo.ValeterId != job.Cleaner. So, the simplest solution is to set the navigation property to null before you attach the job to the context:
if (ModelState.IsValid)
{
job.ValeterInfo = null;
db.Entry(job).State = EntityState.Modified;
db.SaveChanges();
}
This looks a bit strange and like a hack. But the question is why job.ValeterInfo is NOT null when you post the data to the controller action. When you set the state of the job to Modified you are only updating the job's scalar properties (including Cleaner) but not any properties of job.ValeterInfo or any relationships. So, you don't need to send job.ValeterInfo properties to the server in the first place.
Anyway, you have an inconsistency: The FK job.Cleaner is changed but the related entity job.ValeterInfo (especially its primary key property ValeterId) is not. EF doesn't know which represents the correct relationship: The foreign key property value or the navigation property value? This ambiguity causes the exception.

Related

Multiple relations to same table with one required and others optional in EF Core

I have a table named Provider with three relations to another table State. Of these relations one is required and the other two are optional. See the relationship in the diagram below:
Here are the entities along with the fluent configurations for each.
Provider
public class Provider
{
public int Id { get; set; }
[Required]
public int PrimaryStateId { get; set; }
public virtual State PrimaryState { get; set; }
public int? BillingStateId { get; set; }
public virtual State BillingState { get; set; }
public int? ShippingStateId { get; set; }
public virtual State ShippingState { get; set; }
}
class ProviderConfig : IEntityTypeConfiguration<Provider>
{
public void Configure(EntityTypeBuilder<Provider> entity)
{
entity.HasOne(x => x.PrimaryState)
.WithMany(x => x.ProvidersPrimary)
.IsRequired(true)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(x => x.BillingState)
.WithMany(x => x.ProvidersBilling)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
entity.HasOne(x => x.ShippingState)
.WithMany(x => x.ProvidersShipping)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
}
}
State
public class State
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Provider> ProvidersPrimary { get; set; } = new List<Provider>();
public virtual ICollection<Provider> ProvidersBilling { get; set; } = new List<Provider>();
public virtual ICollection<Provider> ProvidersShipping { get; set; } = new List<Provider>();
}
class StateConfig : IEntityTypeConfiguration<State>
{
public void Configure(EntityTypeBuilder<State> entity)
{
entity.Property(x => x.Name).IsRequired();
entity.HasIndex(x => x.Name).IsUnique();
}
}
As you can see, I want to set DeleteBehavior.Restrict for the PrimaryState, and DeleteBehavior.SetNull for the other two relations. However, this throws error on update-database with the following message:
Introducing FOREIGN KEY constraint 'FK_Provider_State_ShippingStateId' on table 'Provider' 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.
However, if I get rid of one of the optional relations, it works fine. That is, it works when I have one required relation and one optional relation, but not when I have one required and two optional relations. Also, it works fine if I get rid of OnDelete(DeleteBehavior.SetNull) from the optional relations, but then in the database the delete rule for foreign key BillingStateId ends up being Set Null, but that for ShippingStateId is No Action. No idea why it is different for two similarly configured optional relations.
Questions:
Why does update-database fail with that error? I don't understand how cycles or multiple cascade paths may be caused.
Why getting rid of OnDelete(DeleteBehavior.SetNull) creates a foreign key with Set Null delete rule for one optional relation but No Action for the other?
What is the correct way to configure this relationship? That is, one required relation and two optional relations, and the foreign keys for the optional relations should have a delete rule of Set Null.
VS Solution link: click

EF 6 Code First storing an entity Reference to specific child in a one of the collections on the entity

I have a domain model that has a collection of entities configured in the normal 1 to many relationship, however I want to store a reference to a specific item in that collection using a FK in this model
The list as defined in the model
public ICollection<SLWOUpdate> Updates { get; set; }
The reference to the specific item in the list
public int? SLWOUpdateId { get; set; }
[ForeignKey("SLWOUpdateId")]
public virtual SLWOUpdate LastUpdate { get; set; }
Of course the code is responsible for updating the specific item as opposed to having EF do it.
Is this kind of relationship configurable in EF?
The reason I want to do this is for querying filtering purposes as part of complex query that must execute as one statement
Ended up adding a new domain model to represent the LastUpdate which simply holds a primary key to this entity and a FK to the LastUpdate
New Domain Model to represent the Last Update
public virtual SLCurrentWOUpdate LastUpdate { get; set; }
public class SLCurrentWOUpdate
{
[Key]
public int SLWorkOrder_Id { get; set; }
public SLWorkOrder SLWorkOrder { get; set; }
public int? SLWOUpdateId { get; set; }
[ForeignKey("SLWOUpdateId")]
public SLWOUpdate SLWOUpdate { get; set; }
}
I can query this as part of a larger more complex set of predicates... I just have to reach into the model one reference deeper:
db.SLWorkOrders
.Where(t => t.TAutoDeclined != null && t.TClosedPendingPayment != null)
.Where(t => t.LastUpdate.SLWOUpdate.UpdateStatusType.SystemName == "CHANGE_PRIORITY");
Feels kind of hackish.. but it works..

Entity Framework self referencing entity

I have a problem with the Entity Framework.
public class User : Receiver
{
public User()
{
if (Groups == null)
Groups = new List<Group>();
if (Buddies == null)
Buddies = new List<User>();
}
[Required]
public string PhoneNumber { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<Group> Groups { get; set; }
}
public class Receiver
{
public Receiver()
{
Guid = Guid.NewGuid();
Created = DateTime.Now;
}
[Key]
public Guid Guid { get; set; }
[Required]
public DateTime Created { get; set; }
}
When i try to add a user...
User user = new User
{
Guid = new Guid("8cd094c9-e4df-494e-b991-5cf5cc03d6e3"),
PhoneNumber = "+4991276460"
};
cmc.Receivers.Add(user);
... it ends in follogwing error.
The object of the Type "System.Collections.Generic.List`1[Project.Models.User]" can't be converted to "Project.Models.User".
When i comment out following two lines:
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...the programm runs fine.
I hope someone can help me to fix this problem.
Otherwise it runs into an error at this line : cmc.Receivers.Add(user);
In your mapping...
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...you specify that User.Buddies is part of a one-to-many relationship and that User.Guid (=Receiver.Guid) is the foreign key in this relationship. But User.Guid is also the primary key, hence it must be unique. As a result a User cannot have a list of Buddies but only a single reference.
The mapping makes no sense but the exception is not very helpful and difficult to understand. (Somehow EF seems to recognize internally that the Buddies cannot be a list with that mapping and wants to cast the list to a single reference. It should detect in my opinion that the mapping is invalid in the first place.)
For a correct one-to-many mapping you need a foreign key that is different from the primary key. You can achieve that by either removing the [ForeignKey] annotation altogether...
[JsonIgnore]
public IList<User> Buddies { get; set; }
...in which case EF will create a default foreign key in the Receivers table (it will be some column with an underscore in its name, but you can rename that with Fluent API if you don't like the default name) or by adding your own foreign key property to the User class:
public Guid? BuddyGuid { get; set; }
[ForeignKey("BuddyGuid"), JsonIgnore]
public IList<User> Buddies { get; set; }

Manually Updating a Many-to-Many Relationship in Entity Framework Code First

Although the link tables which facilitate a many-to-many relationship are usually hidden by EF, I have an instance where I think I need to create (and manage) one myself:
I have the following entities:
public class TemplateField
{
public int Id
{
get;
set;
}
[Required]
public string Name
{
get;
set;
}
}
public class TemplateFieldInstance
{
public int Id
{
get;
set;
}
public bool IsRequired
{
get;
set;
}
[Required]
public virtual TemplateField Field
{
get;
set;
}
[Required]
public virtual Template Template
{
get;
set;
}
}
public class Template
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public virtual ICollection<TemplateFieldInstance> Instances
{
get;
set;
}
}
So essentially; a Template can have many TemplateField and a TemplateField can have many Template.
I believe I could just add a navigation property in the form of a collection of Template items on the TemplateField entity and have EF manage the link entity, but I need to store some additional information around the relationship, hence the IsRequired property on TemplateFieldInstance.
The actual issue I'm having is when updating a Template. I'm using code similar to the following:
var template = ... // The updated template.
using (var context = new ExampleContext())
{
// LoadedTemplates is just Templates with an Include for the child Instances.
var currentTemplate = context.LoadedTemplates.Single(t => t.Id == template.Id);
currentTemplate.Instances = template.Instances;
context.Entry(currentTemplate).CurrentValues.SetValues(template);
context.SaveChanges();
}
However; if I try and update a Template to - for example - remove one of the TemplateFieldInstance entities, it this throws an exception (with an inner exception) which states:
A relationship from the 'TemplateFieldInstance_Template'
AssociationSet is in the 'Deleted' state. Given multiplicity
constraints, a corresponding 'TemplateFieldInstance_Template_Source'
must also in the 'Deleted' state.
After doing some research, it sounds like this is because EF has essentially marked the TemplateFieldInstance foreign key to the Template as being null and then tried to save it, which would violate the Required constraint.
I'm very new to Entity Framework, so this is all a bit of a journey of discovery for me, so I'm fully anticipating there being errors in my approach or how I'm doing the update!
Thanks in advance.
You must map the relationships in your model as two one-to-many relationships. The additional field in the link table makes it impossible to create a many-to-many relationship. I would also recommend to use a composite key in your "link entity" TemplateFieldInstance where both components are foreign keys to the other entities. This ensures in the database that you can only have one row for a unique combination of a template field and a template and comes closest to the idea of a "many-to-many link table with additional data":
public class TemplateField
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<TemplateFieldInstance> Instances { get; set; }
}
public class TemplateFieldInstance
{
[Key, Column(Order = 0)]
public int FieldId { get; set; }
[Key, Column(Order = 1)]
public int TemplateId { get; set; }
public bool IsRequired { get; set; }
public virtual TemplateField Field { get; set; }
public virtual Template Template { get; set; }
}
public class Template
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TemplateFieldInstance> Instances { get; set; }
}
EF naming conventions will detect the FK relations in this model if you use the property names above.
More details about such a model type are here: https://stackoverflow.com/a/7053393/270591
Your approach to update the template is not correct: context.Entry(currentTemplate).CurrentValues.SetValues(template); will only update the scalar fields of the template, not the navigation properties nor will it add or remove any new or deleted child entities of the parent entity. Unfortunately updating detached object graphs doesn't work that easy and you have to write a lot more code, something like this:
var template = ... // The updated template.
using (var context = new ExampleContext())
{
// LoadedTemplates is just Templates with an Include for the child Instances.
var currentTemplate = context.LoadedTemplates
.Single(t => t.Id == template.Id);
context.Entry(currentTemplate).CurrentValues.SetValues(template);
foreach (var currentInstance in currentTemplate.Instances.ToList())
if (!template.Instances.Any(i => i.Id == currentInstance.Id))
context.TemplateFieldInstances.Remove(currentInstance); // DELETE
foreach (var instance in template.Instances)
{
var currentInstance = currentTemplate.Instances
.SingleOrDefault(i => i.Id == instance.Id);
if (currentInstance != null)
context.Entry(currentInstance).CurrentValues.SetValues(instance);
// UPDATE
else
currentTemplate.Instances.Add(instance); // INSERT
}
context.SaveChanges();
}
A similar example with more comments what is happening is here: https://stackoverflow.com/a/5540956/270591

MVC 3 EF 4.1 dbContext - Deleting one-to-many data object with non-nullable foreign-key relation

I am using MVC 3, EF 4.1, and dbContext. I need to know how to delete an entity in one-to-many relation with a non-nullable foreign-key.
When I Remove the child entity and execute SaveChanges I get the error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
From other posts, I understand that using Remove(entity) marks the entity for delete. During SaveChanges, EF sets the the foreign-key to Null and the above error occurs.
I have found some posts that use DeleteObject on the child entity rather than Remove; however, the DeleteObject approach seems to have been dropped because of addition to dbContext and DbSet.
I have found posts that suggest modifying the EDMX foreign-key relation to be Nullable. Modifying the EDMX is fine, but whenever an Update Model for Database is done, these changes get nuked and must be reapplied. Not optimal.
Another post suggested creating a proxy entity with the foreign-key relations set to Nullable but I do not understand that approach. It seems to suffer from the same issue as modifying the EDMX in that the context gets automatically updated when changes to the EDMX are saved.
My simplified model is:
public partial class User
{
public User()
{
this.UserContacts = new HashSet<UserContact>();
}
public long userId { get; set; }
public string userEmail { get; set; }
public string userPassword { get; set; }
public string userFirstName { get; set; }
public string userLastName { get; set; }
. . .
public virtual Country Country { get; set; }
public virtual State State { get; set; }
public virtual ICollection<UserContact> UserContacts { get; set; }
}
}
public partial class UserContact
{
public long userContactId { get; set; }
public long userContactUserId { get; set; }
public long userContactTypeId { get; set; }
public string userContactData { get; set; }
public virtual ContactType ContactType { get; set; }
public virtual User User { get; set; }
}
The userContactUserId and userContactTypeId are required foreign-keys.
In the dbContext container both Users and UserContact are DbSet.
I have a ViewModel for the User and a ViewModel for UserContact as follows
public class UserContactViewModel
{
[HiddenInput]
public long UserContactId { get; set; }
[HiddenInput]
public long UserContactUserId { get; set; }
[Display(Name = "Contact")]
[Required]
public string ContactData { get; set; }
[Required]
public long ContactType { get; set; }
[HiddenInput]
public bool isDeleted { get; set; }
}
public class MyProfileViewModel
{
[HiddenInput]
public long UserId { get; set; }
[Required]
[Display(Name = "First Name")]
[StringLength(100)]
public string FirstName { get; set; }
[Required]
[StringLength(100)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
....
public IEnumerable<UserContactViewModel> Contacts { get; set; }
}
When saving changes to the user profile, I loop over the list of UserContactViewModel entities to determine which have been added, modified, or deleted.
foreach (var c in model.Contacts)
{
UserContact uc = usr.UserContacts.Single(con => con.userContactId == c.UserContactId);
if (uc != null)
{
if (c.isDeleted == true) // Deleted UserContact
{
ctx.UserContacts.Remove(uc); // Remove doesn't work
}
else // Modified UserContact
{
uc.userContactData = c.ContactData;
uc.userContactTypeId = c.ContactType;
ctx.Entry(uc).State = EntityState.Modified;
}
}
else // New UserContact
{
usr.UserContacts.Add(new UserContact { userContactUserId = model.UserId, userContactData = c.ContactData, userContactTypeId = c.ContactType });
}
}
I'd appreciate any help.
I managed to solve the problem as follows:
First, I was able to fetch the ObjectContext by casting my DbContext (eg "ctx") to an IObjectContextAdapter and then obtaining reference to the ObjectContext.
Next, I simply called the DeleteObject method passing the UserContact record to be deleted.
When SaveChanges gets the deletes in the database happen as expected.
if (c.isDeleted == true) // Deleted UserContact
{
ObjectContext oc = ((IObjectContextAdapter)ctx).ObjectContext;
oc.DeleteObject(uc)
}
Here is a snippet of the relevant code:
foreach (var c in model.Contacts)
{
UserContact uc = null;
if (c.UserContactId != 0)
{
uc = ctx.UserContacts.Find(c.UserContactId);
}
if (uc != null)
{
if (c.isDeleted == true) // Deleted UserContact
{
ObjectContext oc = ((IObjectContextAdapter)ctx).ObjectContext;
oc.DeleteObject(uc);
}
else // Modified UserContact
{
uc.userContactData = c.ContactData;
uc.userContactTypeId = c.ContactType;
ctx.Entry(uc).State = EntityState.Modified;
}
}
else // New UserContact
{
usr.UserContacts.Add(new UserContact { userContactData = c.ContactData, userContactTypeId = c.ContactType });
}
}
ctx.Entry(usr).State = EntityState.Modified;
ctx.SaveChanges();
Hope this helps someone in future.
I solved the same problem following the section "Cascade Delete Rules on Relationships" at the MSDN guide page here http://msdn.microsoft.com/en-us/library/bb738695.aspx
Hope tu be helpfull :D
well, implement your own ICollection and mark those child objects for deletion along with removing it. And then, in your own SaveChanges method override, delete those objects.
You can configure the relation to cascade ... this will propagate the delete to dependent entities.
But it's very dangerous :)
I prefer setting a flag in the row that prevents the data tier from including it in future queries, most applications do not need physical delete (and will be a chance to undo).