relationship can't be changed , foreign-keys are non-nullable - c#

I'm running an (old) MVC3 web app on an MSSQL DB, utilizing EF 6.0 code first and the repository pattern.
the system has been running in production for the last 7 years (EF was updated about 1 year ago).
I've been encountering a very strange exception in 1 particular area of the system.
when attempting to create or update certain entities, I encounter the following exception:
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.
here's one of the problematic entities:
public class BeaconAppErrorLog
{
[Key]
public int Id { get; set; }
public int EntityId { get; set; }
public string RawJson { get; set; }
public DateTime SavedAt { get; set; }
public int? EmployeeId { get; set; }
[ForeignKey("EmployeeId")]
public Employee Employee { get; set; }
public int? ContainerId { get; set; }
[ForeignKey("ContainerId")]
public Container Container { get; set; }
public int? DailyTrackId { get; set; }
[ForeignKey("DailyTrackId")]
public DailyTrack DailyTrack { get; set; }
public int? ClientId { get; set; }
[ForeignKey("ClientId")]
public Client Client { get; set; }
public string Error { get; set; }
}
here's the code for creation & saving :
DataContext.BeaconAppErrorLogs.Add(new BeaconAppErrorLog()
{
EntityId = 2,
SavedAt = DateTime.Now,
EmployeeId = activity.EmployeeId,
DailyTrackId = activity.DailytrackId,
Error = error
});
DataContext.SaveChanges();
the 'EmployeeId' and 'DailyTrackId' fields are foreign keys, they receive
valid values (i.e id's that correspond to the respective entity and exist in the DB)
an almost identical code is written hundreds of time throughout the application - and is functioning properly (even for the exact same entity).
I have no idea what is going on and why, and so far all of the solutions I've attempted did not work.
Your help will be greatly appreciated!
Thanks,
Nir

turns out the solution was someplace else entirely.
as #gregH and #tschmit007 have pointed out, EF tracks changes in entities.
once we've started moving the 'DataContext.SaveChanges()' command higher up the execution chain we've found that changes being made to one of the entities higher up were causing the issue.
the actual problem was a modification of a child collection property to the DailyTrack entity. the modification consisted of filtering the collection data (a LINQ WHERE Claus performed on the collection).
thanks, everyone for your help.

Related

Persist database changes with asp.net MVC

I am a beginner in the field of IT, more particularly in the C # language, I use the ASP.Net MVC framework.
I am working on an online restaurant web application, I will be the administrator of the site, so it is I who manage any addition of restaurants or menus.
For the moment I am working on my two classes Restaurant and Menu, they are linked by a relation 1:n and I use the database first approach.
So my ViewModel Restaurant-Edit has a collection of Menu while Menu-Edit has an instance of Restaurant in its code.
namespace RestaurantProjet3.Models
{
public class MenuEditee
{
public int IdMenu { get; set; }
public int fk_Resto { get; set; }
public string NomPlat { get; set; }
public int Prix { get; set; }
public string Description { get; set; }
public string Categories { get; set; }
public byte?[] Photos { get; set; }
public virtual Restaurant Restaurant { get; set; }
}
}
Here, the problem that arises is the following, I worked on the page of edition of the menus of my restaurants whose here is the code
[HttpPost]
public ActionResult Edit_Menu(MenuEditee menuEdit)
{
if(!ModelState.IsValid)
{
return View(menuEdit);
}
Menu menuBd = contexteEF.Menu.Single(m => m.IdMenu == menuEdit.IdMenu);
menuBd = AutoMapper.Mapper.Map<MenuEditee,Menu>(menuEdit,menuBd);
contexteEF.SaveChanges();
return null;
When I want to replace the data contained in my comic by those contained in my ViewModel, an exception occurs telling me that
"System.InvalidOperationException: Operation failed: unable to modify
the relationship because one or all of the foreign key properties do
not accept null values. When a modification is made to a relationship,
a null value is assigned to the associated foreign key property. If
the foreign key does not support null values, a new relationship must
be defined, the non-null value must be assigned to the foreign key
property or the unassociated object must be deleted"
and I noticed when I put a breakpoint in my code, the restaurant instance contained in my Menu-Edit ViewModel returns a null value.
This is why when mapping its content in Menu which is in my database, one of the attributes of Menu-Edit is returned empty (the restaurant instance) which creates an exception.
What should i do to avoid errors and finally be able to persist my changes in the database?

EF issue adding entity with list of child entities

I've been trying to update an entity that will have a list of child entities but for some reason I keep getting the same error and I don't know what I am doing wrong. Any help will be appreciated. The error is:
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.
Bellow is what I am trying:
public class Parent
{
[Key]
public int Id { get; set; }
[MaxLength(25)]
public string Name { get; set; }
[MaxLength(25)]
public string FullName { get; set; }
public virtual ICollection<Child> ChildList { get; set; } = new HashSet<Child>();
}
public class Child
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(12)]
public string Number { get; set; }
[IgnoreMap]
public Parent Parent { get; set; }
public int ParentId { get; set; }
}
public async Task<Parent> UpdateParent(Parent Parent)
{
Parent originalParent = await GetById(Parent.Id, c => c.ChildList);
Mapper.Map(Parent, originalParent);
await DbContext.SaveChangesAsync();
return originalParent;
}
Also I have noticed that if I don't use automapper and I just map the properties manually it works:
private static void MapParentProperties(Parent parent, Parent originalParent)
{
originalParent.Name = parent.Name;
originalParent.FullName = parent.FullName;
}
I haven't used Automapper with EF for a while due to past issues similar to this and lazy load trigger, but I've been digging around into it lately given it seems to have better IQueryable support. I'm not sure if it's still the case, but Automapper had a limitation that mapped child collections would be replaced by default, and with EF this could be treated as new entities being marked for insertion or existing child entities getting "bumped out" of the parent and EF flagging that their FK could not be #nulled. The solution was to tell Automapper to keep the destination collection, then handle those mappings individually. (using the child maps) There might be some additional work needed to handle inserts & deletes in the modified collection.
Something that may help:
http://bzbetty.blogspot.com/2012/06/updating-collections-using-automapper.html

Duplicate entities when setting state within a collection in Entity Framework

I have a solution which uses Entity Framework to insert invoices to a database table. These invoices reference an order, which in turn also references an order item collection.
In this instance I am trying to add an order to the database, however the code is inside a new DbContext and so I need to attach the order and order items to the context, as these already exist in the database and shouldn't be re-added.
I've cut down the model properties for the sake of demonstration:
public class Invoice {
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int InvoiceId { get; set; }
public string OrderNumber { get; set; }
...
public virtual List<InvoiceLineItem> LineItems { get; set; }
}
public class InvoiceLineItem {
[Key]
public int Id { get; set; }
...
public ShopifyOrderItem { get; set; }
}
public class ShopifyOrder {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
public int OrderNumber { get; set; }
...
public OrderInvoiceStatus InvoiceStatus { get; set; }
public virtual List<ShopifyOrderItem> OrderItems { get; set; }
}
public class ShopifyOrderItem {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
...
[Required]
public virtual ShopifyOrder ShopifyOrder { get; set; }
}
In the invoice engine, I'm running the following code for each invoice to add it to the database:
ShopifyOrder order = await db.ShopifyOrders.SingleOrDefaultAsync(x => x.OrderNumber.ToString() == inv.OrderNumber);
if (order != null) {
// Attach marketplace entity to the invoice to avoid duplicate primary key exceptions
db.Marketplaces.Attach(inv.Marketplace);
db.Invoices.Add(inv);
order.InvoiceStatus = OrderInvoiceStatus.InProgress;
}
I've tried a number of methods to try and attach the states, however they all throw errors.
inv.LineItems.ForEach(li => {
db.Entry(li).State = EntityState.Unchanged;
db.Entry(li.ShopifyOrderItem).State = EntityState.Unchanged;
db.Entry(li.ShopifyOrderItem.ShopifyOrder).State = EntityState.Modified;
});
The above code returns the following error on save:
EntityFramework: Saving or accepting changes failed because more than one entity of type 'TorroModels.ShopifyOrder' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model.
What is the best way to attach the LineItems/ShopifyOrderItems without trying to attach the ShopifyOrder connected property multiple times?
Sorry to say but it seems that you need to follow the best practice first when constructing a relationship. You may follow this link :
http://www.entityframeworktutorial.net/entity-relationships.aspx
In short :
Avoid using only "Id" in every entity, or you can use attributes to map between the physical name and the property name
It seems that you have circular references here, so maybe you could simplify it first
Next, you can read this link :
http://www.entityframeworktutorial.net/EntityFramework5/attach-disconnected-entity-graph.aspx
if you need to know more about what's the best practice of attaching entities, but in my opinion, just don't abuse this feature, because using normal CRUD should be sufficient most of the time.
I'm sorry I cannot help you more than this, because of lack of information I may need, and with my reputation I still cannot comment directly in your post to ask for it.

Why EF navigation property return null?

I have two model
1)
public class Indicator
{
public long ID { get; set; }
public string Name { get; set; }
public int MaxPoint { get; set; }
public string Comment { get; set; }
public DateTime DateChanged { get; set; }
public DateTime DateCreated { get; set; }
public virtual IList<CalculationType> CalculationTypes { get; set; }
public virtual IList<TestEntity> TestEntitys { get; set; }
public virtual IndicatorGroup IndicatorGroup { get; set; }
}
2)
public class CalculationType
{
public long ID { get; set; }
public string UnitName { get; set; }
public int Point { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateChanged { get; set; }
public virtual Indicator Indicator { get; set; }
public virtual IList<Сalculation> Calculations { get; set; }
}
I executing this code
var indicator = DataContext.Indicators.FirstOrDefault(i => i.ID == indicatorID);
var test = DataContext.CalculationTypes.FirstOrDefault();
first line return null on navigation property CalculationTypes
Second line return empty collection. Why?
UPDATE
snapshot database
project link https://github.com/wkololo4ever/Stankin
added Calculation
public class Сalculation
{
public long ID { get; set; }
public virtual CalculationType CalculationType { get; set; }
public virtual ApplicationUser Creator { get; set; }
}
1) Is Lazy Loading enabled? If not, you need to explicitly load your navigation properties with the '.Include' syntax.
2) Are you sure EF should be able to detect that relation? Did you use Code First or Database First?
Edit: 3) Are you sure there is data in your database and that the foreign key from Indicator to IndicatorGroup has a value for that specific record? I am saying this because the value "null" is valid if there is simply no data.
P.S. If you do not see a foreign key on Indicator called "IndicatorGroupId", there might be an "IndicatorId" on the table "IndicatorGroup", in which case - going from the names you provided - your database is misconfigured and you will need to use fluent syntax or data attributes to instruct EF on how to make the foreign keys.
Try this:
DbContext.Configuration.ProxyCreationEnabled = true;
DbContext.Configuration.LazyLoadingEnabled = true;
If DbContext.Configuration.ProxyCreationEnabled is set to false, DbContext will not load child objects for some parent object unless Include method is called on parent object. Setting DbContext.Configuration.LazyLoadingEnabled to true or false will have no impact on its behaviours.
If DbContext.Configuration.ProxyCreationEnabled is set to true, child objects will be loaded automatically, and DbContext.Configuration.LazyLoadingEnabled value will control when child objects are loaded.
I think this is problem:
Edit: 3) Are you sure there is data in your database and that the
foreign key from Indicator to IndicatorGroup has a value for that
specific record? I am saying this because the value "null" is valid if
there is simply no data.
P.S. If you do not see a foreign key on Indicator called
"IndicatorGroupId", there might be an "IndicatorId" on the table
"IndicatorGroup", in which case - going from the names you provided -
your database is misconfigured and you will need to use fluent syntax
or data attributes to instruct EF on how to make the foreign keys.
Try to this and make sure foreign key is corrected.
public class CalculationType
{
public long ID { get; set; }
public string UnitName { get; set; }
public int Point { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateChanged { get; set; }
[ForeignKey("IndicatorID")]
public string IndicatorId { get; set; } //this is the foreign key, i saw in your database is: Indicator_ID, avoid this, rename it to IndicatorID or IndicatorId
public virtual Indicator Indicator { get; set; }
public virtual IList<Сalculation> Calculations { get; set; }
}
Same behavior, but different root cause than selected answer:
Navigation property can also be null if you turned off myContext.Configuration.AutoDetectChangesEnabled
Very obvious, but this got me when I was implementing some performance improvments.
Check this out: Navigation Property With Code First . It mentions about why navigation property is null and the solutions of it.
By default, navigation properties are null, they are not loaded by
default. For loading navigation property, we use “include” method of
IQuearable and this type of loading is called Eager loading.
Eager loading: It is a process by which a query for one type of entity
loads the related entities as a part of query and it is achieved by
“include” method of IQueryable.
I experienced this issue, where navigation properites were not loaded, even when the Include statement was present.
The problem was caused by string-comparison differences between SQL Server and EF6 using .NET. I was using a VARCHAR(50) field as the primary key in my customers table and also, as a foreign key field in my audit_issues table. What I did not realize was that my keys in the customers table had two additional white space characters on the end; these characters were not present in my audit_issues table.
However, SQL Server will automatically pad whitespace for string comparisons. This applies for WHERE and JOIN clauses, as well as for checks on FOREIGN KEY constraints. I.e. the database was telling me string were equivalent and the constraint passed. Therefore I assumed that they actually were exactly equal. But that was false. DATALENGTH of one field = 10, while the DATALENGTH of the other = 8.
EF6 would correctly compose the SQL query to pull the foreign key related fields and I would see them both in the generated Sql query and in the results. However, EF6 would silently fail when loading the Navigation Properties because .NET does not consider those strings equal. Watch out for whitespace in string-type foreign key fields!.
This article helped me.
In sum :
Install-Package Microsoft.EntityFrameworkCore.Proxies
In Startup.cs
using Microsoft.EntityFrameworkCore;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<MyDbContext>(builder =>
{
builder.UseLazyLoadingProxies(); // <-- add this
}, ServiceLifetime.Singleton);
This is a variant of Keytrap's answer. Using .NET 6 and EF Core 6, I created a ContextPartials.cs for any custom configurations that I don't want EF's Scaffold command to overwrite:
Required Package:
Install-Package Microsoft.EntityFrameworkCore.Proxies
Code (ContextPartials.cs):
// NOTE: I am not using the new file-scoped namespace on purpose
namespace DataAccess.Models.MyDatabase
{
// NOTE: This is a partial outside of the generated file from Scaffold-DbContext
public partial class MyDatabaseContext
{
// NOTE: This enables foreign key tables to become accessible
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseLazyLoadingProxies();
}
}

Many-to-many relationships using EF Code First

I have two classes defined as such:
public class Questionnaire
{
public int QuestionnaireID { get; set; }
public string Title { get; set; }
public bool Active { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<Vendor> Vendors { get; set; }
}
public class Vendor
{
public int VendorID { get; set; }
public string VendorName { get; set; }
public virtual ICollection<Questionnaire> OpenQuestionnaires { get; set; }
public virtual ICollection<Questionnaire> SubmittedQuestionnaires { get; set; }
public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}
I beleive this is the correct way to establish a many-to-many relationship between these classes, and when the project is built, I would expect three tables to be created.
However, when I attempt to to relate one Questionnaire to two different Vendors, I receive the following error when attempting to save the changes (context.SaveChanges()):
*Multiplicity constraint violated. The role 'Vendor_OpenQuestionnaires_Source' of the relationship 'QuestionnaireApp.Models.Vendor_OpenQuestionnaires' has multiplicity 1 or 0..1.*
If I assign a Questionnaire to only one Vendor, save the changes and then assign it to another and again save changes I no longer get the error; however the Questionaire is then related only to the last Vendor to which it was assigned, indicating that (at best) there is a one-to-many relationship being created.
I'm hoping that there is something wrong with the way I'm declaring the many-to-many relationship between these classes, or perhaps there is something I need to add to the context class to "encourage" the relationsip, but perhaps many-to-many relationships like this are not supported, or cannot be created using "Code First"?
Thank you for your time,
Jason
If you don't have any Fluent API code your expected mapping relies on EF Code First conventions. The convention which you expect to kick in here is the AssociationInverseDiscoveryConvention. Now if you look in Intellisense (and probably also documentation) it says about this convention:
Convention to detect navigation properties to be inverses of each
other when only one pair of navigation properties exists between the
related types.
Now, that's the problem: You don't have only "one pair" of navigation properties between Questionnaire and Vendor. You have two collections in Vendor refering to Questionnaire and one collection in Questionnaire refering to Vendor. The result is that this convention doesn't get applied and EF maps actually three one-to-many relationships with only one end exposed as navigation property in the model.
Moreover the mapping you want to achieve is not possible with your model: You cannot map the one end Questionnaire.Vendors to the two ends Vendor.OpenQuestionnaires and Vendor.SubmittedQuestionnaires.
One workaround is to change your model the following way:
public class Vendor
{
public int VendorID { get; set; }
public string VendorName { get; set; }
[NotMapped]
public IEnumerable<Questionnaire> OpenQuestionnaires
{
get { return Questionnaires.Where(q => q.IsActive); }
}
[NotMapped]
public IEnumerable<Questionnaire> SubmittedQuestionnaires
{
get { return Questionnaires.Where(q => !q.IsActive); }
}
public virtual ICollection<Questionnaire> Questionnaires { get; set; }
public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}
Now Vendor.Questionnaires is mapped to Questionnaire.Vendors (AssociationInverseDiscoveryConvention should detect this) and the helper properties OpenQuestionnaires and SubmittedQuestionnaires allow you to pull out the selected items. (I'm not sure if IsActive is your distinguishing flag. Otherwise you have to introduce some new flag.)
The [NotMapped] attribute is just here to make it explicite. It is probably not necessary because EF won't map IEnumerable collections and readonly properties with only a getter anyway.
Go figure, after an hour or so of searching, I go and find the exact answer 30 seconds after I post my question.
The solution was to add the following to the context class:
modelBuilder.Entity<Vendor>()
.HasMany<Questionnaire>(x => x.OpenQuestionnaires)
.WithMany(x => x.Vendors)
.Map(x =>
{
x.MapLeftKey("vID");
x.MapRightKey("qID");
x.ToTable("VendorQuestionnaires");
});
I found the answer by reading this Stack Overflow post: EF Code First Many-to-Many not working

Categories

Resources