I have a generic repository:
public class GenericRepository<TEntity> : AbstractRepository<TEntity>, IRepository<TEntity> where TEntity : class
{
private DbContext _context;
[...]
public GenericRepository(DbContext context)
{
_context = context;
context.Configuration.AutoDetectChangesEnabled = true;
_dbSet = _context.Set<TEntity>();
}
[...]
public void SaveChanges()
{
_context.SaveChanges();
}
[...]
public void Add(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_dbSet.Add(entity);
}
[...]
public virtual void Update(TEntity entity)
{
_context.Entry(entity).State = EntityState.Modified;
}
In my controller, there is this code:
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
if (project.Id == 0)
{
ProjectRepository.Add(project);
}
else
{
ProjectRepository.Update(project);
}
ProjectRepository.SaveChanges();
[...]
Selecting and Inserting works fine, but Updating not: I get an InvalidOperationException (english translation of the german error message is "An object with the same key already exists in the Object State Manager. The Object State Manager can not track multiple objects with the same key.").
I don't understand that, because I'm the only user on my development machine, and I did not modify the record at another place.
Any idea what I'm doing wrong here?
Take a look at these answers:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
"An object with the same key already exists in the ObjectStateManager..." exception is thrown when setting an entity state to modified
Basically you need to do this:
var entity = _context.Projects.Find(project.ProjectId);
_context.Entry(entity).CurrentValues.SetValues(project);
Hopefully this helps.
The project instance is created by model binding, not loaded from your repository, so you need to actually load a project instance from the repository, and then change the properties on that.
Disclaimer: i never used Entity Framework, i am writing this based on my experience with ASP.NET MVC and nHibernate but you should be able to apply the same pattern
Ok first off your actual problem is in fact a double key insert, this is the case because the project-object which get passed to your edit action is not the same object you are trying to update (this one has been created from your modelbinder based on the values in the FormValueProvider). it may has the excat same values and therefor the same id but for your ORM it`s a brand new object which has never been persisted to the DB.
you can prevent this by using the following pattern (quick'n dirty sample-code)
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
if (project.Id == 0)
{
ProjectRepository.Add(project);
}
else
{
Project dbProject = ProjectRepository.GetById(project.Id); //or whatever your method to retrieve entities by Id is named
UpdateModel(dbProject, "project"); //http://msdn.microsoft.com/en-us/library/dd470933.aspx
ProjectRepository.Update(project);
}
}
}
This is my solution for this problem in EF Core. Not sure if it can be done on EF6.
Repository Interface
public interface IRepository<TEntity> where TEntity : class
{
void Update(TEntity entity);
// I ommited the rest of the methos for simplicity
}
Repository Implementation
public class GenericRepository<T> : IRepository<T> where T : class
{
public void Update(T entity)
{
dbContext.Set<T>().Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
// don't forget to dbContext.SaveChanges() here if needed
}
}
Related
I have a windows forms application where I am trying to use dependency injection for some services, so I did the following configuration initially in Program.cs I register the services:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var services = new ServiceCollection();
ConfigureServices(services);
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
var mainForm = serviceProvider.GetRequiredService<SelecionarEmpresa>();
Application.Run(mainForm);
}
}
private static void ConfigureServices(ServiceCollection services)
{
services.AddDbContext<AnalistDbContext>();
services.AddSingleton<MainForm>();
services.AddScoped<Form1>();
services.AddScoped<Form2>();
services.AddScoped<Form3>();
services.AddTransient<IEmpRepository, EmpRepository>();
services.AddTransient<ISisRepository, SisRepository>();
}
}
So far everything working, there are 3 Forms I made to test the functionality, in Form1 I inject the services I need:
private readonly ISisRepository _sisRepository;
private readonly IEmpRepository _empRepository;
public Form1(ISisRepository sistRepository,
IEmpRepository empRepository)
{
_sisRepository= sistRepository;
_empRepository = empRepository;
InitializeComponent();
}
And the idea is to use, for example, _sisRepository to update a record, the first time I save it works, if I click to save again, an exception is raised, before putting the exception I already inform you that I am using a generic repository, which is the next:
public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : Entity, new()
{
protected readonly AnalistDbContext Db;
protected readonly DbSet<TEntity> DbSet;
protected Repository(AnalistDbContext db)
{
Db = db;
DbSet = db.Set<TEntity>();
Db.ChangeTracker.AutoDetectChangesEnabled = false;
var existeBanco = (Db.Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator).Exists();
if (!existeBanco)
{
(Db.Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator).Create();
(Db.Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator).CreateTables();
}
}
public IEnumerable<TEntity> Buscar(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Where(predicate).AsNoTracking().ToList();
}
public virtual TEntity ObterPorId(Guid id)
{
return DbSet.AsNoTracking().FirstOrDefault(s => s.Id == id);
}
public virtual List<TEntity> ObterTodos()
{
return DbSet.AsNoTracking().ToList();
}
public void Adicionar(TEntity entity)
{
DbSet.Add(entity);
SaveChanges();
}
public void Atualizar(TEntity entity)
{
DbSet.Update(entity);
SaveChanges();
}
public void Remover(Guid id)
{
DbSet.Remove(new TEntity { Id = id });
SaveChanges();
}
public int SaveChanges()
{
return Db.SaveChanges();
}
public void Dispose()
{
Db?.Dispose();
}
}
The exception is:
Despite the clarity of the message, I could not understand if this is a problem caused by the use of dependency injection in the Form's constructor and having caused some problem in relation to the repository instance, it seems that my object is still the same as instantiated earlier, because as I said, this error only occurs on the second call. If that is really the problem, how could you solve it? Or when it comes to windows forms, would I abandon the use of dependency injection?
When you update an item, the DbContext starts tracking that item. When you do a second update, the same DbContext tries to track another item with the same id. It's not an issue with the dependency injection, it's an issue with the lifetime of the injected AnalistDbContext and the objects tracked by it.
Typically, a DbContext would be instantiated on a per-operation basis, instead of living for the duration of the entire form. This helps prevent inconsistencies when two users are trying to modify the same entity. You could try:
Update Form1 to use a factory that can create repositories, and dispose the repository after using it
Update Repository to use a factory that can create an AnalistDbContext, and dispose the context after using it
If you think you're going to be the only user updating the entry, and aren't worried about race conditions or anything like that, you can check if the entry is already being tracked by the db context before trying to attach it.
I would like to create a Details view with entity framework data using a repository pattern.
This is my interface repository:
public interface InterfaceRepositroy: IDisposable
{
IEnumerable<SubjectContent> GetAll();
SubjectContent Get(string id);
}
This is the toher repository:
public class SubjectRepository : InterfaceRepositroy,IDisposable
{
private irfwebpage20161013070934_dbEntities2 db;
public IEnumerable<SubjectContent> GetAll()
{
return db.Set<SubjectContent>().ToList();
}
public SubjectContent Get(string id)
{
return db.Set<SubjectContent>().Find(id);
}
public void Dispose()
{
throw new NotImplementedException();
}
}
Here is my controller:
private InterfaceRepositroy subjectreposi;
public ActionResult Details(string id)
{
SubjectContent subject = subjectreposi.Get(id);
return View(subject);
}
My View is a standard details template.
It gets an error at this point in the controller:
SubjectContent subject = subjectreposi.Get(id);
I would really appreciate the help. This is like the fourth version of a repository pattern i am trying to implement but none of them worked so far. I have tried it without interface, with the instance of the subjecrepository or with different linq to sql in the repository. It either gets http error or it doesnt show the data just the names of the data.
Create constructors that initialise your data context:
public SubjectRepository()
{
db = new irfwebpage20161013070934_dbEntities2();
}
public SubjectRepository(irfwebpage20161013070934_dbEntities2 dbContext)
{
db = dbContext;
}
This allows you to either initialise your repository with no parameters which will initialise you data context or specify your own data context.
You can now use this like this:
var repo = new SubjectRepository();
SubjectContent subject = repo.Get(id);
I want to create an abstraction layer between Entity Framework and the rest of my application. But I am having a few problems with Entity Framework.
Basically (I don't show you all the interface layers that I've created too), I've split my application into several projects like this :
Domain
Contains my domain object, an abstraction of my datastorage object
DAL
Creates a link between my datastorage and my business layer. Contains two types of elements :
Private ones : my EDMX, my database object, and some other generated objects providing me some useful methods like ToDomain/ToEntity
Public ones : my Data Access Object, providing CRUD methods
Business
Contains the logic of my application. Only knows about the public elements of the DAL and the Domain Layer.
Presentation
Presents the domain objects for the user. Only knows about the business layer.
As I said, I want to create an abstraction of my datastorage objects (in my case Database object, but I want a solution that works also for file or WCF storage for example) so that my business layer don't know anything about my DAL implementation.
Here is a glimpse of what I've done in my DAL :
public abstract class GenericDao<TEntity, TDomain, TDbContext> : IGenericDao<TDomain>
where TDbContext : DbContext, new()
where TEntity : class
where TDomain : class
{
protected TDbContext _context;
protected DbSet<TEntity> _dbSet;
public GenericDao(TDbContext dbContext)
{
this._context = dbContext;
this._dbSet = dbContext.Set<TEntity>();
}
public TDomain Create()
{
return this.ToDomain(this._dbSet.Create());
}
public IList<TDomain> GetAll()
{
return this._dbSet.ToList().Select(entity => this.ToDomain(entity)).ToList();
}
public void Update(TDomain domain)
{
var entity = this.ToEntity(domain);
var entry = this._context.Entry(entity);
entry.State = EntityState.Modified;
}
public void Remove(TDomain domain)
{
_dbSet.Remove(this.ToEntity(domain));
}
protected abstract TDomain ToDomain(TEntity entity);
protected abstract TEntity ToEntity(TDomain domain);
}
You will probably see what's wrong with my code by reading it: when I try to delete or update an entity, I am not manipulating an entity attached to Entity Framework. If I try to attach my entity to the dbContext, it fails because there is already an entity in the context with the same id.
I already thought about several solutions, but none of them please me.
Maybe am I doing something wrong in my approach? I am a little bit confused about the Repository and DAO pattern (I read anything and the very opposite about that difference on the internet).
You have two options:
initialize new dbcontext for each operation and dispose it when operation is ended:
public abstract class GenericDao<TEntity, TDomain, TDbContext> : IGenericDao<TDomain>
where TDbContext : DbContext, new()
where TEntity : class
where TDomain : class
{
protected Func<TDbContext> _contextFactory;
public GenericDao(Func<TDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public TDomain Create()
{
using(var context = _contextFactory())
{
return context.Set<TEntity>().Create();
}
}
public IList<TDomain> GetAll()
{
using(var context = _contextFactory())
{
return context.Set<TEntity>().ToList()
.Select(entity => this.ToDomain(entity)).ToList();
}
}
public void Update(TDomain domain)
{
using(var context = _contextFactory())
{
var entity = this.ToEntity(domain);
context.Attach(entity);
var entry = this._context.Entry(entity);
entry.State = EntityState.Modified;
context.SaveChanges();
}
}
public void Remove(TDomain domain)
{
using(var context = _contextFactory())
{
var entity = this.ToEntity(domain);
context.Attach(entity);
context.Set<TEntity>.Remove(entity);
context.SaveChanges();
}
}
protected abstract TDomain ToDomain(TEntity entity);
protected abstract TEntity ToEntity(TDomain domain);
}
or you can try to find entity in your instance of dbcontext using property Local of DbSet:
var contextEntity = context.Set<TEntity>().Local
.Where(c=>c.Id == entity.Id).FirstOrDefault();
You seem to be getting stuck coding to an implementation within your abstraction. If you injected an interface to your generic rather than a concrete type (like EF) then your GenericDao becomes much more flexible. You can inject whatever implementation you choose providing it implements the required interface. In your case, WCF, File, Whatever. For example;
protected IDbContext _context;
public GenericDao(IDbContext dbContext)
{
this._context = dbContext;
}
public void Remove(TDomain domain)
{
_context.Remove(this.ToEntity(domain));
}
//abstraction
public interface IDbContext
{
void Remove(Entity entity);
}
//EF Implementation
public MyEfClass : IDbContext
{
public void Remove(Entity entity)
{
//code to remove for EF example
context.Attach(entity);
context.State = EntityState.Modified;
context.Set<TEntity>.Remove(entity);
context.SaveChanges();
}
}
//WCF Implementation
public MyWCFClass : IDbContext
{
public void Remove(Entity entity)
{
//Wcf implementation here
}
}
//File example
public FileWriter : IDbContext
{
public void Remove(Entity entity)
{
LoadFile();
FindEntry(entity);
WriteFile(entity);
SaveFile();
}
public void LoadFile()
{
//use app settings for file directory
}
}
I've got some models that only have two fields: Id and Name. All this models inherit from IDbDictionary interface.
My goal is to make "universal" controller for CRUD operations for this models. My problem is how to (using EF) modify database table by name. For example, there is method
[HttpPost]
public ActionResult Create(IDbDictionary newRecord, string collectionName)
{
if (ModelState.IsValid)
{
db.<collectionName>.Add(newRecord);
db.SaveChanges();
return View("Success");
}
return View("Create", newRecord);
}
Is there a way to do it the way I described? I thought about reflection, but I've no idea how to do this.
Regards,
C
Usually you would have a Service that would handle your generic database operations which you can call from all your methods. Example:
public class DataService
{
public readonly ApplicationDbContext dbContext;
public DataService(ApplicationDbContext dbContext)
{
this.dbContext = dbContext;
}
public void Create<TEntity>(TEntity entity) where TEntity : IDbDictionary
{
this.dbContext.Set<TEntity>().Add(entity);
this.dbContext.SaveChanges();
}
}
Then in your method you would use:
var dataService = new DataService(this.dbContext);
dataService.Create<ClassName>(newEntity);
I am developing some extensions methods to add some funcionalities for DbSet. However, when creating an "Update" method, I need the DbSet's DbContext to be able to modify the state of a entity. The current implementation:
public void Update<TEntity>(this DbSet<TEntity> repository, TEntity entity) where TEntity : class
{
repository.Attach(entity);
var context = // how get the context from repository?
((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
Does any one know how to get a DbContext from a DbSet instance?
I've found a better way to accomplish that:
public static void MarkAsModified(this DbContext context, object entity)
{
context.Entry(entity).State = EntityState.Modified;
}