Update Entity from ViewModel in MVC using AutoMapper - c#

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.

Related

EF Core Saving error for two objects depending on a second equal one

I have a vey long long POCO class ("FichaCliente") that I'll simplify here emphasizing the property that's an ObservableCollection:
{
public int FichaCliente1 { get; set; }
...
public int FichaClienteN { get; set; }
public ObservableCollection<Reclamado> Reclamados { get; set; }
public int FichaClienteN+2 { get; set; }
...
public int FichaClienteN+M { get; set; }
}
The "Reclamado" class is also a relative long POCO that has another class as element, the UF class. Thus we can understand the "Reclamado" as being:
{
public int Reclamado1 { get; set; }
...
public int ReclamadoN { get; set; }
public UF Uf { get; set; }
public int ReclamadoN+2 { get; set; }
...
public int ReclamadoN+M { get; set; }
}
Being UF class a simple POCO as follows:
{
public int Id { get; set; }
public string Apelido { get; set; }
public string Descricao { get; set; }
public string Municipio { get; set; }
}
The SAVE operation to the database is handled by just the "SaveFichaAsync" method that's as simple as:
public async void SaveFichaAsync(FichaCliente ficha)
{
using (var context = new DataContext(_dBService))
{
context.FichasClientes.Update(ficha);
await context.SaveChangesAsync();
return;
}
}
What's a sure thing is that "FichaCliente" can have many "Reclamado" as it's needed. And that's fine, it works!
Just to make it clear, the UF entity is the equivalent to me as what would be states in the US, thus, a given address belongs to a state (city, state...). As "Reclamados" are distinct entities, I can have one or more "Reclamados" belonging to the same UF.
That's where it fails!
If I have the same UF for two or more different "Reclamados", a error shows, as if, it was saving twice the same UF, although they're actually different, each one belonging to a different "Reclamado". It's not saving that same UF twice. It saves one for each "Reclamado"
An example of error is:
"The instance of entity type 'UF' cannot be tracked because another instance with the key value '{Id: 26}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
How can I handle this?
Although this could be avoided if you decouple the persistence model from the actual business code, to solve that you'll have to iterate throughout all UFs before the SaveChangesAsync and execute the following per UF
context.Entry(uf).State = EntityState.Unchanged;
Your entry is already tracked by the context. Just remove line context.FichasClientes.Update(ficha); and your code will work.
Update method is only useful when the entry is not tracking by the Context. Read more about update

Conflicting changes detected. This may happen when trying to insert multiple entities with the same key

I'm trying to have Auto Generated ID with EF6. The table in the Database has Identity set to yes on the PK. I keep getting the error "Conflicting changes detected. This may happen when trying to insert multiple entities with the same key.". I'm using Code First.
Model:
public partial class CurrentEquipmentOrderStatu
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long CurrentEquipmentOrderStatusId { get; set; }
public long? GPSInsightRowId { get; set; }
[StringLength(50)]
public string Status { get; set; }
[StringLength(50)]
public string TimeToJob { get; set; }
public long? OrderEquipAssignmentId { get; set; }
[StringLength(50)]
public string DistanceToJob { get; set; }
public long? EquipmentId { get; set; }
public virtual Equipment Equipment { get; set; }
public virtual GPSInsight GPSInsight { get; set; }
public virtual OrderEquipAssignment OrderEquipAssignment { get; set; }
}
Create and save method:
public static void CreateCurrentOrderStatus()
{
var Status = new List<CurrentEquipmentOrderStatu>();
using (FFAELDB db = new FFAELDB())
{
db.Configuration.AutoDetectChangesEnabled = false;
var EquipAssignments = db.OrderEquipAssignments;
foreach(OrderEquipAssignment assign in EquipAssignments)
{
db.CurrentEquipmentOrderStatus.Add(new CurrentEquipmentOrderStatu()
{
EquipmentId = assign.EquipmentId,
OrderEquipAssignmentId = assign.OrderEquipAssignmentId,
});
}
db.SaveChanges();
db.Dispose();
}
}
Any Help would be greatly appreciated.
There could be a problem with a foreign key. I had mapped my foreign key wrongly so I was getting this error. By correcting the FK and updating it in my Entity Framework it worked.
The key of CurrentEquipmentOrderStatu seems correct. The problem could be in some of the child entities. For instance, the key in GPSInsight. Is it an Identity as well? Are you assigning a new instance and leaving its Id uninitialized? (This part of your method is not included in the code you provided, for simplicity, I guess). If the Id is not automatically generated and you don't iniitalize it it will be 0, so in several iterations of the loop EF may guess that you are trying to insert several entities with the same Id 0 and regard it as duplicated values.
Also, db.Configuration.AutoDetectChangesEnabled = false might be interfering with your changes. Unless you have a good reason to use it I wouldn't recommend you to use it here like this.
Check FKs(EquipmentId) on list(List<CurrentEquipmentOrderStatu>).
You should not have FK with a value of 0 in the list.

Lazy loading not working with 1 to 0 or 1 relationship

I have the following table design.
As can be seen here, there is a one to many relationship, with the many on the EpisodePatient side.
Then, I have the following classes.
public class EpisodeModel
{
public int ID { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public int EpisodePatientID { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
I am setting up the relationship, in Entity Framework, to be a one to 0 or many. The reason for this is, I am selecting all EpisodePatients from a View, and I want the Episode to be Lazy loaded when accessed.
This is how I am setting up my relationship.
modelBuilder.Entity<EpisodePatientModel>().HasRequired(r => r.Episode).WithOptional(o => o.EpisodePatient);
I want this to act as a One to zero or many in my code, as an Episode will always have an EpisodePatient, and vice versa.
The problem I am facing is, when I load the EpisodePatient, and try to access the Episode linked item, it is always null, and Lazy loading does not occur.
What am I doing wrong here?
UPDATE
This is how I load the original EpisodePatient items.
this.DbContext.EpisodePatients.AsNoTracking();
I re-created your model but with data annotations like below and it workes fine:
public class EpisodeModel
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public string Name { get; set; }
[Key, ForeignKey("Episode")]
public int EpisodeId { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
Try without AsNoTracking(), because if you use it your context is not tracking and you can't include more data if you need.
And try change to relation one to many.
modelBuilder.Entity<EpisodePatientModel>().HasRequired<Episode>(s => s.Episode).WithMany(s => s.EpisodePatient);

Entity Framework One-To-One Relationship Form Edit/Save

I have a domain class called Bar which has a bunch of properties
One of the properties Bar has is actually another domain class called Address which is of type Address.
Address has a one to one relationship with Bar. So one Bar has one address and one address has one bar.
Due to this, I have both domain classes set to use BarId as their primary key.
Per the Microsoft Virtual Academy instructors, in order for EF to understand this, you have to specify the primary key with both the [Key] attribute, as well as the [ForeignKey] attribute. They also say you have to declare the property types as virtual, as I have done. See here:
Address Domain Model:
public class Address
{
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Must be a number")]
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int ZipCode { get; set; }
public virtual Bar Bar { get; set; }
[Key]
[ForeignKey("Bar")]
public int BarId { get; set; }
}
BAR Domain Model:
public class Bar
{
public int BarId { get; set; }
public string Name { get; set; }
[Required]
public string GooglePlaceId { get; set; }
public string SundayDiscounts { get; set; }
public string MondayDiscounts { get; set; }
public string TuesdayDiscounts { get; set; }
public string WednesdayDiscounts { get; set; }
public string ThursdayDiscounts { get; set; }
public string FridayDiscounts { get; set; }
public string SaturdayDiscounts { get; set; }
public virtual Address Address { get; set; }
[Display(Name = "Last Updated")]
public DateTime LastUpdated { get; set; }
Last but not least, this is the view model that I am using:
{
public class BarFormViewModel
{
public Address Address { get; set; }
public Bar Bar { get; set; }
public bool IsNew { get; set; }
}
}
My question is, how do I properly implement this into a form logic so that the address can be edited when the bar is edited? I am getting thrown off because the Bar domain has an Address which means I can tell my form to update either Address.StreetName or I can tell my form to update Bar.Address.StreetName where the Address.BarId == the bar.BarId. Likewise, I can select an Address object from the database and pass it to my form (via viewmodel), or I can select just a bar and then pass my view bar.Address. Is there any difference between doing these two things? So far my form works but the data passed through the viewmodel, into the form and back, isn't actually saving into the database. All data that is not passed to the viewmodel and through the form is saving just fine.
Here are my edit and save actions for reference:
public ActionResult Edit(int id)
{
var bar = _context.Bars.SingleOrDefault(b => b.BarId == id);
//Make sure that the id actually exists:
if (bar == null)
{
return HttpNotFound();
}
var viewModel = new BarFormViewModel
{
Address = bar.Address,
Bar = bar,
IsNew = false
};
return View("BarForm", viewModel);
}
[ValidateAntiForgeryToken]
public ActionResult Save(Bar bar)
{
if (!ModelState.IsValid)
{
var viewModel = Mapper.Map<Bar, BarFormViewModel>(bar);
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
if (bar.BarId == 0)
{
bar.LastUpdated = DateTime.UtcNow;
_context.Bars.Add(bar);
}
else
{
var barInDb = _context.Bars.Single(b => b.BarId == bar.BarId);
// var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Bar.Address.BarId);
Mapper.Map(bar, barInDb);
barInDb.MondayDiscounts = bar.MondayDiscounts;
barInDb.LastUpdated = DateTime.UtcNow;
}
_context.SaveChanges();
return RedirectToAction("Index", "Bar");
}
I have taken a look at this question, however, this does not seem to be the same issue.
What you missing with your current database design is Identify each set of related data with a primary key in First Normal Form on database normalization rules (check here). If you have one primary key, why not make them just one table? It will be much faster and easy to maintain.
But I think since Bar and Address are different things in their nature, so it is better to have two tables with different primary key. Then add BarId to Address table and public virtual Address Address to Bar table. So you will have a main Bar object and associated Address object.
Then Entity Framework will do the rest!: If you don't have current Address object for your current Bar , It will create it , if you change related address details on bar and try to update bar, EF will update Address too.
Please also check here for getting some ideas how EF handles associated objects.

Update to database does not save

I am working on an ASP.NET MVC application and I'm using entity framework and linq.
I am having an issue were when I try to update a record it does not update in the database.
This is the class I'm working with:
public class Customer
{
[Key]
[Required]
public int Id { get; set; }
[Required]
public string firstName { get; set; }
[Required]
public string surname { get; set; }
[Required]
public string userName { get; set; }
[Required]
public string password { get; set; }
[Required]
public bool subscription { get; set; }
public List<int> Articles { get; set; }
public void AddArticle(int id)
{
if (Articles != null)
{
Articles.Add(id);
}
else
{
Articles = new List<int> {id};
}
}
}
I have created a new entry based on the above class and saved it to the database and this has worked fine. I have left the ListArticles null for the time being.
Now I get the record:
var customer = context.Customers.SingleOrDefault(o => o.Id == 1);
Here the customer.Articles == null
Now I add to it:
customer.AddArtical(0);
Looking into this I can see that it has updated the variable customer now I need to save these updates in the database.
I have tried all the examples in here but none seem to save to the DB
Entity Framework 5 Updating a Record
context.Customers.Attach(query);
context.Entry(query).State = EntityState.Modified;
context.SaveChanges();
After this code is finished another area of the project is called and performs the same query:
var customer = context.Customers.SingleOrDefault(o => o.Id == 1);
however the List<int> Articles is still null.
Any ideas?
I suppose you have one entity Article in your project. After all, having just id's of articles and not articles themselves is not that usefull. The point is that this model of yours have just one list of ints, the list of id's and not the list of the articles.
Entity Framework Code First is convention based, so to establish relationships between entities you add reference between the entities themselves and not their id's. Think for a while, you have a list of ints, how EF could know these ints correspond to id's referencing other entities? It's not like that.
If you have this, instead:
public class Customer
{
[Key]
[Required]
public int Id { get; set; }
[Required]
public string firstName { get; set; }
[Required]
public string surname { get; set; }
[Required]
public string userName { get; set; }
[Required]
public string password { get; set; }
[Required]
public bool subscription { get; set; }
public List<Article> Articles { get; set; }
public Customer()
{
this.Articles = new List<Article>();
}
}
Then to add an article you would add the entity itself using the add method from the list collection. In your case, the addition method you write seems superflous. You are using it to avoid null reference exceptions, but if you instantiate the list on the constructor there will be no problems. You would need an adition method if there were some business logic on the addition of an article.
Doing things in this way you are sticking to EF conventions. When you add an article, EF will know what it must be done. Try doing this.

Categories

Resources