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

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.

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?

Web Api, Update DB, connection reset 200ok

I have asp.net web api application. I have the table Companies in the databse which have two fields: id and description. Recently I've updated the database and added a new column called CustomerID. After that when I am trying to call getCompanies
private readonly BackendContext _context;
public CompaniesController(BackendContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public IEnumerable<Company> GetCompanies()
{
return _context.Companies;
}
I get
I think the controller tries to return the old companies model but can't achieve it because it doesnt exist now but I don't know how to fix this though the controller should return the updated model. Maybe I should somehow rebuild the app to make it use the updated version?
Additional code:
Context
public class BackendContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<IdentityUser>//DbContext
{
public BackendContext(DbContextOptions<BackendContext> options) : base(options) { }
public DbSet<Company> Companies { get; set; }
public DbSet<CompanyToProduct> CompanyToProducts { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Vendor> Vendors { get; set; }
public DbSet<VendorToProduct> VendorToProducts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceItem> InvoiceItems { get; set; }
}
Model
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<CompanyToProduct> CompaniesToProducts { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
UPDATE
I've added some values to the table and I got the response of the first company:
[{"id":1,"name":"Google","description":"free food","customerID":6,"customer":null,"companiesToProducts":null,"invoices":null}
BUT I also got the fields which is not specified in the table: customer, companiesToProducts,invoices. Invoices and companiesToProducts are tables in my database and I don't know what is customer referred to. I should also mention that these tables are connected by foreign key.
UPDATE
Error:
Based on the comments on the question above, it sounds like the related tables are all trying to serialize and the overall process is failing likely due to circular references in the object graph. This comment above in particular hints at a solution:
I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices
While it's convenient to just return directly from the data context, this has the added side-effect of coupling the API with the database (and with the data access framework, which appears to be the issue here). In API design in general it's always a good idea to explicitly define the "shape" of that API. The fields to return, etc.
Project your result into an explicitly defined shape and return only what you want to return:
var result = _context.Companies
.Select(c => new
{
c.ID,
c.Name,
c.Description,
c.CustomerID
})
.ToList();
This defines specifically what you want to return, fetches only that information from the backing data, materializes it into an in-memory list, and finally then returns it through the API.
There is a potential downside to this, however. Because now we also need to change the return type of your API method. There are a couple options there, such as returning a generic response object or creating a view model which closely approximates your already existing model and starts to feel like duplication.
As with just about anything, it's a balance. Too far in any one direction and that direction starts to become a problem. Personally I often go the route of defining a view model to return:
public class CompanyViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
}
and returning that:
return _context.Companies
.Select(c => new CompanyViewModel
{
ID = c.ID,
Name = c.Name,
Description = c.Description,
CustomID = c.CustomerID
})
.ToList();
But the reason I normally do this is because I normally work in an environment where the web application is just one application attached to a common shared business domain, so the view models don't feel like code duplication. They're in a separate project, often take a different shape than the backing data objects, etc. But if your domain models are already in your web project and that's the only project you have, there's a strong desire to want to return those.
Another option when that's the case could be to universally set your JSON serialization to ignore circular references:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore );
But do keep in mind that this still couples your API to your DB models. Maybe that's okay in this project, but if you ever add a column to your DB that you don't want users to see then it becomes an issue. As with anything, you have options.

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

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.

MVC3 + Entity Framework, updating selective fields in an entity

Assume I have Entity Framework setup correctly, and the POCO classes are have the proper relationships, etc.
<!-- language: c# -->
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string SomeFieldIDontWantUsersToEdit { get; set; } // there are a lot of these
public IList<Address> Addresses { get; set; }
}
public class Address
{
public string EntireAdressInOneLine { get; set; }
}
When I display things to the user, I only want to show FirstName and LastName, and the addresses. That works and the view is functioning properly.
This is my view:
<!-- language: c# -->
#using (Html.BeginForm(actionName: null, controllerName: null, method: FormMethod.Post))
{
#Html.TextBoxFor(model => Model.Person.FirstName)
#Html.TextBoxFor(model => Model.Person.LastName)
#Html.HiddenFor(model => Model.Person.Id)
}
But when it comes time to Update this entry, I'm lost. If this is the action for update:
<!-- language: c# -->
[HttpPost]
public ActionResult UpdatePerson(int personId, Person updatedPerson)
{
if (ModelState.IsValid)
{
}
}
When I submit the form, the model binder will pickup that I want a Person object. Cool, that works. Of course, SomeFieldIDontWantUsersToEdit will be null since it wasn't included in the view -- I understand that part.
My question is, is there some way to tell EF that I only want to update certain properties in this entity, and use whatever value was there already for all other properties.
I want the original values of SomeFieldIDontWantUsersToEdit to say the same, but want to allow the user to edit First and Last name. If it were only 1 or 2 fields, sure, I can do it manually, but it's a lot more fields in the actual code.
Maybe a better question is, what is the proper/recommended way of doing this?
Why not actually create a ViewModel, such as:
public class PersonalInfoModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And just have your Action receive that type:
[HttpPost]
public ActionResult UpdatePerson(PersonalInfoModel updatedPerson)
{
if (ModelState.IsValid)
{
}
}
In this scenario, you should go to your context, load the real entity using PersonalInfoModel.Id, and just update the two properties you want to update.
It isn't mandatory that you use your real entities as Models, you can create your own representations of information (in several cases even recommended, instead of using the actual entities).
Use Automapper if you don't want to do the "mapping" by hand.

Categories

Resources