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
Related
Look at my code
using (MyBridgeContext context = new MyBridgeContext())
{
context.Users.Attach(user); //both working fine
context.Orders.Attach(order);
foreach (Defect def in defectList.Items)
{
if (defectList.SelectedItem == def)
{
context.Defects.Attach(def);//throwing error
defect = def;
}
}
DefectEntry entry = new DefectEntry();
entry.user = user;
entry.defect = defect;
entry.order = order;
entry.dt = DateTime.Now;
context.Entries.Add(entry);
context.SaveChanges();
this.Content = new MainMonitoring(ref order, ref user);
} <br />
When I am attaching user and order, it works fine even if such objects with their primary key exist in database(THAT IS THE REASON WHY I AM ATTACHING , I am not going to create a new object just attach it to ENTRY). But when I try to attach defect too, it throws that System.InvalidOperationException error.
So this the combobox of Defects:(Program loads the list of defects and when user chooses it, it should be attached to the entry):
private void loadDefects()
{
MyRepository rep = new MyRepository();
var defects = rep.GetDefects();
foreach(var def in defects)
{
defectList.Items.Add(def);
}
defectList.DisplayMemberPath = "Name"; //defectList is comboBox
} <br /> <br />
So this is MyRepository:
public class MyRepository
{
public List<Defect> GetDefects()
{
MyBridgeContext context = new MyBridgeContext();
return context.Defects.AsNoTracking().ToList(); //AsNoTracking doing nothing
}
public List<User> GetUsers()
{
MyBridgeContext context = new MyBridgeContext();
return context.Users.AsNoTracking().ToList();
}
public List<Order> GetOrders()
{
MyBridgeContext context = new MyBridgeContext();
return context.Orders.AsNoTracking().ToList();
}
}
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();
}
}
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)
I need to map two entities with one to zero or one relationship. when I create a principal object it works.
I have the following models:
//Principal class
public class Sugerencia
{
public int ID { get; set; }
[DisplayName("TÃtulo")]
public string Titulo { get; set; }
//More properties simplified for example
}
//Related class
public class Resultado
{
[ScaffoldColumn(false)]
public int SugerenciaID { get; set; }
[ScaffoldColumn(false)]
public int ID { get; set;}
//More properties
}
This is my DbContext Class
public class SugerenciaContext : DbContext
{
public SugerenciaContext() : base("SugerenciaContext")
{
}
public DbSet<Sugerencia> Sugerencias { get; set; }
public DbSet<Resultado> Resultados { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Resultado>()
.HasRequired(r => r.Sugerencia)
.WithOptional(s => s.Resultado);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Note the OnModelCreate overriding for map the relationship.
This is the ResultadoController Create actions (POST and GET)
//GET
public ActionResult Create(int? id)
{
ViewBag.ID = new SelectList(db.Sugerencias, "ID", "Titulo");
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
//I am retrieving the principal model instance to set resultado.Sugerencia property.
Sugerencia sugerencia = db.Sugerencias.Find(id);
if (sugerencia == null)
{
return HttpNotFound();
}
Resultado resultado = new Resultado();
resultado.Sugerencia = sugerencia;
return View(resultado);
}
//POST
public ActionResult Create([Bind(Include = "ID,SugerenciaID,Responsable,Resultados,Fecha")] Resultado resultado, string status)
{
if (ModelState.IsValid)
{
resultado.Fecha = System.DateTime.Now;
Sugerencia sugerencia = db.Sugerencias.Find(resultado.ID);
if (sugerencia == null)
{
return HttpNotFound();
}
sugerencia.Status = status;
//Here I am modifying the state property of Sugerencia model.
db.Entry(sugerencia).State = EntityState.Modified;
db.Entry(resultado).State = resultado.ID == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ID = new SelectList(db.Sugerencias, "ID", "Titulo", resultado.ID);
return View(resultado);
}
The error I am getting is triggered in db.SaveChenges();line. When I am trying to add a new Resultado model. However when I invoke the Create action for a principal instance which already has a related object in the DB it recreates the object rewriting the values on DB as expected.
In the create view of Resultado controller I have to update one property of Sugerencia model.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded
Note I am inserting or modifying the instance. I am debugging via Command Window and setting a breakpoint at that line. When printing resultado and sugerencia variables all properties seem to be valid.
Thanks for considering my question.
Looking at your code, this is what I think is happening:
// This is not needed, by the way, since the previous line should have done this
db.Entry(sugerencia).State = EntityState.Modified;
// This is probably setting resultado's state to Modified, even for new items!
db.Entry(resultado).State = resultado.ID == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
First of all, when would you need to set the resultado state to EntityState.Modified in a Create action? Also, I'm guessing that your model configuration is setting Resultado.ID as the foreign key, so when you previously set resultado.Sugerencia = sugerencia, Resultado.ID was set to a value not equal to zero.
You should be fine with
db.Entry(resultado).State = EntityState.Added;
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);