Repository pattern Generic Write Repository issue - Object State Manager Issue - c#

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.

Related

get Scope ID in entity framework 6 after saving data

I have implemented repository pattern and unit of work on top of Data Access Layer. I have all the crud operation in Generic Repository but save method in unit of work. In my business class I am passing object to generic class, followed by Save method in unit of work, my question is how can i get scope ID from this point forward
Basically I need to get ID of object after saving where I don't know what is object ID name as I am using generic class to save data
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
protected DbSet<TEntity> _DbSet;
private readonly DbContext _dbContext;
public GenericRepository()
{ }
public GenericRepository(DbContext dbContext)
{
this._dbContext = dbContext;
_DbSet = _dbContext.Set<TEntity>();
}
public void InsertEntity(TEntity obj)
{
_DbSet.Add(obj);
}
}
...
public class FunctionsNavigation_UnitOfWork : IDisposable
{
private FunctionContext _FunctionContext = new FunctionContext();
uow.Qualification_FeeSchemeRepository.InsertEntity(obj);
}
}
public void Save()
{
_FunctionContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
_FunctionContext.SaveChanges();
}
in following business class after saving I try to get ID on object but I am not getting any thing
_uof.Sys_Nav_Functions_Repository.InsertEntity(_Sys_Nav_FunctionEntity);
_uof.Save();
_FunctionID = _obj.Sys_Nav_Function.Function_ID;
You will get the inserted Id at the object itself.
private FunctionContext _FunctionContext = new FunctionContext();
var obj = new yourEntity();
uow.Qualification_FeeSchemeRepository.InsertEntity(obj);
uow.Save();
Once you save the data into the database. The entity-framework will fill the entity type PK that is generated from the database.
In short you get here
int id = obj.Id;
Update Inserting 1:1 Relationship Sample
Person person = new Person//create person entity
{
FirstName = "Eldho",
LastName = "Abe",
};
AuthorizedUser user = new AuthorizedUser//create authorized user role entity
{
Person = person, //The reference of newly inserted user
UserId = myUserid,
HashedPassword = password,
};
uow.PersonDA.Insert(person);
uow.AuthorizedUserDA.Insert(user);
uow.Save();//insert to database

Why does my database not save with EF?

I am using EF to update my database.
I first query the database based upon some criteria. This returns the record I am expecting (or returns null). Naturally during this query, there is a slight delay as it executes the query.
I then change a property in this case a nullable boolean from false to true or create a new entry.
I then add this object to my Entities, and finally choose myEntitiy.SaveChanges(). This does not seem to execute. There is no delay. Yet the code passes to the next line, so no exception is thrown.
This is what I have
public class MyCompany : AbstractCompany
{
public override bool UpdateStatus(string emailAddress, bool isOptIn)
{
var personDetail = (from a in base.Entities.MyCompanyTable
where a.EmailAddress == emailAddress
select a).SingleOrDefault();
if (personDetail == null)
{
personDetail = new MyCompanyTable();
personDetail.EmailAddress = emailAddress;
}
personDetail.IsSubscribed = isOptIn;
base.Entities.MyCompanyTable.Add(personDetail); //also tried attach, same issue over
return base.SaveData();
}
}
And my base class is
public abstract bool UpdateStatus(string emailAddress, bool isOptIn);
protected Entities Entities
{
get { return new Entities(); }
}
protected bool SaveData()
{
var x = Entities.GetValidationErrors();//returns 0 items
try
{
Entities.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
What have I done wrong?
Entity Framework uses change tracking to detect changes in any entities in the current context.
However, your Entities property instantiates a new instance of your context everytime it is called. So when you query you use one context and then when you save you use another! EF has no way to detect that you made any changes!
It would be best to instantiate your context in the base class' constructor:
public abstract class BaseClass
{
protected BaseClass()
{
Entities = new Entities();
}
protected Entities Entities { get; private set; }
}
That should fix it up.

Cannot modified the object in repository

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) .

Can you get the DbContext from a DbSet?

In my application it is sometimes necessary to save 10,000 or more rows to the database in one operation. I've found that simply iterating and adding each item one at a time can take upwards of half an hour.
However, if I disable AutoDetectChangesEnabled it takes ~ 5 seconds (which is exactly what I want)
I'm trying to make an extension method called "AddRange" to DbSet which will disable AutoDetectChangesEnabled and then re-enable it upon completion.
public static void AddRange<TEntity>(this DbSet<TEntity> set, DbContext con, IEnumerable<TEntity> items) where TEntity : class
{
// Disable auto detect changes for speed
var detectChanges = con.Configuration.AutoDetectChangesEnabled;
try
{
con.Configuration.AutoDetectChangesEnabled = false;
foreach (var item in items)
{
set.Add(item);
}
}
finally
{
con.Configuration.AutoDetectChangesEnabled = detectChanges;
}
}
So, my question is: Is there a way to get the DbContext from a DbSet? I don't like making it a parameter - It feels like it should be unnecessary.
With Entity Framework Core (tested with Version 2.1) you can get the current context using
// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;
How to get a DbContext from a DbSet in EntityFramework Core 2.0
Yes, you can get the DbContext from a DbSet<TEntity>, but the solution is reflection heavy. I have provided an example of how to do this below.
I tested the following code and it was able to successfully retrieve the DbContext instance from which the DbSet was generated. Please note that, although it does answer your question, there is almost certainly a better solution to your problem.
public static class HackyDbSetGetContextTrick
{
public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
where TEntity: class
{
object internalSet = dbSet
.GetType()
.GetField("_internalSet",BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(dbSet);
object internalContext = internalSet
.GetType()
.BaseType
.GetField("_internalContext",BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(internalSet);
return (DbContext)internalContext
.GetType()
.GetProperty("Owner",BindingFlags.Instance|BindingFlags.Public)
.GetValue(internalContext,null);
}
}
Example usage:
using(var originalContextReference = new MyContext())
{
DbSet<MyObject> set = originalContextReference.Set<MyObject>();
DbContext retrievedContextReference = set.GetContext();
Debug.Assert(ReferenceEquals(retrievedContextReference,originalContextReference));
}
Explanation:
According to Reflector, DbSet<TEntity> has a private field _internalSet of type InternalSet<TEntity>. The type is internal to the EntityFramework dll. It inherits from InternalQuery<TElement> (where TEntity : TElement). InternalQuery<TElement> is also internal to the EntityFramework dll. It has a private field _internalContext of type InternalContext. InternalContext is also internal to EntityFramework. However, InternalContext exposes a public DbContext property called Owner. So, if you have a DbSet<TEntity>, you can get a reference to the DbContext owner, by accessing each of those properties reflectively and casting the final result to DbContext.
Update from #LoneyPixel
In EF7 there is a private field _context directly in the class the implements DbSet. It's not hard to expose this field publicly
Why are you doing this on the DbSet? Try doing it on the DbContext instead:
public static void AddRangeFast<T>(this DbContext context, IEnumerable<T> items) where T : class
{
var detectChanges = context.Configuration.AutoDetectChangesEnabled;
try
{
context.Configuration.AutoDetectChangesEnabled = false;
var set = context.Set<T>();
foreach (var item in items)
{
set.Add(item);
}
}
finally
{
context.Configuration.AutoDetectChangesEnabled = detectChanges;
}
}
Then using it is as simple as:
using (var db = new MyContext())
{
// slow add
db.MyObjects.Add(new MyObject { MyProperty = "My Value 1" });
// fast add
db.AddRangeFast(new[] {
new MyObject { MyProperty = "My Value 2" },
new MyObject { MyProperty = "My Value 3" },
});
db.SaveChanges();
}
maybe you could create a helper that disabled this for you and then just call the helper from within the AddRange method

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