I am using Entity Framework and IUnitOfWork to not repeat code, my Add is Ok, the problem is to do Update.
I get this error:
Attaching an entity of type 'ViewModelTicket' 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.
I am using this BaseContext class:
public class BaseContext<T> : DbContext where T : class
{
public DbSet<T> DbSet { get; set; }
public BaseContext() : base("DefaultConnection")
{
//Caso a base de dados não tenha sido criada,
//ao iniciar a aplicação iremos criar
Database.SetInitializer<BaseContext<T>>(null);
}
public virtual void ChangeObjectState(object model, EntityState state)
{
//Aqui trocamos o estado do objeto,
//facilita quando temos alterações e exclusões
((IObjectContextAdapter)this)
.ObjectContext
.ObjectStateManager
.ChangeObjectState(model, state);
}
public virtual int Save(T model)
{
this.DbSet.Add(model);
return this.SaveChanges();
}
public virtual int Update(T model)
{
var entry = this.Entry(model);
if (entry.State == EntityState.Detached)
this.DbSet.Attach(model);
this.ChangeObjectState(model, EntityState.Modified);
return this.SaveChanges();
}
public virtual T GetById(object id)
{
return this.DbSet.Find(id);
}
}
The IUnitWork interface:
public interface IUnitOfWork<T> where T : class
{
int Save(T model);
int Update(T model);
void Delete(T model);
IEnumerable<T> GetAll();
T GetById(object id);
int Max();
IEnumerable<T> Where(Expression<Func<T, bool>> expression);
IEnumerable<T> OrderBy(Expression<System.Func<T, bool>> expression);
}
The controller method:
public ActionResult SavedTicket(Models.Ticket.ViewModelTicket ticket)
{
if (ModelState.IsValid)
{
if (this.UnitOfTicket.GetById(ticket.TicketId) == null) {
ticket.TicketId = Convert.ToInt32( UnitOfTicket.Max());
ticket.OpenDateAndTime = DateTime.Now;
this.UnitOfTicket.Save(ticket);
} else {
this.UnitOfTicket.Update(ticket);
}
return RedirectToAction(nameof(NewTicket));
}
else {
return View(nameof(NewTicket), ticket);
}
}
Thank you
EDIT
I solved the problem with this.
public virtual int Update(T model, int id)
{
var entity = DbSet.Find(id);
this.Entry(entity).CurrentValues.SetValues(model);
return this.SaveChanges();
}
Thank you guys for helping me.
Shouldn't you generate the next higher Id when inserting a new entity?
// vvv
ticket.TicketId = Convert.ToInt32( UnitOfTicket.Max() + 1 );
Related
Premisse:
I am folowing IUnitOfWork Patterns I created a Base class with my methods to persist data.
My Problem:
I found a problem to write a method Next Number of table from SQLServer, because of this, I am repeating this method in every class .
Classes:
BaseContext Class:
public class BaseContext<T> : DbContext where T : class
{
public DbSet<T> DbSet
{
get;
set;
}
public BaseContext() : base("DefaultConnection")
{
Database.SetInitializer<BaseContext<T>>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// Class Mapping from IMapping
var typesToMapping = (from x in Assembly.GetExecutingAssembly().GetTypes()
where x.IsClass && typeof(IMapping).IsAssignableFrom(x)
select x).ToList();
foreach (var mapping in typesToMapping)
{
dynamic mappingClass = Activator.CreateInstance(mapping);
modelBuilder.Configurations.Add(mappingClass);
}
}
public virtual void ChangeObjectState(object model, EntityState state)
{
((IObjectContextAdapter)this)
.ObjectContext
.ObjectStateManager
.ChangeObjectState(model, state);
}
// Implement IUnitOfWork
public virtual int Save(T model)
{
this.DbSet.Add(model);
this.ChangeObjectState(model, EntityState.Added);
return this.SaveChanges();
}
public virtual int Update(T model, int id)
{
var entity = DbSet.Find(id);
this.Entry(entity).CurrentValues.SetValues(model);
return this.SaveChanges();
}
public virtual void Delete(T model)
{
var entry = this.Entry(model);
if (entry.State == EntityState.Detached)
this.DbSet.Attach(model);
this.ChangeObjectState(model, EntityState.Deleted);
this.SaveChanges();
}
public virtual IEnumerable<T> GetAll()
{
return this.DbSet.ToList();
}
public virtual T GetById(object id)
{
return this.DbSet.Find(id);
}
public virtual IEnumerable<T> Where(Expression<Func<T, bool>> expression)
{
return this.DbSet.Where(expression);
}
public IEnumerable<T> OrderBy(Expression<Func<T, bool>> expression)
{
return this.DbSet.OrderBy(expression);
}
}
My method NextNumber in every class:
public class VersionDao : BaseContext<Version>, IUnitOfWork<Version>
{
public int Next() => DbSet.Max(x => x.VersionId) + 1;
}
public class TicketDao : BaseContext<ViewModelTicket> , IUnitOfWork<ViewModelTicket>
{
public int Next() => DbSet.Max(x => x.TicketId) + 1;
}
public class CompanyDao : BaseContext<Company>, IUnitOfWork<Company>
{
public int Next() => DbSet.Max(x => x.CompanyId) + 1;
}
Solicitation:
I need a suggestions to stop repeatition on Next method in every class.
Thank you
There is no need to implement Unit of Work pattern if you are using Entity Framework. If you click F12/Go To Definition on DbContext you'll see the following summary
A DbContext instance represents a combination of the Unit Of Work and
Repository patterns such that it can be used to query from a database
and group together changes that will then be written back to the store
as a unit. DbContext is conceptually similar to ObjectContext.
I am using Entity Framework 6 and I have a repository looking like the following with the Add and Update methods removed to make it shorter:
public class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("An instance of DbContext is required to use this repository", "context");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
protected DbContext DbContext { get; set; }
protected DbSet<T> DbSet { get; set; }
public virtual IQueryable<T> Find(Expression<Func<T, bool>> predicate)
{
return DbSet.Where<T>(predicate);
}
public virtual IQueryable<T> GetAll()
{
return DbSet;
}
public virtual T GetById(int id)
{
//return DbSet.FirstOrDefault(PredicateBuilder.GetByIdPredicate<T>(id));
return DbSet.Find(id);
}
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
DbSet.Attach(entity);
DbSet.Remove(entity);
}
}
public virtual void Delete(int id)
{
var entity = GetById(id);
if (entity == null) return; // not found; assume already deleted.
Delete(entity);
}
}
In my controller I call the repository like this:
public HttpResponseMessage DeleteTest(int id)
{
Test test = _uow.Tests.GetById(id);
if (test == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
try
{
_uow.Tests.Delete(test);
_uow.Commit();
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
}
This works for a single test but how can I delete for example all tests that have an examId column value of 1 being
that examId is one of the columns in the Test table.
You can create another delete method in your generic repository class, see below:
public virtual void Delete(Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = DbSet.Where(predicate).AsQueryable();
foreach (T obj in query)
{
DbSet.Remove(obj);
}
}
Then you can use it like below, it will delete all records which Id equalsid.
_uow.Test.Delete(n => n.Id = id)
I'm not sure if EF is able to handle multiple delete now given a certain value, but the last time I did this I had to resort to a loop.
public HttpResponseMessage DeleteTest(int id)
{
var testList = _uow.Tests.GetAll().Where(o => o.Id == id);
if (testList.Count() == 0)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
try
{
foreach (var test in testList)
{
_uow.Tests.Delete(test);
}
_uow.Commit();
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
}
If the "test" table is a foreign table linked to a primary table on the "ID" column, you may want to consider doing a cascading delete in this case.
You can use RemoveRange()
public virtual void Delete(Expression<Func<T,bool>> predicate)
{
var query = Context.Set<T>().Where(predicate);
Context.Set<T>().RemoveRange(query);
}
I am using this template for my project
public interface IUnitOfWork
{
IDbSet<TEntity> Set<TEntity>() where TEntity : class;
int SaveChanges();
void RejectChanges();
DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
}
Implementation:
public class BookStoreDbContext : DbContext, IUnitOfWork
{
public DbSet<Categori> Categoris { get; set; }
public new DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class
{
return base.Entry(entity);
}
public override int SaveChanges()
{
return base.SaveChanges();
}
Controler:
public class CategoriController : Controller
{
private IUnitOfWork _uw;
private ICategoriService _categoriService;
public CategoriController(IUnitOfWork uw,ICategoriService categoriservice )
{
_uw = uw;
_categoriService = categoriservice;
}
public ActionResult Edit(int id = 0)
{
var categori = _categoriService.Find(i => i.Id == id);
if (categori == null)
{
return HttpNotFound();
}
return View(categori);
}
[HttpPost]
public ActionResult Edit(Categori categori)
{
if (ModelState.IsValid)
{
_uw.Entry(categori).State = EntityState.Modified;
_uw.SaveChanges();
}
return View(categori);
}
}
Repository or Servis layer:
public interface IGenericService<T> : IDisposable where T : class
{
void Add(T entity);
void Delete(T entity);
T Find(Func<T, bool> predicate);
IList<T> GetAll();
IList<T> GetAll(Func<T, bool> predicate);
}
public interface ICategoriService : IGenericService<DomainClasses.Models.Categori>
{
}
impliment repository:
public class EfGenericService<TEntity> : IGenericService<TEntity> where TEntity : class
{
protected IUnitOfWork _uow;
protected IDbSet<TEntity> _tEntities;
public EfGenericService(IUnitOfWork uow)
{
_uow = uow;
_tEntities = _uow.Set<TEntity>();
}
public virtual void Add(TEntity entity)
{
_tEntities.Add(entity);
}
public void Delete(TEntity entity)
{
_tEntities.Remove(entity);
}
public TEntity Find(Func<TEntity, bool> predicate)
{
return _tEntities.Where(predicate).FirstOrDefault();
}
public IList<TEntity> GetAll()
{
return _tEntities.ToList();
}
public IList<TEntity> GetAll(Func<TEntity, bool> predicate)
{
return _tEntities.Where(predicate).ToList();
}
public class EfCategoriService : EfGenericService<Categori>,ICategoriService
{
public EfCategoriService(IUnitOfWork uow)
: base(uow)
{
}
}
Global.asax
private static void InitStructureMap()
{
ObjectFactory.Initialize(
x =>
{
x.For<IUnitOfWork>().HttpContextScoped().Use(() => new BookStoreDbContext());
x.ForRequestedType<ServiceLayer.Interfaces.ICategoriService>()
.TheDefaultIsConcreteType<EfCategoriService>();
}
But I get this error when update entity:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries
Please help me to resolve this error?
The only relevant lines in your snippets are:
_uw.Entry(categori).State = EntityState.Modified;
_uw.SaveChanges();
Now, look at the exception you get:
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded.
Does setting the entity state to Modified insert an entity? No.
Does it delete an entity? No.
Does it update an entity? Yes.
May the entity that EF tries to update have been deleted? Well, perhaps. How to check that? When an entity is deleted the database must know the key in order to know which row to delete. To confirm if the key is correct use a debugger in your controller post action, inspect the key value of categori that is passed into the method. Does it have the expected value? If not, you probably have a problem in your view or with binding the form and route values to the categori model. If yes, check in the database if the entity with that key is in the database table. If yes, next point.
May the entity have been modified? It could happen that EF "thinks" it has been modified in the database (even if it hasn't) if you have marked another property in your Categori model as a concurrency token. If that property has changed in the database or in the view between loading the entity in the GET request and reattaching (setting the state to Modified) and SaveChanges in the POST request you'll get a concurrency violation.
Priority has the test in bold above because it is the most likely cause of the problem in my opinion. If it turns out that the key doesn't have the expected value better ask a new question because it will be a pure ASP.NET MVC question that has nothing to do with EF and your UOW and service architecture.
I have something like this:
public void Delete(T entity)
{
Context.DeleteObject(entity);
Context.SaveChanges();
}
I end up wit a exception:
"The object cannot be deleted because it was not found in the ObjectStateManager."
If I try to add the entity to objectContext with AttachTo() I get:
"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
Whats wrong?
Example:
namespace CAFM.Data.Repository
{
public abstract class RepositoryBase<T> : IRepository<T>
where T : EntityObject
{
public RepositoryBase()
: this(new CAFMRepositoryContext())
{
}
static RepositoryBase()
{
}
public RepositoryBase(IRepositoryContext repositoryContext)
{
_context = repositoryContext ?? new CAFMRepositoryContext();
_entity = _repositoryContext.ObjectContext.CreateObjectSet<T>();
}
private readonly ObjectContext _context;
private readonly ObjectSet<T> _entity;
protected ObjectContext Context
{
get { return _context; }
}
protected IObjectSet<T> Entity
{
get { return _entity; }
}
#region IRepository Members
private string GetEntityName()
{
return string.Format("{0}.{1}", _entity.EntitySet.EntityContainer, _entity.EntitySet.Name);
}
public T Add(T entity)
{
var fqen = GetEntityName();
Context.AddObject(fqen, entity);
Context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
return entity;
}
public T Update(T entity)
{
Context.ApplyCurrentValues(GetEntityName(), entity);
Context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
return entity;
}
public void Delete(T entity)
{
Context.DeleteObject(entity);
Context.SaveChanges();
}
#endregion
}
}
You have to fetch the entity you wish to delete from your context first. Best to do this with a comparison of the primary key. It could look like this, but i do not know the object structure of TabMaster and TabMasterViewModel, so the properties may be wrong named.
public void Delete(TabMasterViewModel entity) {
TabMaster des = _tabmasterRepository.FirstOrDefault( e.Id = entity.ID );
if (des != null) {
_tabmasterRepository.Delete(des);
}
}
You have created a new Entity and mapped the values from your view model to that entity. But the context does not know of the entity, so he could not delete it.
You could just "Attach" the object to the current context like that:
public void Delete(T entity)
{
context.AttachTo(entity.EntityKey.EntitySetName, entity);
Context.DeleteObject(entity);
Context.SaveChanges();
}
how to use following function
Generic Function:
public T GetSingle(Expression<Func<T, bool>> whereCondition)
{
return this.ObjectSet.Where(whereCondition).FirstOrDefault<>();
}
Business logic wise:
//Now in the following function i would like to call Generic function.
public TabMasterViewModel GetSingle(Expression<Func<TabMasterViewModel, bool>> whereCondition)
{
_tabmasterRepository.GetSingle( .. what should be here.. );
}
//Calling function from Controller level.
public ActionResult Details(int id)
{
return View(_tabmasterService.GetSingle(x => x.colID == id));
}
I could not able to use the function, please suggest.
_tabmasterRepository.GetSingle( .. what should be here.. );
Thanks,
Imdadhusen
Either you modify your first generic function as
public T GetSingle(Expression<Func<T, bool>> whereCondition)
{
return context.CreateObjectSet<T>().Where(whereCondition).FirstOrDefault();
}
or create a genetic repository
public class RepositoryGeneric<TEntity>
{
public RepositoryGeneric(Context context)
{
Context = context;
}
protected ObjectContext Context { get; private set; }
protected virtual ObjectSet<TEntity> ObjectSet
{
get { return Context.CreateObjectSet<TEntity>(); }
}
public virtual TEntity GetByKey(params object[] keys)
{
return DbSet.Find(keys);
}
public TEntity GetSingle(Expression<Func<TEntity, bool>> whereCondition)
{
return ObjectSet.Where(whereCondition).FirstOrDefault();
}
}
Edit:
using the generic function
TabMasterViewModel model = _tabmasterService.GetSingle(x => x.colID == id);
or using generic repository
var tabmasterRepository = new RepositoryGeneric<TabMasterViewModel>(new Context());
var model = tabmasterRepository.GetSingle(x => x.colID == id);