Persist database changes with asp.net MVC - c#

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?

Related

How Should I Set Navigation Properties Before Inserting Into The Database?

Note: I asked a similar question yesterday, but I've since moved past that problem into another issue. Although it's very closely related, I think it's best expressed in a separate question.
I have three models: Account, AccountType, and Person. I want to make a single form page, through which a new Account, with a specific AccountType, and with specific Person information could be POSTed to the database.
public class AccountType
{
[Key]
public int AccountTypeID { get; set; }
[Required]
public string AccountTypeName { get; set; }
}
public class Person
{
[Key]
public int PersonID { get; set; }
// Bunch of properties not relevant to the question here...
}
public class Account
{
[Key]
public int AccountID { get; set; }
[ForeignKey("AccountType")]
public int AccountTypeID { get; set; }
[ForeignKey("Person")]
public int PersonID { get; set; }
// A few properties here...
public virtual AccountType AccountType { get; set; }
public virtual Person Person { get; set; }
}
Since creating a new account requires me to insert into the account as well as the person table, I created a view model for both of these models:
public class Register
{
public Account Account { get; set; }
public Person Person { get; set; }
}
In the Register view, I simply bound the properties from the Account and Person models to form fields. I also used a ViewBag to display a list of AccountTypes in a dropdown.
The part that I don't understand is in the POST controller:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Register(Register Register)
{
if (ModelState.IsValid) {
_db.Accounts.Add(Register.Account);
_db.SaveChanges();
return View(Register);
}
// do something else
}
The ModelState check passes successfully, after having commented out the Nullable setting in the project file. However, Register.Account has null properties:
All the values that I bound in the Register view get set correctly, but I did not bind the navigation properties (Register.Account.AccountType and Register.Account.Person) to anything, since I did not know what to do with them.
Now, I can't insert into the database with the above code, because I get a Person foreign key constraint error. It seems that Register.Account cannot have null values for its Person or AccountType navigation properties. Apparently, they must be set (or, at least, the Person property must be).
I know that I can set these navigation properties manually in the controller. For Person, I can write something like this before saving to the DB: Register.Account.Person = Register.Person, and I can likewise come up with something for AccountTypes to give it its proper value. I've tested this, and it does insert into the database.
But, this doesn't strike me as the right approach. It seems to me that there must be a better, more proper way of clarifying the model or table relationships to .NET before inserting into the database.
Does anybody know a better way?
P.S.: I'm using .NET 6.
Per Jeremy's suggestion, I solved this problem by creating a new View Model, which only included the properties that I needed to bind, and omitting any navigation properties that weren't necessary to insert into the database successfully.

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

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.

The instance of entity type 'XY' cannot be tracked because another instance with the same key value for {'XId', 'YId'} is already being tracked

Sorry to raise this one again - I see it plenty of times on here but I am still puzzled about it in my case, especially because I seem to be able to 'overcome' it, but I'm not sure I like how, and I would love to understand why.
I have a many to many relationship between Staff and Departments, with the StaffDepartment table being set up with a composite key like so:
modelBuilder.Entity<StaffDepartment>()
.HasKey(sd => new { sd.StaffId, sd.DepartmentId });
I suspect that must be something to do with the exception I sometimes get:
InvalidOperationException: The instance of entity type 'StaffDepartment' cannot be tracked because another instance with the same key value for {'StaffId', 'DepartmentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I say sometimes because, strangely, if I change the department in my staff details page and update, it goes through fine, but when I leave it the same (maybe just changing the staff name for example) I receive this exception.
I tried implementing the various suggestions on this
similar but apparently different SO discussion
and all sorts of ways of tinkering with my repository update method, and strangely enough this is the only way I have got it working:
public async Task UpdateStaff(StaffDto staff)
{
var staffToUpdate = await _db.Staff
.Include(d => d.Departments)
.ThenInclude(d => d.Department)
.SingleOrDefaultAsync(s => s.Id == staff.Id);
// Without either these two line I get the exception
_db.StaffDepartment.RemoveRange(staffToUpdate.Departments);
await _db.SaveChangesAsync();
_mapper.Map(staff, staffToUpdate);
await _db.SaveChangesAsync();
}
So it seems strange that I should have to call SaveChangesAsync() twice in same method. What is actually happening there? And how would I achieve the suggestion in the error When attaching existing entities, ensure that only one entity instance with a given key value is attached without doing so.
Entity classes look like so:
public class Staff
{
public int Id { get; set; }
...
public ICollection<StaffDepartment> Departments { get; set; }
}
public class Department
{
public int DepartmentId { get; set; }
...
public ICollection<StaffDepartment> Staff { get; set; }
}
public class StaffDepartment
{
public int StaffId { get; set; }
public Staff Staff { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
}
And the staffDto that comes back from the domain etc. is simply:
public class StaffDto
{
public int Id { get; set; }
...
public List<DepartmentDto> Departments { get; set; }
}
The repositories are all registered as transient i.e.
services.AddTransient<ICPDRepository, CPDRepository>();

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();
}
}

Fluent NHibernate Relation Without Foreign Key

I'm trying so simplify my problem here, but basically I'm trying to map 2 entities however i don't have a Foreign Key in the database set, since the column could be null. When I try to do an insert on the parent, I'm getting the following error:
object references an unsaved transient instance - save the transient
instance before flushing or set cascade action for the property to
something that would make it autosave.
This is what I have so far:
My entities
public class DocumentDraft
{
public virtual Guid Id { get; set; }
public virtual string Subject { get; set; }
public virtual string ReferenceNo { get; set;}
public virtual DocumentType DocumentType { get; set; }
}
public class DocumentType
{
public virtual short Id { get; set; }
public virtual string Description { get; set; }
}
Mapping
public class DocumentDraftMap : ClassMap<DocumentDraft>
{
public DocumentDraft()
{
// other mappings ...
References(x => x.DocumentType)
.Columns("DocumentTypeId")
.Nullable()
.Not.LazyLoad()
.NotFound.Ignore(); // <-- added this since the value could be null and it throws an error
}
}
I tried specifying Cascade.None() in the mapping, but I'm getting the same result. Basically what happens is that a null value is attempted at being inserted in the DocumentType, and I don't want this (I want to insert null in the parent table, but I don't want to touch the child tables at all, I don't want this to cascade).
I've also tried: .Not.Insert(), but that didn't work either.
I'd appreciate it if someone could help me out on this one.
I guess the property DocumentType is not really null when saving.
It seems there is an instance and without Cascade.All() on the reference it can not be saved.

Categories

Resources