Updating partial entity using Entity Framework (with razor view) - c#

I have an entity:
public class Organization
{
public int OrganizationId { get; set; }
[Required]
[StringLength(160)]
public string Name { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<Team> Teams { get; set; }
public virtual ICollection<ApplicationUser> Persons { get; set; }
}
I have created a OrganizationViewModel for representing data to view as well as creating new entities. I later map it using Automapper before saving changes to database using Entity Framework...
public class OrganizationViewModel
{
public int OrganizationId { get; set; }
[Required]
[StringLength(160)]
public string Name { get; set; }
public List<ApplicationUser> Persons { get; set; }
}
I have created an action method Edit which should only allow user to edit property Name (for this example only, but in reality, it does update many other properties which I have slimmed down for this question)..
The Edit view only has two properties #Html.HiddenFor(model => model.OrganizationId) and #Html.EditorFor(model => model.Name)
Now in [HttpPost]
I got OrganizationViewModel with updated Name property but property Persons remains null, if there are users already present in an organization EF throws an error as it is not expected by the context.
I could have updated the properties I want to update EXPLICITLY in Post method but I don't want to do everything manually. Is there any other way where I can say that just update the entities I want. I tried using Bind's Include and Exclude but it has null for Persons as well.
Help me :)

If you create a view model that contains only the OrganizationId and Name properties that are edited by this view, you can achieve it by executing
db.Entry(db.Organizations
.First(x => OrganizationId == viewModel.OrganizationId))
.CurrentValues.SetValues(yourViewModel);
yourViewModel - can be any object with set of properties that you want to update, so you don't need to repeat your code but you have to have a specific view model for each operation. You may have it even locally for the update method and automap to it but it must have only properties you want to update.

Related

The property 'x' is not a navigation property of entity type 'y'

I'm using EF Core with ASP Core 2.0. Using latest Identity framework. I get this exception on page All.
InvalidOperationException: The property 'User' is not a navigation property of entity type 'Gallery'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names.
ApplicationUser looks like:
public class ApplicationUser : IdentityUser<Guid>
{
public ICollection<Gallery> Galleries { get; set; }
}
Entity Gallery looks like:
public class Gallery
{
public int Id { get; set; }
public Guid UserId { get; set; }
public string Title { get; set; }
public int? ArticleId { get; set; }
public string Photos { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Article Article { get; set; }
public ApplicationUser User { get; set; }
[NotMapped]
public List<string> PhotosList
{
get { return Photos?.Split('|').ToList(); }
set { Photos = string.Join("|", value); }
}
}
Controller for View looks like:
public async Task<IActionResult> All()
{
var databaseContext = db.Galleries.Include(x => x.Article).Include(x => x.User);
return View(await databaseContext.ToListAsync());
}
I have no idea why it dont crash on Article..
Database is up-to-date.
add a ForeignKey attribute
using System.ComponentModel.DataAnnotations.Schema;
...
[ForeignKey("Article")]
public int? ArticleId { get; set; }
[ForeignKey("User")]
public Guid UserId { get; set; }
You can also put the attribute on the navigation property
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
Also, make sure your dbContext inherits from IdentityDbContext<ApplicationUser, ...>
You can run into this if you manually add extra properties to Models.
To troubleshoot it, run SQL Profiler and capture the RAW SQL, execute the SQL against the database and see why the query doesn't work, ie which property 'x' is not a navigation property of entity type 'y'.
Then go to the model and remove the extra property you added manually.
PS: If you don't have a SQL dB you can use another profiler. Alternatively just check the diffs in source control.

Update Entity from ViewModel in MVC using AutoMapper

I have a Supplier.cs Entity and its ViewModel SupplierVm.cs. I am attempting to update an existing Supplier, but I am getting the Yellow Screen of Death (YSOD) with the error message:
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.
I think I know why it is happening, but I'm not sure how to fix it. Here's a screencast of what is happening. I think the reason I'm getting the error is because that relationship is lost when AutoMapper does its thing.
CODE
Here are the Entities that I think are relevant:
public abstract class Business : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
public virtual ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
public class Supplier : Business
{
public virtual ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public class Address : IEntity
{
public Address()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string Area { get; set; }
public string City { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
public class Contact : IEntity
{
public Contact()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
And here is my ViewModel:
public class SupplierVm
{
public SupplierVm()
{
Addresses = new List<AddressVm>();
Contacts = new List<ContactVm>();
PurchaseOrders = new List<PurchaseOrderVm>();
}
public int Id { get; set; }
[Required]
[Display(Name = "Company Name")]
public string Name { get; set; }
[Display(Name = "Tax Number")]
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
[Display(Name = "Status")]
public bool IsDeleted { get; set; }
public IList<AddressVm> Addresses { get; set; }
public IList<ContactVm> Contacts { get; set; }
public IList<PurchaseOrderVm> PurchaseOrders { get; set; }
public string ButtonText => Id != 0 ? "Update Supplier" : "Add Supplier";
}
My AutoMapper mapping configuration is like this:
cfg.CreateMap<Supplier, SupplierVm>();
cfg.CreateMap<SupplierVm, Supplier>()
.ForMember(d => d.Addresses, o => o.UseDestinationValue())
.ForMember(d => d.Contacts, o => o.UseDestinationValue());
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>()
.Ignore(c => c.Business)
.Ignore(c => c.CreatedOn);
cfg.CreateMap<Address, AddressVm>();
cfg.CreateMap<AddressVm, Address>()
.Ignore(a => a.Business)
.Ignore(a => a.CreatedOn);
Finally, here's my SupplierController Edit Method:
[HttpPost]
public ActionResult Edit(SupplierVm supplier)
{
if (!ModelState.IsValid)
return View(supplier);
_supplierService.UpdateSupplier(supplier);
return RedirectToAction("Index");
}
And here's the UpdateSupplier Method on the SupplierService.cs:
public void UpdateSupplier(SupplierVm supplier)
{
var updatedSupplier = _supplierRepository.Find(supplier.Id);
Mapper.Map(supplier, updatedSupplier); // I lose navigational property here
_supplierRepository.Update(updatedSupplier);
_supplierRepository.Save();
}
I've done a load of reading and according to this blog post, what I have should work! I've also read stuff like this but I thought I'd check with readers before ditching AutoMapper for Updating Entities.
The cause
The line ...
Mapper.Map(supplier, updatedSupplier);
... does a lot more than meets the eye.
During the mapping operation, updatedSupplier loads its collections (Addresses, etc) lazily because AutoMapper (AM) accesses them. You can verify this by monitoring SQL statements.
AM replaces these loaded collections by the collections it maps from the view model. This happens despite the UseDestinationValue setting. (Personally, I think this setting is incomprehensible.)
This replacement has some unexpected consequences:
It leaves the original items in the collections attached to the context, but no longer in scope of the method you're in. The items are still in the Local collections (like context.Addresses.Local) but now deprived of their parent, because EF has executed relationship fixup. Their state is Modified.
It attaches the items from the view model to the context in an Added state. After all, they're new to the context. If at this point you'd expect 1 Address in context.Addresses.Local, you'd see 2. But you only see the added items in the debugger.
It's these parent-less 'Modified` items that cause the exception. And if it didn't, the next surprise would have been that you add new items to the database while you only expected updates.
OK, now what?
So how do you fix this?
A. I tried to replay your scenario as closely as possible. For me, one possible fix consisted of two modifications:
Disable lazy loading. I don't know how you would arrange this with your repositories, but somewhere there should be a line like
context.Configuration.LazyLoadingEnabled = false;
Doing this, you'll only have the Added items, not the hidden Modified items.
Mark the Added items as Modified. Again, "somewhere", put lines like
foreach (var addr in updatedSupplier.Addresses)
{
context.Entry(addr).State = System.Data.Entity.EntityState.Modified;
}
... and so on.
B. Another option is to map the view model to new entity objects ...
var updatedSupplier = Mapper.Map<Supplier>(supplier);
... and mark it, and all of its children, as Modified. This is quite "expensive" in terms of updates though, see the next point.
C. A better fix in my opinion is to take AM out of the equation completely and paint the state manually. I'm always wary of using AM for complex mapping scenarios. First, because the mapping itself is defined a long way away from the code where it's used, making code difficult to inspect. But mainly because it brings its own ways of doing things. It's not always clear how it interacts with other delicate operations --like change tracking.
Painting the state is a painstaking procedure. The basis could be a statement like ...
context.Entry(updatedSupplier).CurrentValues.SetValues(supplier);
... which copies supplier's scalar properties to updatedSupplier if their names match. Or you could use AM (after all) to map individual view models to their entity counterparts, but ignoring the navigation properties.
Option C gives you fine-grained control over what gets updated, as you originally intended, instead of the sweeping update of option B. When in doubt, this may help you decide which option to use.
I searched all stackoverflow answers and google searches. Finally i just added 'db.Configuration.LazyLoadingEnabled = false;' line and it worked perfectly for me.
var message = JsonConvert.DeserializeObject<UserMessage>(#"{.....}");
using (var db = new OracleDbContex())
{
db.Configuration.LazyLoadingEnabled = false;
var msguser = Mapper.Map<BAPUSER>(message);
var dbuser = db.BAPUSER.FirstOrDefault(w => w.BAPUSERID == 1111);
Mapper.Map(msguser, dbuser);
// db.Entry(userx).State = EntityState.Modified;
db.SaveChanges();
}
I've gotten this issue many times and is normally this:
The FK Id on the parent reference doesn't match the PK on that FK entity. i.e. If you have an Order table and a OrderStatus table. When you load both into entities, Order has OrderStatusId = 1 and the OrderStatus.Id = 1. If you change OrderStatusId = 2 but do not update OrderStatus.Id to 2, then you'll get this error. To fix it, you either need to load the Id of 2 and update the reference entity or just set the OrderStatus reference entity on Order to null before saving.
I am not sure if this is going to fit your requirement but I would suggest following.
From your code it surely looks like you are loosing relationship during mapping somewhere.
To me it looks like that as part of UpdateSupplier operation you are not actually updating any of the child details of the supplier.
If that is the case I would suggest to updadate only changed properties from the SupplierVm to the domain Supplier class. You can write a separate method where you will assign property values from SupplierVm to the Supplier object (This should change only non-child properties such as Name, Description, Website, Phone etc.).
And then perform db Update. This will save you from possible messup of the tracked entities.
If you are changing the child entities of supplier, I would suggest to update them independent of suppliers because retrieving an entire object graph from database would require lot of queries to be executed and updating it will also execute unnecessary update queries on database.
Updating entities independently would save lot of db operations and would add to the performance of the application.
You can still use the retrieval of entire object graph if you have to display all the details about the supplier in one screen. For updates I would not recommend update of entire object graph.
I hope this would help resolving your issue.

The entity type 'Microsoft.AspNet.Mvc.Rendering.SelectListGroup' requires a key to be defined

Not sure what happened but I am getting the following error while attempting to pull up any view in my web app. The code is auto generated by visual studio and I am not getting any errors before building. Using ASP.Net MVC 6, EF7.
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.Core.dll but was not handled in user code
Additional information: The entity type 'Microsoft.AspNet.Mvc.Rendering.SelectListGroup' requires a key to be defined.
Here is the line the code is erroring out on.
public IActionResult Index()
{
var schoolContext = _context.Schools
.Include(s => s.District)
.Include(s => s.Location)
.Include(s => s.Tier);
return View(schoolContext.ToList());
}
After some searching I can't figure out exactly what I need to fix. This was working at one point. Not sure what changed.
The view does have a defenition
#model IEnumerable<School>
As requested here is the School model
public class School
{
//Original Fields
public int SchoolId { get; set; }
[Display(Name = "Name")]
public string SchoolName { get; set; }
[Display(Name = "Date Added")]
public DateTime SchoolDateAdded { get; set; }
[Display(Name = "Last Update")]
public DateTime SchoolLastUpdate { get; set; }
[Display(Name="Updated By")]
public string SchoolUpdatedBy { get; set; }
//Referance Fields
public int DistrictId { get; set; }
public IEnumerable<SelectListItem> DistrictList { get; set; }
public int LocationId { get; set; }
public IEnumerable<SelectListItem> LocationList { get; set; }
public int TierId { get; set; }
public IEnumerable<SelectListItem> TierList { get; set; }
//Navigation Property
public District District { get; set; }
public Location Location { get; set; }
public Tier Tier { get; set; }
}
These IEnumerable<SelectListItem>s should not be part of your EF model. Remember the single responsibility principle. Keep any UI framework away from your DAL implementation. Use a view model representing a School.
As for the error, from EF's point of view, School has a 1-n association with SelectListItem, so it tries to make it part of its mapping schema. But each mapped type needs a primary key, which of course isn't mapped, and EF can't infer any.
A quick, but dirty, fix would be to exclude the properties from being mapped by the [NotMapped] attribute, but a better segregation of your code is the real remedy.

Entity Framework - Code First - Map results to Not Mapped properties

I have created these entities Product, Order, OrderedItem in EF using Code First.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public int IssuedQuantity { get; set; }
[NotMapped]
public int InhandQuantity { get; set; }
public virtual ICollection<OrderedItem> OrderedItems { get; set; }
...
}
public class Order
{
public int Id { get; set; }
public string ReferenceNumber { get; set; }
public virtual ICollection<OrderedItem> OrderedItems { get; set; }
...
}
public class OrderedItem
{
public int OrderId { get; set; }
public string ProductId { get; set; }
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
...
}
Now I want to get all products by passing current user id to a stored procedure. It will then return all products along with total product quantity currently in user's hand.
The problem is that EF is not mapping SP results back to Product entity for NotMapped properties. i.e. all properties in product entity have values but NotMapped properties are set to NULL even when I return their values from SP.
What I want to ask is that does EF support this kind of functionality? If yes then how?
NOTE I know about Computed Properties but that will create unneccessary columns in tables and I don't want that, since these properties are calculated at run-time.
NOTE I know that I don't need to create OrderedItem entity. But I am storing some other properties in it, which are removed here for brevity.
I'm quite sure that EF does not support dynamic mapping (you could try to change the mapping metadata but is not a clean way or delete the mapping cache but then EF will be very slow). In this case the razionale is that the entity are 2 different entities because they have different data. In your case probably the best thing is to do 2 entities the ProductWithQuantities that inherits from Product.
BTW Thinking about ERPs, the model of orders/wms usually is different. Products does not contain informations about QtyOnHand or sales/buy information. Usually is another object (Inventory?) that contains this informations.
I would create a View Model of the product with all the required properties and pass that to the view instead of the Product model. Then you are not constrained by the mappings of the Product model and you do not have to use the [NotMapped] Attribute on the fields.
[NotMapped]
public class ProductVM
{
public int Id { get; set; }
public string Name { get; set; }
public int IssuedQuantity { get; set; }
public int InhandQuantity { get; set; }
public virtual ICollection<OrderedItem> OrderedItems { get; set; }
...
}
I hope that helps.

Validation of nested models in view model in ASP.Net MVC

I have an application with a Company model. The Company model has a navigation property to an Address model (one-to-one relationship):
Company.cs
public class Company
{
public int CompanyID { get; set; }
public string Name { get; set; }
// Snip...
public virtual Address Address { get; set; }
}
I've created a view model to handle the edit, detail, and create actions:
CompanyViewModel.cs
public class CompanyViewModel
{
public int CompanyID { get; set; }
[Required]
[StringLength(75, ErrorMessage = "Company Name cannot exceed 75 characters")]
public string Name { get; set; }
// Snip...
public Address Address { get; set; }
}
I'm using AutoMapper in my controller to map back and forth between the model and view model, and everything is working properly. However, I now want to use validation on the address object - I do not want a company to be created without an address being present.
My first thought was the simple route - I tried putting a '[Required]' annotation on the Address property. This didn't do anything.
I then thought it would be better to do away with the Address property and abstract that data in the view model, so I added properties to the view model for all the properties in my Address class:
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
// etc....
This seemed like good practice, but now my AutoMapper can't map these properties to the Company class' Address object, so I had to manually map in the controller:
public ActionResult Details(int id = 0)
{
// Snip code retrieving company from DB
CompanyViewModel viewModel = new CompanyViewModel();
viewModel.Name = company.Name;
viewModel.Address1 = company.Address.Address1;
// Snip...
return View(viewModel);
}
This leads to a lot of extra code in my controller instead of a nice one-line AutoMapper statement...so what's the right way to deal with this (validation of nested models in a view model)?
Is it good practice to expose the Address property directly in the view model, or better to abstract it out with separate properties like I have done?
Can AutoMapper work in a situation where source and destination are not exact matches?
if you want automapper to be able to map your properties from model to your viewmodel without specifying the mappings explicitly, you've got to use the "flattenting convention" : means that you must concatenate the navigation property's name with its property names.
So your ViewModel should contain
public int CompanyID { get; set; }
[Required]
[StringLength(75, ErrorMessage = "Company Name cannot exceed 75 characters")]
public string Name { get; set; }
// Snip...
//Address is the navigation property in Company, Address1 is the desired property from Address
public string AddressAddress1 { get; set; }
public string AddressAddress2 { get; set; }
public string AddressCity { get; set; }
public string AddressPostalCode { get; set; }
}
by the way, you can also tell AutoMapper to map properties which don't respect the naming convention explicitly :
Mapper.CreateMap<Company, CompanyViewModel>()
.ForMember(dest => dest.Address1, opt => opt.MapFrom(src => src.Address.Address1));

Categories

Resources