Cannot modified the object in repository - c#

In ASP.NET MVC project I try to update with method SaveItem item in repository. I use Entity Framework 6.1. I got the exception. How can I to solve this problem?
Attaching an entity of type 'Domain.Entities.Post' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
My repository:
public class BlogRepository<T> : IBlogRepository<T> where T : class
{
private readonly DbContext context;
private readonly IDbSet<T> dbSet;
public BlogRepository()
{
this.context = new BlogDbContext();
dbSet = context.Set<T>();
}
public BlogRepository(DbContext context, IDbSet<T> dbSet)
{
this.context = context;
this.dbSet = dbSet;
}
public void SaveItem(T item)
{
object value = Reflector.GetPropertyValue(item);
var foundItem = dbSet.Find((int) value);
if (foundItem == default(T))
dbSet.Add(item);
else
{
context.Entry(item).State = EntityState.Modified;
}
context.SaveChanges();
}
public void Delete(T item)
{
dbSet.Remove(item);
context.SaveChanges();
}
public T GetById(int id)
{
return dbSet.Find(id);
}
public IEnumerable<T> GetAll()
{
return dbSet.AsEnumerable();
}
}
In controller I imlpemented the logic of editing
public class PostController : Controller
{
private readonly IBlogRepository<Post> blogRepository;
public PostController(IBlogRepository<Post> blogRepository)
{
this.blogRepository = blogRepository;
}
public ActionResult Index(int id = 1)
{
Post post = blogRepository.GetById(id);
return View(post);
}
public ActionResult Edit(int id)
{
Post post = blogRepository.GetById(id);
return View(post);
}
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid)
{
blogRepository.SaveItem(post);
return RedirectToAction("Index", new { id = post.Id });
}
return View(post);
}
public ActionResult Create()
{
return View("Edit", new Post {Date = DateTime.Now});
}
}

The error message says:
"if any entities in the graph have conflicting key values".
Maybe your post is a new element but has associated elements (Author?) that already exist in the database. Then you must also "Attach" those items before adding your Post.
By the way, I think is better don't use directly the entity classes as your model for views.
Its better to use some "PostViewModel" class that exposes the information that must be show and maybe also a separated "EditPostViewModel" with edit data.
Your controller should construct this classes and send it to views and also create your Post entity and send it to the repository (this is better done in a Service class) .

Related

Repository pattern Generic Write Repository issue - Object State Manager Issue

I am doing an MVC project using a repository pattern and I have a core write repository as follows
public abstract class WriteRepository<TContext> : IWriteRepository
where TContext : DbContext, new()
{
private readonly TContext _context;
protected TContext Context { get { return _context; } }
protected WriteRepository()
{
_context = new TContext();
}
public TItem Update<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
{
return PerformAction(item, EntityState.Modified, saveImmediately);
}
public TItem Delete<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
{
return PerformAction(item, EntityState.Deleted, saveImmediately);
}
public TItem Insert<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
{
return PerformAction(item, EntityState.Added, saveImmediately);
}
public void Save()
{
_context.SaveChanges();
}
protected virtual TItem PerformAction<TItem>(TItem item, EntityState entityState, bool saveImmediately = true) where TItem : class, new()
{
_context.Entry(item).State = entityState;
if (saveImmediately)
{
_context.SaveChanges();
}
return item;
}
public void Dispose()
{
_context.Dispose();
}
}
I wanted to update a single field in my db on an action method and I was doing a get all before I could update that value like below
public ActionResult UpdateTenant(string id)
{
Tenant model = new Tenant();
model = _TenantServices.GetItemById(Guid.Parse(id));
model.IsLoginEnabled = true;
_TenantServices.Update(model);
return RedirectToAction("ViewDetails", new { id = model.TenantId });
}
When I do that I am getting an error saying "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
I am using AsNoTracking to retrieve data as follow
public Tenant GetItemById(Guid id)
{
return Context.Tenants.AsNoTracking().Where(t => t.TenantId == id).FirstOrDefault();
}
Any Idea how can I solve this ?
Whenever you retrieve an object from the database, Entity Framework begins tracking (attaches) the object immediately. You will be able to make changes to the retrieved object (i.e. set property values) and call SaveChanges() so that the object will be updated in the database, without the need to set the EntityState.
And in fact, if you attempt to Attach or set the EntityState of an already-tracked object, you will get the error you mentioned above.
So, to resolve the error, you can:
Use one instance of of your TContext to retrieve and another instance to update. In this case, you should attach and set the EntityState in the update method for the changes to get persisted.
Use a single instance of your TContext to retrieve and update, but don't attempt to Attach or to set the EntityState anymore. Call SaveChanges directly after setting the property values.
Use a single instance of your TContext, but when retrieving the record you can call AsNoTracking(). This will allow you to safely Attach or set EntityState during the update.
Hope that helps.

Custom value type, EF Code First and routing

In our WebApi project we use EF CodeFirst approach. Also we use 2 types of databases: SQL Server and MySQL. All tables have the field ID, but in SQL Server database this field has int data type, in MySQL database this field is char(36) and contains GUID.
To solve the problem I created a custom value type like IdType and changed all model classes to use that type insted int:
public class Document
{
public IdType ID { get; set; }
public string DocumentNm { get; set; }
...
}
Then I configured the DbContext (e.g for SQL Server)
modelBuilder.Properties<IdType>().Configure(c => c.HasColumnType("int"));
...and changed repository:
public interface IRepository<T> where T : IEntity
{
IQueryable<T> GetAll();
T GetById(IdType id);
...
}
After that, when I try to go to e.g. http://localhost:7081/api/Document, it gives me an error:
Multiple actions were found that match the request: \r\nGet on type
WebUI.Controllers.API.DocumentController\r\nGetById on type
WebUI.Controllers.API.DocumentController
I use default settings of routing. Here is [HttpGet] methods from DocumentController:
public HttpResponseMessage Get() { ... }
public HttpResponseMessage GetById(IdType id) { ... }
How can I solve the problem? Could this be the cause of incorrect implementation of IdType?
P.S. I created IdType for int values as described here. if I have to add more informations, please let me know.
UPDATE
DocumentController:
public HttpResponseMessage GetById(IdType id)
{
var entity = repository.GetById(id);
if (entity == null)
{
return ErrorMsg(HttpStatusCode.NotFound, string.Format("No {0} with ID = {1}", GenericTypeName, id););
}
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
My repository:
public virtual T GetById(IdType id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
public virtual IQueryable<T> GetAll()
{
return entities = context.Set<T>();
}
It seems that it not implemented yet in current version of Entity Framework
And as mentioned in task on GitHub
we're currently planning to work on lighting this feature up after our
initial RTM of EF7.

Not able delete Entity from another Entity container

//Repository Method
public void Delete(int id)
{
using (var scope = new UnitOfWork(_container))
{
var entity = AuthorService.GetById(id);
scope.Container.Authors.DeleteObject(entity);
}
}
Ninject binding
public class LibraryManagerInjectModule : NinjectModule
{
public override void Load()
{
Bind<LibManagerContainer>().To<LibManagerContainer>().InThreadScope();
}
}
//Author Service Class
public static class AuthorService
{
private static LibManagerContainer Container
{
get { return MF.MF.Get<LibManagerContainer>(); }
}
public static Author GetById(int id)
{
using (var scope = new UnitOfWork(Container))
{
return Container.Authors.SingleOrDefault(x => x.Id == id);
}
}
public static Author GetByName(String name)
{
using (var scope = new UnitOfWork(Container))
{
return Container.Authors.SingleOrDefault(x => x.Name == name);
}
}
}
Using this code i m not able to delete the entity from database. it show me an error that entity not belong to same object state manager but i create the libContainer object inThreadscope but never able to delete the entity.
You don't need to load entity from db to delete record. Id is enough to know, and it resolves another context problem:
var employer = new Employ { Id = 1 };
ctx.Employ.Attach(employer);
ctx.Employ.Remove(employer);
ctx.SaveChanges();
Taken from here: Delete a single record from Entity Framework?
P.S. Something wrong with your architecture. A single context should be used within one scope.
The DataContext you use to retrieve the entity tracks the entity, to be aware of changes to it. Because of that, you are not able to save entities retrieved from one DataContext (or UnitOfWork in your case) using another.
As you mentioned in the comments, deleting should be another transaction. To achieve this you should delete by id, not the object.
Just add a RemoveById method to AuthorService:
public static class AuthorService
{
...
public static void RemoveById(int id)
{
using (var scope = new UnitOfWork(Container))
{
var author = Container.Authors.SingleOrDefault(x => x.Id == id);
Container.Authors.Remove(author);
}
}
...

ASP.NET MVC 3 Database Context Querying

I have my Database Context:
public class ProductContext : DbContext
{
public ProductContext() : base ("DefaultConnection") {}
public DbSet<Product> Products {get;set;}
}
and my Repository:
public class ProductRepository : IProductRepository
{
private ProductContext _dbContext = new ProductContext();
public IQueryable<Product> Products { get { return _dbContext.Products; } }
}
when I query my database in the Edit Action:
public ActionResult Edit(Guid id)
{
var item = _repository.Products.FirstOrDefault(x => x.Id.Equals(id));
return View(item);
}
I would usually use a ViewModel but this is purely to show the scenario.
When I query the database using the var item line, does EntityFramework change the state of that item.
Can I pass around that item through a multitude of Services in the Service Layer and then finally save it using my method:
public void SaveEntity<TEntity>(TEntity entityToSave) where TEntity : DbEntity
{
if (entityToSave.Id.Equals(Guid.Empty))
_dbContext.Set<TEntity>().Add(entityToSave);
else
_dbContext.Entry<TEntity>(entityToSave).State = EntityState.Modified;
_dbContext.SaveChanges();
}
It won't throw an exception saying that there is already a Entity with the same Id as the one you're trying to Save?
So after trial and error, it seems that this works perfectly fine, and it doesn't bring back any errors. There is one thing to look out for:
This navigation property:
public virtual Category Category { get;set; }
public Guid CategoryId { get;set; }
That could reside in the Product model has a little gotcha, that is:
When editing or saving a new Product, you should only set the CategoryId and not just the Category exclusively because you will get duplicate Category entries every time you edit or save if you use the a Category that already exist within the database...
I think you should the navigation property solely for your ease, not for use when modifying entities...

Saving in entity framework

I have read this article and still misunderstanding key moments. Don't we need call
_context.SaveChanges()
in every Delete/Update/... operations?
If I change property of any entity does SaveChanges() submitted result to database or I must manually set EntityState.Modifyed?
Here is my code:
public class Repository<T> : IRepository<T>
where T : class
{
private IDbContext _context;
public Repository(IDbContext context)
{
_context = context;
}
private IDbSet<T> DbSet
{
get
{
return _context.Set<T>();
}
}
#region IRepository<T> Members
public void Insert(T entity)
{
DbSet.Add(entity);
}
public void Delete(T entity)
{
DbSet.Remove(entity);
}
public IQueryable<T> SearchFor(Expression<Func<T, bool>> predicate)
{
return DbSet.Where(predicate);
}
public IQueryable<T> GetAll()
{
return DbSet;
}
public T GetById(int id)
{
return DbSet.Find(id);
}
#endregion
}
public interface IDbContext
{
IDbSet<T> Set<T>() where T : class;
int SaveChanges();
void Dispose();
}
You ask:
Don't we need call
_context.SaveChanges()
in every Delete/Update/... operations?
No we don't. When calling Delete we don't accually delete the entity - we mark it for deletion.
Same thing with Update, although you dont have to do anything other that make the changes you want to the entity. All properties (generated by the default template) will implement INotifyPropertyChanged so it knows when a entity is modified.
All entities (in database first - autogenerated by defullt template) have a State property. This property is maintained by the ObjectContext as long as the chages take place within the scope of the ObjectEntity.
e.g.
Customer c;
using(var context = new MyEntityContext())
{
c = context.Customer.FirstOrDefault(); //state is now Unchanged
c.Name = "new name"; // this set the State to Modified
//context.SaveChanges(); // will persist the data to the store, and set the State back to unchaged
}
//if we look at our customer outside the scope of our context
//it's State will be Detacth
Console.WriteLine(c.State);
Then you call SaveChanges all entites that have a state of Added Deleted or Modified will have their changes persisted to the database in a local transaction
EDIT
If an entity is marked for deletion, and you try to modify it - you will get an InvalidOperationException
You can perform many changes in your in-memory context,
such as inserts, updates and deletes.
Once you call SaveCahnges() all the changes you've made will be saved
in the DB at a single transaction.
This means that eiteher they are all submited, or none of them
in case of an error.

Categories