EntityFramework Update Item by another Item - c#

I have this Code:
public static void SaveItem(Item itemFrom)
{
using (myEntitites ctx = new myEntitites())
{
Item itemTo = ctx.Items.First(x => x.ID = itemFrom.ID);
itemTo.Property1 = itemFrom.Property1;
itemTo.Property2 = itemFrom.Property2;
itemTo.Property3 = itemFrom.Property3;
//..lot of properties
ctx.SaveChanges();
}
}
I'm wondering whether there is a way to update an item without assigning each property.
itemFrom is an updated version of itemTo.

You can manually attach the item to the context without getting an object from the database.
Entity Framework will update the correct row by using the primary key defined in the model.
public static void SaveItem(Item itemFrom)
{
using (myEntitites ctx = new myEntitites())
{
ctx.Items.Attach(itemFrom);
ctx.Entry(itemFrom).State = EntityState.Modified;
ctx.SaveChanges();
}
}

Related

Update Navigation Property in Entity Framework

I am a new at depth of the Entity Framework
I have just wondered why Entity Framework doesn't save changes especially the navigation property although all other properties are already updated
Please I want simple explanation
This is My Service Class
public class ProductsService
{
AppDbContext _Context;
public ProductsService()
{
_Context = new AppDbContext();
}
public Product GetProduct(int id)
{
return _Context.Products.Include(p=>p.Category).Where(pro =>pro.Id == id).SingleOrDefault();
}
public void UpdateProduct(Product product)
{
_Context.Entry(product).State = System.Data.Entity.EntityState.Modified;
_Context.SaveChanges();
}
}
In Controller:
[HttpPost]
public ActionResult Edit(NewCategoryViewModel pro,int Id)
{
CategoriesService ser = new CategoriesService();
var NewProduct = ProService.GetProduct(Id);
var NewCat = ser.GetCategory(pro.CategoryId);
NewProduct.Description = pro.Description;
NewProduct.Name = pro.Name;
NewProduct.Price = pro.Price;
NewProduct.Category = NewCat;
ProService.UpdateCategory(NewProduct);
return RedirectToAction("ProductTable");
}
I have tried this and it works fine
[HttpPost]
public ActionResult Edit(NewCategoryViewModel pro,int Id)
{
using (var Context = new AppDbContext())
{
var NewProd = Context.Products.FirstOrDefault(pr => pr.Id == Id);
var Cat = Context.Categories.FirstOrDefault(cat => cat.Id == pro.CategoryId);
Context.Entry(NewProd).State = EntityState.Modified;
NewProd.Name = pro.Name;
NewProd.Description = pro.Description;
NewProd.Price = pro.Price;
NewProd.Category = Cat;
Context.SaveChanges();
}
}
and for UpdateCategory
public void UpdateCategory(Category category)
{
using (var Context = new AppDbContext())
{
Context.Entry(category).State = System.Data.Entity.EntityState.Modified;
Context.SaveChanges();
}
}
Why the first one Not work
I know may be the problem in the state of the navigation property
Since you created the DbContext inside ProductService and you created a new Context inside:
public void UpdateCategory(Category category)
{
using (var Context = new AppDbContext())
{
Context.Entry(category).State = System.Data.Entity.EntityState.Modified;
Context.SaveChanges();
}
}
-> you use two different DbContext's together (which can cause problems with change tracking)!
Solution:
Try to use DependencyInjection for all DbContext's instead of creating them locally to prevent problems with change tracking.
You might consider using .add() instead of .entry().
.add() will also track other reachable entities.
documentation can be found here:
entity framework

EF Core - Disposable DbContext and Attach() - or - DbContext as member - or - Disconnected Entities

I'm not sure about how to correctly use the DbContext for Entities bound to a WPF DataGrid?
How do I correctly "re-attach" and save changes to the database for all the entities that were loaded to the datagrid during UserControl load?
I was using a DbContext as a member variable and ObservableCollection as DataSource for Datagrids. So everything was fine so far, no need to search for errors in the code below. Just to show what I have done so far.
// Old code - working perfectly as desired
private TestMenuDataContext _Db;
public ObservableCollection<Vendor> Vendors { get; set; }
private void ucGeneralSettings_Loaded(object sender, RoutedEventArgs e) {
//Do not load your data at design time.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) {
_Db = new TestMenuDataContext();
_Db.Database.EnsureCreated();
Vendors = new ObservableCollection<Vendor>(_Db.Vendors);
Vendors.CollectionChanged += Vendors_CollectionChanged;
vendorDataGrid.ItemsSource = Vendors;
}
}
private void Vendors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
_Db.Vendors.AddRange(e.NewItems.Cast<Vendor>());
foreach (var vendor in e.NewItems.Cast<Vendor>()) {
vendor.TimeStamp = DateTime.Now;
vendor.UserName = Environment.UserName;
}
break;
case NotifyCollectionChangedAction.Remove:
_Db.Vendors.RemoveRange(e.OldItems.Cast<Vendor>());
break;
}
}
private void SaveSettingsButton_Click(object sender, RoutedEventArgs e) {
var queryDeletedUsedVendor = _Db.TestMenu.Where(t => !Vendors.Any(v => v.Name== t.Vendor));
if (queryDeletedUsedVendor.Any()) {
_AppManager.AddStatusMessage($"Saving settings not possible. Vendor {queryDeletedUsedVendor.FirstOrDefault().Vendor} deleted but it is in use in the Test Menu!", State.Error);
return;
}
try {
_Db.SaveChanges();
_AppManager.AddStatusMessage("Settings saved", State.Ok);
}
catch (Exception ex) {
_AppManager.AddStatusMessage($"Saving data failed {ex.Message}", State.Error);
}
// fire delegate event to inform MainWindow
onDatabaseUpdated?.Invoke(this);
}
private void ucGeneralSettings_Unloaded(object sender, RoutedEventArgs e) {
if (_Db != null)
_Db.Dispose();
}
BUT, currently starting with MVVM and search how to correctly integrate EF Core. Now I have read several times:
Your DbContext lifetime should be limited to the transaction you are
running.
E.g. here:c# entity framework: correct use of DBContext class inside your repository class
So taking this into account and changed the saving code to:
// new code
using (TestMenuDataContext db = new TestMenuDataContext())
{
foreach (Vendor v in Vendors) {
var test = db.Vendors.Attach(v);
bool isAlreadyInside = db.Vendors.Any(v2 => v2.Id == v.Id);
if (!isAlreadyInside)
db.Vendors.Add(v);
}
db.SaveChanges();
Do I really need to loop over all entities, attach every single entity and check manually for deleted or added entities? I don't like to have a DbContext opened every time when CollectionChanged event appears. I can't believe it should be this complicated... So currently I would prefer to go with the DbContext as member variable as used before...
If I'm googling correct the not implemented disconnected entities aren't intended to be used in a WPF app with DB-Server connection, they are meant to be used in n-tier environment. So this is not the topic to search for, correct?
Do I need disconnected entities?
Disconnected Entities on MSDN
Side Note:
in MVVM ObservableCollection.CollectionChanged is supposed to inform View about changes in Model, resp ViewModel. I would not recommend to let View modify ObservableCollection and then use CollectionChanged to reflect the changes in ViewModel. Try to keep ViewModel -> View notification flow, not the other direction.* Every change is done in ViewModel and reflected in the View.
First approach:
basically split your application logic and your data access, which is exactly what viewmodel is for.
public class YourPageViewModel
{
private readonly ObservableCollection<VendorItemVm> _deletedVendors = new ObservableCollection<VendorItemVm>();
public List<VendorItemVm> Vendors { get; } = new List<VendorItemVm>();
void Add()
{
Vendors.Add(new VendorItemVm
{
IsNew = true,
Id = new Guid(),
UserName = "New Vendor",
});
}
void Remove(VendorItemVm vendor)
{
Vendors.Remove(vendor);
_deletedVendors.Add(vendor);
}
async Task Load()
{
using(var db = new DbContext())
{
var vendors = db.Vendors.AsNoTracking().ToList();
foreach(var entity in vendors)
{
Vendors.Add(new VendorItemVm
{
Id = entity.Id,
Name = entity.Name,
});
}
}
}
async Task Save()
{
using (var db = new DbContext())
{
//convert viewmodels to entities
var newVendorsEntities = Vendors
.Where(v => v.IsNew)
.Select(v => new Vendor
{
Id = v.Id,
UserName = v.UserName,
TimeSpan = DateTime.Now,
})
.ToArray();
//add new entities
foreach (var vm in Vendors.Where(v => v.IsNew))
{
var entity = new Vendor
{
Id = vm.Id,
UserName = vm.UserName,
TimeSpan = DateTime.Now,
};
db.Vendors.Add(vendor);
}
//delete removed entities:
foreach(var vm in _deletedVendors)
{
var entity = new Vendor { Id = vm.Id };
db.Vendors.Attach(entity);
db.Ventors.Remove(entity);
db.Vendors.Add(vendor);
}
await db.SaveChangesAsync();
//reset change tracking
foreach (var vm in Vendors) vm.IsNew = false;
_deletedVendors.Clear();
}
}
}
Second approach:
In the previevious example we have basically implemented our own primitive Unit of Work pattern. However, DbContext is already implementation of UoW and change tracking pattern.
We will create instance of DBContext, but we will use it only for tracking Added/Removed entities:
public class YourPageViewModel
{
MyDbContext _myUoW;
public ObservableCollection<Vendor> Vendors { get; } = new ObservableCollection<Vendor>();
void Add()
{
var entity = new Vendor
{
Id = new Guid(),
UserName = "New Vendor",
};
Vendors.Add(entity)
_myUoW.Vendors.Add(entity);
}
void Remove(VendorItemVm vendor)
{
Vendors.Remove(vendor);
_myUoW.Vendors.Attach(entity);
_myUoW.Vendors.Add(entity);
}
async Task Load()
{
using(var db = new MyDbContext())
{
Vendors = db.Vendors.AsNoTracking.ToList();
foreach(var entity in vendors) Vendors.Add(entity);
}
_myUoW = new MyDbContext();
//if you want to track also changes to each vendor entity, use _myUoW to select the entities, so they will be tracked.
//In that case you don't need to attach it to remove
}
async Task Save()
{
//add new entities and delete removed entities
_myUoW.SaveChanges();
//reset change tracking
_myUoW.Dispose();
_myUoW = new MyDbContext();
}
}
I don't like to have a DbContext opened every time when CollectionChanged event appears.
Then don't. Create a single TestMenuDataContext in your view model and use this one as you did before.
So currently I would prefer to go with the DbContext as member variable as used before.
There is nothing stopping you from doing so, is it? Apparently, you do want a single TestMenuDataContext per instance of your view model in this case. Just create a TestMenuDataContext once in your view model, for example in its constructor, and use this one in your CollectionChanged event handler. Or create the context in your save method.
The optimal lifetime of a DbContext may certainly vary depending on your requirements. In general you should use short-lived contexts, but in this case it seems like you do want (and should use) the same context for all changes made to the entity objects in your DataGrid.
The other option would of course be to create the context and attache your entities when the save button is pressed (and not every time the in-memory collection is modified).

Search all entities with specific property and their entries

I'm using EF Code First but my models have NO relationships (PKs - FKs). So I'm trying to find a way to workaround it by using EF6 Reflections in order to avoid an entry deletion that would have relationships (same property name).
Lookup over all my context entities in which has any specific property (FK);
For every entity found, check if this entity has any entry;
If its true, instead of deleting my entry, set a property "Canceled" as true;
If its false, keep entity state deleted and save my context changes;
public override int SaveChanges()
{
foreach (var myEntity in ChangeTracker.Entries<IAuditable>())
{
if (myEntity.State == EntityState.Deleted)
{
ObjectContext objContext = ((IObjectContextAdapter)this).ObjectContext;
var container = objContext.MetadataWorkspace.GetEntityContainer(objContext.DefaultContainerName, DataSpace.CSpace);
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(myEntity.Entity);
var entityKeys = objectStateEntry.EntityKey.EntityKeyValues;
var entity = myEntity;
var hasAnyFk = false;
foreach (var entityKey in entityKeys)
{
if (hasAnyFk)
{
break;
}
var keyName = entityKey.Key;
foreach (var entitySet in container.EntitySets)
{
hasAnyFk = entitySet.ElementType.Members.Any(es => es.Name == keyName);
if (hasAnyFk)
{
break;
}
}
}
if (hasAnyFk)
{
var deletedProperty = myEntity.OriginalValues.PropertyNames.Where(p => myEntity.Property(p).Name == "Deleted").FirstOrDefault();
if (deletedProperty != null)
{
myEntity.State = EntityState.Modified;
myEntity.CurrentValues[deletedProperty] = true;
}
}
}
}
return base.SaveChanges();
}
You can handle this is an overload of SaveChanges:
public override int SaveChanges()
{
foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State ==
System.Data.Entity.EntityState.Deleted).ToList())
{
var delPropName = "IsDeleted";
if (entry.OriginalValues.PropertyNames.Contains(delPropName))
{
var delProp = entry.Property(delPropName);
delProp.CurrentValue = true;
entry.State = System.Data.Entity.EntityState.Modified;
}
}
return base.SaveChanges();
}
Here, entry.OriginalValues.PropertyNames is used to check if the property exists in the entity and then its value is set and the entry's state is changed to Modified. Note that I loop through this.ChangeTracker.Entries() after applying ToList(), otherwise the content of the collection changes while looping through it.

Batch update on object list using EntityFramework 6 and Linq

I want to create a function that accepts a list of objects with their properties already set and does a batch update or insert on them only making 1 call to the database.
(UPDATE) Here is the working version:
public static void BatchInsertProducts(IList<Product> productList)
{
using (var context = new DbContext())
{
context.Products.AddRange(productList);
context.SaveChanges();
}
}
This is my attempt at doing either and update or an insert on several items based on the Id. I don't think I can use AddRange here so I tried it this way and I am getting an error:
public static void SaveMultipleProducts(IList<Product> productList)
{
using (var context = new DbContext())
{
foreach (Account p in productList)
{
if (p.ID == 0)
{
context.Entry(p).State = EntityState.Added;
p.InsertUserId = "jtunney";
p.InsertDate = DateTime.Now;
}
else
{
context.Entry(p).State = EntityState.Modified;
p.UpdateUserId = "jtunney";
p.UpdateDate = DateTime.Now;
}
}
context.SaveChanges();
}
}
Removed foreach loop, changed IList<Product> to IEnumerable<Product>:
public static void BatchInsertProducts(IEnumerable<Product> productList)
{
using (var context = new DbContext())
{
context.Products.AddRange(productList);
context.SaveChanges();
}
}
One way to do save multiple:
public static void SaveMultipleProducts(IEnumerable<Product> productList)
{
using (var context = new DbContext())
{
foreach (Account p in productList)
{
p.InsertUserId="jtunney";
p.InsertDate=DateTime.Now;
context.Entry(p).State=p.Id==0?EntityState.Added:EntityState.Modified;
}
context.SaveChanges();
}
}
Another way:
public static void SaveMultipleProducts(IList<Product> productList)
{
using (var context = new DbContext())
{
foreach (Account p in productList)
{
p.InsertUserId="jtunney";
p.InsertDate=DateTime.Now;
}
// Add all records
context.Products.AddRange(productList);
// Handle updates
foreach(var p in productList.Where(p=>p.id!=0))
{
context.Entry(p).State=EntityState.Modified;
}
context.SaveChanges();
}
}
It's not necessary to call Save method for each product. If you get the products and save with the same DbContext you only have to call Save one time, then It will save all your modifications.
Imagine
List<Product> products = Context.Product.ToList();
foreach(var product in products)
{
product.Price = new Random().Next();
}
Context.Product.SaveChanges();
This code is modifying the price for all Products in the list, but thanks to the fact that we are using the same context to retrieve the results and that EF implements Tracking to save the modifications with one call to SaveChanges it's enough.
For bulkInserts
Use AddRange method from Entity Framework http://www.entityframeworktutorial.net/EntityFramework6/addrange-removerange.aspx or you can try this library https://efbulkinsert.codeplex.com/. I have heard about it, but I have never used it
This is bit different approach than other....
public static DbContext BatchInsertProducts(DbContext context,IList<Product> productList)
{
context.Configuration.AutoDetectChangesEnabled = false;
for (int i = 0; i < lobbies.Count; i += 100)
{
context.Set<Product>().AddRange(lobbies.GetRange(i, Math.Min(100, lobbies.Count - i)));
context.Products.AddRange(productList);
context.SaveChanges();
//Dispose and create a context
context.Dispose();
context = new DbContext;
context.Configuration.AutoDetectChangesEnabled = false;
}
return context;
}
The above mentioned code snippet does some additional task inorder to improve the performance and also we can specify the number of records that need to included in each batch manipulation.
If we want to insert bulk records, creating new context for each batch
and dispose then once task is done. very much improve the performance
(from minutes to operation to seconds)

Updating Parent Entity doesn't Update Child Entity

Whether attaching parent entity to the context again and updating it should update the child entity? Am i missing something?
Or should I necessarily write the updating EF logic (in DAL) for the child entity?
This is my sample code model:
ChildEntity entityChild;
if (ParentEntity.ChildEntity.SingleOrDefault() != null)
entityChild = ParentEntity.ChildEntity.SingleOrDefault();
else
{
entityChild = new ChildEntity();
ParentEntity.ChildEntity.Add(entityChild);
}
entityChild.ColumnA= txtA.Text;
entityChild.ColumnB= txtB.Text;
// Send entityParent for update
_objParent.Update(entityParent)
_objParent.Update() code:
context.vouchers.Attach(entityParent);
ObjectStateEntry objectState = context.ObjectStateManager.GetObjectStateEntry(entityParent);
objectState.ChangeState(System.Data.EntityState.Modified);
context.SaveChanges();
UPDATE (Parent Loading Code Sample)
public ParentEntity GetById(int id)
{
using (var context = new DBEntities())
{
ParentEntity _entity = context.ParentEntity
.Include("ChildEntity")
.Where(e => e.parent_id == id);
return (ParentEntity)_entity.SingleOrDefault()
}
}
EF only track changes from the point where it gets to know about the object. The most simple way to work with updates in EF is to load the existing object from the DB, update it and then save it back. In this case it means that you should load the ParentEntity object from the DB.
The other approach is to use Attach as you do. In that case you should first call attach the unchanged ParentEntity and then call ParentEntity.ChildEntity.Add(entityChild).
Another alternative is to explicitly add the new ChildEntity directly to the DbContext, then you could simply set the foreign key value of the ChildEntity to the key value of the ParentEntity to make them connected.
You should load and update your entities inside the using statement - this way all the changes will be tracked by the Entity Framework:
using (var context = new DBEntities())
{
// load and update entities
// ....
context.SaveChanges();
}
UPDATE - This is just an example, I'll keep it simple. I would create a service where I'd put my logic - something like this:
public class ParentService
{
// other service methods here
protected DBEntities CreateContext()
{
return new DBEntities();
}
public ParentEntity Update(int id, string columnA, string columnB)
{
ParentEntity _entity = null;
using (var context = CreateContext())
{
bool isNew = false;
_entity = context.ParentEntity
.Include("ChildEntity")
.SingleOrDefault(e => e.parent_id == id);
ChildEntity entityChild = ParentEntity.ChildEntity.SingleOrDefault();
if(entityChild == null)
{
entityChild = new ChildEntity();
isNew = true;
}
entityChild.ColumnA = columnA;
entityChild.ColumnB = columnB;
if(isNew)
{
ParentEntity.ChildEntity.Add(entityChild);
}
context.SaveChanges();
}
return _entity;
}
}
In the UI code:
string id = .....;
string columnA= txtA.Text;
strign columnB = txtB.Text;
var service = new ParentService();
ParentEntity parent = service.Update(id, columnA, columnB);

Categories

Resources