Entity Framework many to many relationship updating - c#

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

Related

Trying to update records in database with Entity Framework and using two keys from another list of object

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

.net core 3.1 - list is being overwritten during foreach

I have a project with .NET Core 3.1 and EF with the following model defined:
public class CustomerModel
{
[Key]
public int id { get; set; }
public int sales_person_id { get; set; }
public int company_id { get; set; }
[NotMapped]
public List<FileModel> _files { get; set; }
[NotMapped]
public List<StepModel> _steps { get; set; }
}
I am fetching a list of customers from my db:
List<CustomerModel> customer_records = _dbContext.customers.Where(c => c.company_id == company_record.id && c.sales_person_id == agent_record.id).ToList();
and then trying to populate that list with the [NotMapped] variables as follows:
foreach (CustomerModel c in customer_records)
{
c._steps = new List<StepModel>();
c._files = _dbContext.files.Where(f => f.company_id == company_record.id && f.customer_id == c.id).ToList();
c._steps = await _getStepsByCustomerId(c.id);
}
The _getStepsByCustomerId method:
private async Task<List<StepModel>> getStepsByCustomerId(int customer_id)
{
List<CustomerStepRef> _ref = await _dbContext.customer_step_ref.Where(s => s.customer_id == customer_id).ToListAsync();
List<StepModel> steps = new List<StepModel>();
foreach (CustomerStepRef _cs in _ref)
{
StepModel _step = await _dbContext.steps.FindAsync(_cs.step_id);
_step._customer_id = _cs.customer_id;
_step._step_status = _cs.step_status;
steps.Add(_step);
}
return steps;
}
Now, to the problem - the list _steps in customer_records is being overwritten during the foreach and eventually the _steps list has entries from the last iteration.
What am I missing?
Thanks,
Yaniv
The most obvious issue I see why c._steps will be overwritten is because instantiating the List<StepModel> happens within the for loop and there for will always be the populated with the last iteration. Place it on the outside and append to it. Assuming you want to append to the steps after every loop.
c._steps = new List<StepModel>();
foreach (CustomerModel c in customer_records)
{
c._files = _dbContext.files.Where(f => f.company_id == company_record.id && f.customer_id == c.id).ToList();
c._steps = await _getStepsByCustomerId(c.id);
}
Found it...
the following code solved the problem:
List<StepModel> _temp = await _platform.getStepsByCustomerId(c.id);
c._steps = _temp.Select(item => new StepModel
{
company_id = item.company_id,
step_name = item.step_name,
s_id = item.s_id,
_step_status = item._step_status,
_customer_id = item._customer_id
}).ToList();
I think the call to the method keeps updating the same memory area, hence causing my list items to point to the same memory area which keeps on updating on each iteration.
The _temp.Select copies the entire list items to a new one...
Thanks

Entity Framework - New child entities not added to database on saving parent entity (only in production environment)

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

Entity Framework 6 does not store child object

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.

update list from another list - entity framework

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.

Categories