I have added a prop to my class and added a new DbSet but when saving, it does not store my child objects.
My house class:
[Table("Houses")]
public class House
{
[Key]
public int ID { get; set; }
public List<Price> Prices { get; set; } // <-- new prop
}
my dbcontext has a Prices prop now: public DbSet<Price> Prices { get; set; } and I enabled migrations, added the migration and updated the database. So the prices table is created.
When I update a House object, it does not insert anything in the Prices table.
var h = new House(); // with prices etc filled
if (db.Houses.Any(hc => hc.Code.Equals(h.Code, StringComparison.InvariantCultureIgnoreCase)))
{
var original = db.Houses.First(k => k.Code.Equals(h.Code, StringComparison.InvariantCultureIgnoreCase));
h.ID = original.ID; // workaround for The property 'ID' is part of the object's key information and cannot be modified.
h.CountryId = original.CountryId;
db.Entry(original).CurrentValues.SetValues(h);
db.SaveChanges(); // does update House fields, but no prices
} else { // add new
db.Houses.Add(h);
db.SaveChanges();
}
I did add public virtual House House { get; set; } to my Price class. But I do not fill it, because when populating the house object, I do not know the ID in the db yet. Maybe that is causing it? I have also read https://stackoverflow.com/a/26572122/169714 and added this to my Price class:
[ForeignKey("HouseId")]
public virtual House House { get; set; }
public int HouseId { get; set; }
but still no entries in the prices table. I am probably doing something wrong storing/updating the database.
edit current store method:
using (var db = new MyCommon.HouseContext())
{
var l = db.Countries.First(tmpC => tmpC.Code.Equals(h.Country.Code));
h.OperatorId = op.ID;
h.CountryId = l.ID;
h.Country = l;
var existingHouse = db.Houses.Where(p => p.Code.Equals(h.Code, StringComparison.InvariantCultureIgnoreCase)).SingleOrDefault();
if (existingHouse != null)
{
// update
h.ID = existingHouse.ID; // workaround for The property 'ID' is part of the object's key information and cannot be modified.
h.CountryId = existingHouse.CountryId;
h.OperatorId = existingHouse.OperatorId;
db.Entry(existingHouse).CurrentValues.SetValues(h);
db.Entry(existingHouse).State = System.Data.Entity.EntityState.Modified;
//db.SaveChanges(); // moved to bottom for perf.
}
else
{
existingHouse = h;
db.Houses.Add(h); // insert
}
foreach (var ft in existingHouse.Prices)
{
var existingFt = existingHouse.Prices.SingleOrDefault(f => f.ID == ft.ID);
if (existingFt != null)
{
db.Entry(existingFt).CurrentValues.SetValues(ft);
db.Entry(existingFt).State = System.Data.Entity.EntityState.Modified;
}
else
{
existingHouse.Prices.Add(ft);
}
}
db.SaveChanges();
}
Check the EntityState of your objects. Attach the object to your context, and iterate through these Prices to mark the EntityState.Modified. If the Price objects are new, you can use EntityState.Added. You can see here for an 'upsert' pattern example.
Related
So I'm passing in a list of objects (that are originally in the database already) to a function to update a property (SentDate) in the database, the structure is similar to
public class Product
{
[Key, Column("SKU", Order = 0)]
public string SKU { get; set; }
[Key, Column("Sequence", Order = 1)]
public int Sequence { get; set; }
public DateTime SentDate { get; set; }
}
And it is going into a function to update. Where I went wrong was I was attempting to do:
public static void UpdateSentDate(List<Product> records, DateTime CurrentDate)
{
DbContext db = new DbContext(); // there is a DbSet for Product in here
var toUpdate = db.Products.Where(c => records.Contains(c)).ToList();
foreach (var rec in toUpdate)
{
rec.SentDate = CurrentDate;
}
db.SaveChanges();
}
This bombs at the toUpdate creation due to the records.Contains(c) as it doesn't involve primitives. So I'm curious how to get the records where records's SKUs and Sequences match up with the database's that is better than my current stopgap:
List<Product> dbRecords = new List<Product>();
foreach (var record in records)
{
var item = db.Products.Where(c => c.SKU == record.SKU && c.Sequence == record.Sequence).Single();
dbRecords.Add(item);
}
you can make it work a little faster if you assign a new date in the same time
foreach (var record in records)
{
var item = db.Products.Where(c => c.SKU == record.SKU && c.Sequence == record.Sequence).Single();
if (item!=null)
{
item.SentDate = CurrentDate;
db.Entry(item).Property(i=>i.SentDate).IsModified = true; // maybe you can ommit this
}
}
db.SaveChanges();
What I want to do: When a user selects a product, populate a data grid with every Product. If that Product / Event combination have an associated EventProduct, fill in other pieces of the data grid with that data. If not, create a new EventProduct and default all properties to 0. On saving the event, if the EventProduct properties have changed or been populated, save that EventProduct to the DB as a new EventProduct.
My current approach:
I have three classes: Event, Product, and EventProduct as defined here (truncated).
public partial class Event
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Event()
{
EventProducts = new HashSet<EventProduct>();
}
[Key]
public int index { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<EventProduct> EventProducts { get; set; }
}
public partial class Product
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Product()
{
EventProducts = new HashSet<EventProduct>();
}
[Key]
public int index { get; set; }
[StringLength(200)]
public string name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<EventProduct> EventProducts { get; set; }
}
public partial class EventProduct
{
public EventProduct()
{
Event = new Event();
Product = new Product();
quantity_allocated = 0;
quantity_sold = 0;
quantity_sampled = 0;
}
public int index { get; set; }
public int EventID { get; set; }
public int ProductID { get; set; }
public int? quantity_allocated { get; set; }
public int? quantity_sold { get; set; }
public decimal? quantity_sampled { get; set; }
public virtual Event Event { get; set; }
public virtual Product Product { get; set; }
}
I'm populating the table by querying and joining my Products to my EventProducts, and creating a new Associative Object which has a Product and an EventProduct in a one-to-one relationship. I'm setting my itemsource equal to the following:
public static List<ProductEventProduct> GetProductEventProduct(Event e, DatabaseModel dbContext)
{
var query = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = e.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty()
select new ProductEventProduct
{
Product = product,
EventProduct = eventProduct
};
var dataSource = query.ToList();
foreach (ProductEventProduct entry in dataSource)
{
if (entry.EventProduct == null)
{
entry.EventProduct = new EventProduct()
{
EventID = e.index,
ProductID = entry.Product.index,
Product = entry.Product,
Event = e
};
}
}
return dataSource;
}
And when I have a single, manually input (direct into my data source) EventProduct it works as intended, and users can edit the Allocated amount (sold and sampled are locked in this view):
My problem is with saving. Right now I'm iterating through each row of the data grid, and if it's been changed or if the value is not null, create an EventProduct from that and add that EventProduct to my Database Context:
List<Associations.ProductEventProduct> entries = (List<Associations.ProductEventProduct>)EventProductDataGrid.ItemsSource;
IEnumerable<Associations.ProductEventProduct> changedEntries = entries.Where(association =>
association.EventProduct.quantity_allocated != 0 ||
association.EventProduct.quantity_sampled != 0 ||
association.EventProduct.quantity_sold != 0);
foreach (Associations.ProductEventProduct entry in changedEntries)
{
// if there are no event products in the database that have the same product and event, it's new so save it to DB
if (!(dbContext.EventProducts.Any(ep =>
ep.EventID == entry.EventProduct.EventID && ep.ProductID == entry.Product.index)))
{
dbContext.EventProducts.Add(entry.EventProduct); // line where I get the error described below
dbContext.SaveChanges();
}
else // if it is an EventProduct which exists in the database already
{
EventProduct modifyEvent = dbContext.EventProducts.Single(ep => ep.Event.index == entry.EventProduct.Event.index && ep.Product.index == entry.Product.index);
modifyEvent.quantity_allocated = entry.EventProduct.quantity_allocated;
modifyEvent.quantity_sampled = entry.EventProduct.quantity_sampled;
modifyEvent.quantity_sold = entry.EventProduct.quantity_sold;
}
}
dbcontext.SaveChanges();
But when adding my EventProduct to my DBContext, I get the error, "'A referential integrity constraint violation occurred: A primary key property that is a part of referential integrity constraint cannot be changed when the dependent object is Unchanged unless it is being set to the association's principal object. The principal object must be tracked and not marked for deletion.'". Which doesn't make sense to me, since both its references to Product and Event are populated, valid, and correct in my debugger.
I've been stuck on various pieces of this issue for days now and I know that my approach is wrong, any advice would be enormously appreciated.
I imagine your problem is that the EventProduct you are adding to your DbContext refers to an Event or Product (or both) that already exist in the database but are not currently being tracked by the DbContext. When calling dbContext.EventProducts.Add(entry.EventProduct); it has the effect that it's trying to add the entry.EventProduct.Event and entry.EventProduct.Product in the DbContext as if they are new entities.
If you know that entry.EventProduct.Event and entry.EventProduct.Product already exists in the database, then you can add them to the change tracker letting EF know that they already exist and haven't changed:
// Let EF know the entities already exist
dbContext.Set<Event>().Attach(entry.EventProduct.Event);
dbContext.Set<Product>().Attach(entry.EventProduct.Product);
// Now add the EventProduct letting it refer to the existing Event and Product
dbContext.EventProducts.Add(entry.EventProduct);
dbContext.SaveChanges();
Note: as per the documentation the entities you attach will be given the state Unchanged which means if you do have changes to the Event or Product that you want to update in the database you should instead use DbContext.Entry() and set the returned Entry.State to Modified.
I am facing a weird issue in an application. I have the following code which updates a parent entity and add new child entities.
Item item = db.Items.Find(Id);
if (newSubItems.Count() > 0)
{
newSubItems.ForEach(x =>
{
var subItem = new SubItem();
subItem.Name = x.Name;
item.SubItems.Add(subItem);
});
}
item.ModifiedAt = DateTime.Now;
item.ModifiedBy = UserId;
db.Entry(item).State = EntityState.Modified;
using (var s = db.Database.BeginTransaction(isolationLevel: IsolationLevel.RepeatableRead))
{
await db.SaveChangesAsync();
s.Commit();
logger.info("Updated successfully.");
}
This code is working fine in my local environment. If I supply new sub-items, those are added successfully to the respective table.
The models are given below.
public partial class Item
{
public Item()
{
this.SubItems = new HashSet<SubItem>();
}
public int Id { get; set; }
public DateTime ModifiedAt { get; set; }
public int ModifiedBy { get; set; }
public virtual ICollection<SubItem> SubItems { get; set; }
}
public partial class SubItem
{
public int Id { get; set; }
public string Name { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
}
However this is not working in my production environment as expected. Parent entity is updated, but new child entities are not added if there are no existing child entities. I checked the logs and I can see that "Updated successfully" is logged. If there is at-least 1 child entity for the parent, then new child entities are added successfully.
So now as a work around in production environment, I am re-adding the sub-items again after the first save operation using the below code.
int subItemsCount = db.SubItems.Where(a => a.ItemId == item.Id).Count();
if (subItemsCount == 0 && newSubItems.Count() > 0)
{
logger.info(string.Format("Sub-items are not added for Id - {0}. Adding those again.", item.Id));
newSubItems.ForEach(x =>
{
var subItem = new SubItem();
subItem.Name = x.Name;
subItem.ItemId = item.Id;
db.SubItems.Add(subItem);
});
await db.SaveChangesAsync();
logger.info(string.Format("Sub-items re-added successfully for Id - {0}.", item.Id));
}
Now at looking the logs from production environment, I can see that the message "Sub-items are not added for Id" is logged many times and sub-items are added successfully in the second save operation.
Wondering if any one know the reason for this weird behavior in specific environment alone.
In your first approach you should check if item.SubItems for null before doing item.SubItems.Add() on it.
If it is null then initialize like item.SubItems = new ICollection<SubItem>();
In your second approach,in this code block you are not assigning ItemId
newSubItems.ForEach(x =>
{
var subItem = new SubItem();
subItem.Name = x.Name;
subItem.ItemId = item.Id;/* missing line*/
db.SubItems.Add(subItem);
});
How to update the many to many navigation property?
I want to update the entity I get an error
have the same primary key value
I know there is way to split the many-many relation to two many-to-one relations, but this is the case. Update many-many relation.
I do this way to add new a entity with navigation property without problem BUT for updating there is an error. I tried to remove db.Entry(item).State = ... in update, but the problem is still there.
public class TrendChart
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<ParameterMonitor> Monitors { get; set; }
}
public class ParameterMonitor
{
public int Id { get; set; }
public virtual List<TrendChart> Charts{ get; set; }
}
var db = new DataAccess.ApplicationDbContext();
var newTrendChart = db.TrendChart.Where(x => x.Id == trendChart.Id).FirstOrDefault();
if (newTrendChart != null)
{
if (newTrendChart.Monitors != null)
newTrendChart.Monitors.Clear();
newTrendChart.Name = trendChart.Name;
newTrendChart.Monitors = new List<ParameterMonitor>();
foreach (var item in trendChart.Monitors)
{
newTrendChart.Monitors.Add(new DataAccess.ParameterMonitor { MOParameterId = item.MoParameterId });
}
// prevent from adding new parameter
foreach (var item in newTrendChart.Monitors)
{
// here the ERROR happens
db.Entry(item).StateSystem.Data.Entity.EntityState.Unchanged;
}
db.SaveChanges();
}
I found the solution, the point is that whenever you try to add something in intermediate table the CLEAR() function does not remove the history of items in the behind scene which we do not see, consider there are two items in the intermediate table , 1,2 and 1,3 . if you want to update the table by just 1,2 , it means the clear function remove 1,2 and 1,3 and again add 1,2. but it does not work because there is already an item 1,2 in behind scene. we need to remove() 1,3 and don't care about 1,2. Is there any other Solution.
var tempMOList = new List<int>();
tempMOList = newTrendChart.Monitors.Select(x=> x.MOParameterId).ToList();
foreach (var id in tempMOList)
{
var temp = newTrendChart.Monitors.Where(x => x.MOParameterId == id).FirstOrDefault();
if (trendChart.Monitors.Any(x => x.MoParameterId == id) == false)
newTrendChart.Monitors.Remove(temp);
}
foreach (var item in trendChart.Monitors)
{
if (newTrendChart.Monitors.Any(x => x.MOParameterId == item.MoParameterId) == false)
newTrendChart.Monitors.Add(new DataAccess.ParameterMonitor { MOParameterId = item.MoParameterId });
}
//prevent from adding new parameter
foreach (var item in newTrendChart.Monitors)
{
db.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
db.SaveChanges();
Room Model
public class Room
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Id is the primary key here
As in entity framework, all the room details are in the dbcontext
dbContext.Rooms
And there is a IList<Room> updateRoomswith list of updated name and address for few rooms.
How do I update dbContext.Rooms for the matching items in updateRooms using the primary key Id and save to DB using entity framework.
Note: I do understand that I can update each Room in dbContext.Rooms and save as below
foreach (var room in updateRooms)
{
dbContext.Rooms.Attach(room);
dbContext.Entry(room).State = EntityState.Modified;
dbContext.SaveChanges();
}
but is there a way attach all rooms and save at once
For another awswer
foreach (var room in updateRooms)
{
dbContext.Entry(room).State = EntityState.Modified;
}
dbContext.SaveChanges();
You also use this.
First You need to find all the entries with Id (Primary Key) and update the values. Then call SaveChanges() method.
foreach (var room in updateRooms)
{
var roomToUpdate = dbContext.Rooms.Find(room.Id);
roomToUpdate.Name = room.Name;
roomToUpdate.Address = room.Address;
}
dbContext.SaveChanges();
foreach(var item in updateRooms)
{
var oldModel= dbContext.Rooms.First(a => a.Id == item.Id);
if(oldModel !=null)
{
oldModel.Name = item.Name;
oldModel.Address = item.Address;
dbContext.SaveChanges();
}
}
Try This.