EF Core 3.1 is overwriting all of the fields with null values from DTO when I send an HTTP PUT update request to my API - entity-framework

Context
I have an API set up with CRUD operations - I am having trouble with the HTTP PUT endpoint that I use for updating an entity. I only want the API to be able to update some fields for the entity, so I use a Data transfer object (DTO), and use AutoMapper to map it to the domain model for Entity Framework Core (EF Core) to update the entity in the database. When using AutoMapper to map from the DTO to the domain model, null values will be added, and then overwrite these values in the database. How can I prevent this from happening?
Code
PlayerController.cs
[HttpPut("{id}")]
public async Task<IActionResult> PutPlayer(Guid id, PlayerUpdateDto playerUpdateDto)
{
Player player = _mapper.Map<Player>(playerUpdateDto);
_context.Entry(player).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!PlayerExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok(player);
}
Player.cs
public class Player
{
[Key]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
public double Rating { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public ICollection<Match> MatchesOne { get; set; }
public ICollection<Match> MatchesTwo { get; set; }
public ICollection<Match> MatchesThree { get; set; }
public ICollection<Match> MatchesFour { get; set; }
}
PlayerUpdateDto.cs
public class PlayerUpdateDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
The Player rating, created, and updated properties all get set to null values, but I want them to remain the same since they're not in the DTO. Any and all help would be greatly appreciated!

This
_context.Entry(player).State = EntityState.Modified;
Marks all the properties as modified. Instead mark only the ones you want to update. Eg
_context.Entry(player).Property(p => p.Name).IsModified = true;

Related

Entity Framework Core: How do I update a record with nested fields?

I've got a simple "ContactsList" ASP.Net Core Web (REST) application, .Net Core 3.0, an MSSQL LocalDB, using MSVS 2019.
My "Contact" entity contains a list of "Notes".
When I create a new contact that already contains one or more notes, everything works fine. EF automagically inserts the notes into the notes table.
But when I try to UPDATE a contact, EF seems to disregard "notes".
Q: For "Updates", do I need write code in my controller to explicitly update the notes myself?
Or am I doing something "wrong", such that EF can't "automagically" do the updates it's supposed to?
Models/Contact.cs:
public class Contact
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContactId { get; set; }
public string Name { get; set; }
public string EMail { get; set; }
public string Phone1 { get; set; }
public string Phone2 { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public List<Note> Notes { get; set; }
}
Models/Note.cs:
public class Note
{
public Note()
{
this.Date = DateTime.Now; // Default value: local "now"
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int NoteId { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
[ForeignKey("Contact")]
public int ContactId { get; set; }
}
Controllers/ContactsController.cs (POST works: if there are notes in the contacts list, it adds them):
[HttpPost]
public async Task<ActionResult<Contact>> PostContact(Contact contact)
{
_context.Contacts.Add(contact);
await _context.SaveChangesAsync();
//return CreatedAtAction("GetContact", new { id = contact.ContactId }, contact);
return CreatedAtAction(nameof(GetContact), new { id = contact.ContactId }, contact);
}
Controllers/ContactsController.cs (PUT seems to completely disregard any assocated notes):
[HttpPut("{id}")]
public async Task<IActionResult> PutContact(int id, Contact contact)
{
if (id != contact.ContactId)
{
return BadRequest();
}
_context.Entry(contact).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ContactExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
The SQL for POST shows four separate INSERTs: one for the contact, and one for each note.
The SQL for PUT only shows one UPDATE: just the contact; nothing else.
The debugger shows "notes" are clearly part of the "Contact" record that the controller received by PutContact().
Q: Should EF deal with "updating" notes automagically, or do I need to hand-code my updates in the controller?
Entity Framework Core ignores relationships unless you explicitly include them in queries.
_context.Entry(contact).State = EntityState.Modified;
The problem with the line above is that you did not specify that the related data has been modified, so it will be ignored in the query.
So you can either
attach all the related data
set the state of the related data to EntityState.Modified
or you can
query the object in the database and include the related data
and then assign the contact object to that queried object
var dbContactObj = _context.Contacts.Include(x => x.Notes).First(x => x.Id == contact.Id);
dbContactObj = contact;
_context.SaveChangesAsync();

Asp.net core 2.0 The instance of entity type 'X' cannot be tracked because another instance with the key value 'Id:x' is already being tracked

I have been working on a shop site project, using asp.net core spa templates provided with the latest VS2017, and have come across an issue that I haven't had before, possibly because until now my apps were quite simple!
I know what the problem is and where, I just can't fix it. I have a product model which has a collection of "Attributes" and a collection of "Variations" (different colour size, etc) and those variations also have attributes, so if the same Attribute shows up in the Variation (VAttributes), as is already in the main "Attributes" I get the error
InvalidOperationException: The instance of entity type
'ProductAttribute' cannot be tracked because another instance with the
key value 'Id:2' is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached.
The best answer I found was here : https://stackoverflow.com/a/19695833/6749293
Unfortunately, even with the above check I got the error, I even tried making a list of attached attributes, and if the vattribute matched one of the items in the list, I didn't attach it. In fact I found that even if I don't attach (_context.attach()) any of the vAttributes, it still throws the error!.
Here's the code in question:
public async Task<Product> Create(Product product)
{
try
{
foreach (var variation in product.Variations)
{
foreach (var vAttr in variation.VAttributes)
{
bool isDetached = _context.Entry(vAttr).State == EntityState.Detached;
if (isDetached)
_context.Attach(vAttr);
}
}
foreach (var attribute in product.Attributes)
{
bool isDetached = _context.Entry(attribute).State == EntityState.Detached;
if (isDetached)
_context.Attach(attribute);
}
foreach (var category in product.Categories)
{
_context.Attach(category);
_context.Attach(category).Collection(x => x.Children);
}
_context.Products.Add(product);
await Save();
return product;
}
catch (Exception)
{
throw;
}
}
The models for the 3 objects are as follows:
public class Product
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string StockRef { get; set; }
public DateTime? LastModified { get; set; }
//image needed
public ICollection<ProductCategory> Categories { get; set; }
public ICollection<ProductAttribute> Attributes { get; set; }
public ICollection<ProductVariation> Variations { get; set; }
public Product()
{
Attributes = new List<ProductAttribute>();
Variations = new List<ProductVariation>();
Categories = new List<ProductCategory>();
}
}
Variation:
public class ProductVariation
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<ProductAttribute> VAttributes { get; set; }
//needs images
public decimal VPrice { get; set; }
public string VStockRef { get; set; }
}
Finally the Attribute:
public class ProductAttribute
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("AttributeCategory")]
public int AttributeCategoryId { get; set; }
public virtual AttributeCategory AttributeCategory { get; set; }
}
Most help I found when searching was more related to having repo's injected as singletons, or HttpPut methods where the code had check for existence omitting the .AsNoTracking() or it was a mistake that they had the second instance in some way, where I am aware of the second instance, I just don't know how to prevent it from being tracked!
EDIT: I found that adding a foreign key on the ProductVariation model to the Product that was being created failed as it was only a temp key!? anyway removed it from the variation model, so have updated my code. Also thought I'd add one of my earler failed attempts, that led to all of the foreach loops.
_context.AttachRange(product.Attributes);
_context.AttachRange(product.Categories);
_context.AttachRange(product.Variations);
_context.Add(product);
I believe you can allow EF to handle the tracking.
public virtual bool Create(T item)
{
try
{
_context.Add(item);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
This allows for you to save the entire object structure without worring about attaching items.
var newProduct = new Product();
newProduct.Categories.Add(cat);
newProduct.Attributes.Add(att);
newProduct.Variations.Add(vari);
Create(newProduct);

Updating a relation between two Entity Framework entities?

I have two related Entity Framework 6 classes in my data layer.
public class Order
{
public int Id { get; set; }
public virtual SalesStatus SalesStatus { get; set; }
}
public class SalesStatus
{
public SalesStatus()
{
Orders = new List<Order>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Order> Orders { get; set; }
}
public class OrderVM
{
public int Id { get; set; }
public SalesStatus SalesStatus { get; set; }
}
I am using Automapper to map these to my view models and back again.
cfg.CreateMap<Order, OrderVM>()
.MaxDepth(4)
.ReverseMap();
The status entity is used to populate a drop down list.
In my method I am taking the selected value and trying to update the order record to the new selected status.
private bool SaveOrderToDb(OrderVM orderVM)
{
using (var db = new MyContext())
{
var order = AutomapperConfig.MapperConfiguration.CreateMapper().Map<OrderVM, Order>(orderVM);
order.SalesStatus = db.SalesStatuses.Find(Convert.ToInt16(orderVM.SalesStatusSelectedValue));
db.Set<Order>().AddOrUpdate(order);
db.SaveChanges();
}
return true;
}
This does not update the relationship in the database. Why? What am I missing?

.Net MVC 4 REST Cannot send Object

I have build a .Net Mvc 4 application and now I want to extend it with REST.
I am using the Entity Framework and I have the following problem.
My goal is to have a system where categories have a number of products and where products can belong to multiple categories.
As follows:
public class Categorie
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
private List<Product> producten;
public virtual List<Product> Producten
{
get { return producten; }
set { producten = value; }
}
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
[Required]
public double Prijs { get; set; }
private List<Categorie> categorien = new List<Categorie>();
public virtual List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
[Required]
public byte[] Plaatje { get; set; }
}
NOTE: There are virtual properties in there so that my entity framework creates a merging table. Normally it links all the categorie's to the products and vice versa.
And my rest looks like:
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
Product newProduct = new Product();
if (product == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
else
{
product.Categorien = null;
}
newProduct.Id = product.Id;
newProduct.Naam = product.Naam;
newProduct.Omschrijving = product.Omschrijving;
newProduct.Plaatje = product.Plaatje;
newProduct.Prijs = product.Prijs;
newProduct.Categorien = product.Categorien;
return newProduct;
}
First problem: I cannot send any product aslong as it has a categorie. I have to make it null.
Second problem: I cannot send the original product because of the first problem.
I am assuming your problem is with a circular reference during serialization, since categories reference multiple products and products reference multiple categories. One solution is to use Data Transfer Objects (DTO) instead of returning the straight entities you are using for EF. To make it easy to map your entities to the DTO's I would use AutoMapper. This is essentially what you are doing when you create an instance of newProduct in your REST API method, but AutoMapper takes the hard coding and drudgery out of mapping. Your DTO for a product would look very similar but they would not have the virtual navigation properties or the attributes needed by EF. A DTO for a product would look something like this.
public class Categorie
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public double Prijs { get; set; }
public List<Categorie> categorien = new List<Categorie>();
public List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
public byte[] Plaatje { get; set; }
}
Notice that the DTO for Categorie does not contain a list of products, since in this case you want a listing of products. If you keep the field names the same for your DTO's as your entities AutoMapper will handle the mapping automatically. I usually keep the same class name for the DTO's and just distinguish them from the entities by having a different namespace. Your REST API method would look something like this.
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
return Mapper.Map<Product, Dto.Product>(product);
}

Entity framework Database First & EF Code First get Relation Object By ID

in EF Database First when change ForeignKey(CommodityGroupID) automatic Get CommodityGroup for Commodity object, But in EF Code First(4.3.1) not doing.
public class Commodity
{
public int CommodityID { get; set; }
public string MadeBy { get; set; }
public decimal ServiceTimePrice { get; set; }
public decimal QCPrice { get; set; }
public int ServicePoint { get; set; }
public string Note { get; set; }
public int CommodityGroupID { get; set; }
[ForeignKey("CommodityGroupID")]
public virtual CommodityGroup CommodityGroup { get; set; }
}
public class CommodityGroup
{
public int CommodityGroupID { get; set; }
public string CommodityGroupName { get; set; }
public virtual ICollection<Commodity> Commodities { get; set; }
}
this Property defined in Edmx file (database first), i Should define this code in ef code first?
[BrowsableAttribute(false)]
[DataMemberAttribute()]
public EntityReference<CommodityGroup> CommodityGroupReference
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<CommodityGroup>("GaamRepairModel.FK_Commodity_CommodityGroup", "CommodityGroup");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<CommodityGroup>("GaamRepairModel.FK_Commodity_CommodityGroup", "CommodityGroup", value);
}
}
}
It sounds like you're wanting a change tracking proxy. You want the CommodityGroup navigation property to update automatically when the FK is changed correct?
See this post on MSDN for details about the change tracking proxy.
This post on MSDN shows some code on how to test of your proxy object is being created properly.
Is this a new object? If so, you'll need to call the CreateObject function on your DbSet, not use the New Commodity().