Update all fields of an object using entity framework - c#

I want to change all of an object properties using entity framwork.
after searching i got to have this:
Controller,action:
public ActionResult Test()
{
var user = GetCurrentUser();
user.FirstName = "BLAH BLAH";
new UserRepository().UpdateUser(user);
return RedirectToAction("Index");
}
and in my UserRepository:
public bool UpdateUser(ApplicationUser target)
{
using (var db = new AppDatabase())
{
db.Entry(target).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
but when i try execute i got this error
An entity object cannot be referenced by multiple instances of EntityChangeTracker.
so,any ways to fix or any better way?
using entity framework 6.0.0 and .net 4.5
public ApplicationUser GetCurrentUser()
{
return UserManager.FindById(User.Identity.GetUserId());
}

You should use same instance of db context for finding and updating, so you UserRepository can be:
class UserRepository : IDisposable //using IDisposable to dispose db context
{
private AppDatabase _context;
public UserRepository()
{
_context = new AppDatabase();
}
public ApplicationUser Find(string id)
{
return _context.Set<ApplicationUser>().Find(id);
}
public void Update(ApplicationUserentity entity)
{
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
You can use it in controller:
public ActionResult Test()
{
using (var repository = new UserRepository())
{
var user = repository.Find(User.Identity.GetUserId());
user.FirstName = "BLAH BLAH";
repository.Update(user);
}
return RedirectToAction("Index");
}
I also think using some dependency injection framework would be beneficial for you. So go for it!!

Be sure that all objects came from the same context!
var userContextOne = new MyDbContext();
var user = userContextOne.Users.FirstOrDefault();
var AppDbContextTwo = new MyDbContext();
// Warning when you work with entity properties here! Be sure that all objects came from the same context!
db.Entry(target).State = EntityState.Modified;
AppDbContextTwo.SaveChanges();
The scond problem (not related to the exception!):
db.Entry(target).State = EntityState.Modified;
Why you are doing that?! You dont not have Detached Scenario? did you have disabled your Changetracker? anyway just execute DetectChanges and this method will find the changed data you do not have to do it by your self.

Related

Entity is still being tracked although NoTracking behaviour is set

The dbcontext tracking behaviour is set to NoTracking, but I still get a fail in the test.
BaseDbContext in this context does not have any relevant code compared to IdentityDbContext provided by EF. Also is the same with BaseUser, where it is basically only IdentityUser.
Method for creating DbContext:
public static T GetDbContext<T>()
where T : BaseDbContext<BaseUser<Guid>>
{
var optionBuilder = new DbContextOptionsBuilder<T>();
optionBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
optionBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
var obj = Activator.CreateInstance(typeof(T), optionBuilder.Options);
if (obj == null)
{
throw new SystemException(typeof(T) + " was null!");
}
var ctx = (T)obj;
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
return ctx;
}
The test, that fails:
[Fact]
public async void Test_UpdateSingle()
{
var dbContext = DbContextFactory.GetDbContext<SimpleDbContext>();
var uow = UowFactory.GetUow<SimpleUow, SimpleDbContext>(dbContext);
var id1 = Guid.NewGuid();
var name1 = Guid.NewGuid().ToString();
var testEntity1 = new DalSimpleEntity
{
Id = id1,
Name = name1
};
uow.SimpleRepo.Add(testEntity1);
await uow.SaveChangesAsync();
var newName = Guid.NewGuid().ToString();
testEntity1.Name = newName;
//Fails here:
uow.SimpleRepo.Update(testEntity1);
await uow.SaveChangesAsync();
Assert.Single(await uow.SimpleRepo.GetAllAsync());
var getEntity1 = await uow.SimpleRepo.FirstOrDefaultAsync(id1);
Assert.Equal(newName, getEntity1?.Name);
}
The UnitOfWork is for using it as a layer on top of the dbcontext.
The SaveChanges method calls directly the DbContext savechanges.
UnitOfWork also contains the reference for Repository.
The SimpleRepo is derived from BaseRepository. Nothing is changed in the SimpleRepo.
public abstract class BaseRepository<TDbContext, TEntityIn, TEntityOut> : BaseRepositoryWebApp<TDbContext, TEntityIn, TEntityOut, Guid>, IBaseRepositoryWebApp<TEntityOut>
where TEntityIn : class, IDomainEntityId, IDomainEntityId<Guid>
where TEntityOut : class, IDomainEntityId, IDomainEntityId<Guid>
where TDbContext : BaseDbContext<BaseUser<Guid>>
{
protected readonly DbContext RepoDbContext;
protected readonly DbSet<TEntityIn> RepoDbSet;
protected readonly IBaseMapper<TEntityIn, TEntityOut> Mapper;
public BaseRepository(TDbContext dbContext, IBaseMapper<TEntityIn, TEntityOut> mapper)
{
RepoDbContext = dbContext;
RepoDbSet = dbContext.Set<TEntityIn>();
Mapper = mapper;
}
public virtual async Task<IEnumerable<TEntityOut>> GetAllAsync(bool noTracking = true, Guid userId = default)
{
var entities = await InitQuery(noTracking, userId).ToListAsync();
return entities.Select(e => Mapper.Map(e)!);
}
public virtual async Task<TEntityOut?> FirstOrDefaultAsync(TKey id, bool noTracking = true, Guid userId = default)
{
var query = InitQuery(noTracking, userId).FirstOrDefaultAsync(e => e.Id.Equals(id));
return Mapper.Map(await query);
}
public virtual async Task<bool> ExistsAsync(TKey id, Guid userId = default)
{
return await InitQuery(userId: userId).AnyAsync(e => e.Id.Equals(id));
}
public virtual async Task<TEntityOut?> RemoveAsync(TKey id, Guid userId = default)
{
var entity = await InitQuery(userId: userId).FirstOrDefaultAsync(e => e.Id.Equals(id));
if (entity == null) return null;
return Mapper.Map(RepoDbSet.Remove(entity).Entity);
}
public TEntityOut Add(TEntityOut? entity)
{
return Mapper.Map(RepoDbSet.Add(Mapper.Map(entity)!).Entity)!;
}
public TEntityOut Update(TEntityOut? entity)
{
return Mapper.Map(RepoDbSet.Update(Mapper.Map(entity)!).Entity)!;
}
protected virtual IQueryable<TEntityIn> InitQuery(bool noTracking = true, Guid userId = default)
{
var query = RepoDbSet.AsQueryable();
if (typeof(IDomainEntityUsers).IsAssignableFrom(typeof(TEntityIn)))
{
query = query.Where(e => (e as IDomainEntityUsers)!.UserId.Equals(userId));
}
if (noTracking)
{
query = query.AsNoTracking();
}
return query;
}
}
My question is, have I forgotten some place, where I should also state to use NoTracking behaviour?
The problem was made difficult with the fact, that every time the entity moved down or up the layers (example: from uow to dbcontext and back, containing 2 mappings here alone).
This ensured that calling
dbContext.Entity(entity).State = EntityState.Detached;
was not an option, because Savechanges was called with the uow object, not dbContext. Also in the real use case the dbContext is out of reach, thus not being able to call functions from dbContext at all.
So I searched for options to detach all objects, that were still attached after saveChanges.
ChangeTracker.Clear();
satisfied this requirement.
The solution for this problem is:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
{
var res = await base.SaveChangesAsync(cancellationToken);
ChangeTracker.Clear();
return res;
}

EF Core in-memory database generate System.InvalidOperationException when testing an update operation

I got the following error when I try to test an update operation using Entity Framework core:
System.InvalidOperationException : The instance of entity type 'Companies' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
After doing some research, I tried everything that I found:
Create in scope DB context
deattach and attached the object I want to update from the DB context
Return the object to be updated using "AsNoTracking()" , my repository actually do this.
For the testing I am using EF in-memmory database with it fixture, I am using XUnit and .NET 5.
Can I get any help with this please?
Here is my code:
// The repository I am trying to test
public class RepositoryBase<T> : ICrudRepository<T> where T : class, IModel
{
protected PrjDbContext DatabaseContext { get; set; }
public RepositoryBase(PrjDbContext databaseContext) => DatabaseContext = databaseContext;
protected IQueryable<T> FindAll() => DatabaseContext.Set<T>().AsNoTracking();
protected IQueryable<T> FindBy(Expression<Func<T, bool>> expression) => DatabaseContext.Set<T>().Where(expression).AsNoTracking();
public void Create(T entity) => DatabaseContext.Set<T>().Add(entity);
public void Update(T entity) => DatabaseContext.Set<T>().Update(entity);
public void Delete(T entity) => DatabaseContext.Set<T>().Remove(entity);
public async Task<IEnumerable<T>> ReadAllAsync() => await FindAll().ToListAsync().ConfigureAwait(false);
public async Task<T> ReadByIdAsync(int id) => await FindBy(entity => entity.Id.Equals(id)).FirstOrDefaultAsync().ConfigureAwait(false);
}
//The Database context
public partial class PrjDbContext : DbContext
{
public PrjDbContext()
{
}
public PrjDbContext(DbContextOptions<PrjDbContext> options)
: base(options)
{
}
public virtual DbSet<Companies> Companies { get; set; }
}
// This is my fixture with the in-memory Database
public sealed class PrjSeedDataFixture : IDisposable
{
public PrjDbContext DbContext { get; }
public PrjSeedDataFixture(string name)
{
string databaseName = "PrjDatabase_" + name + "_" + DateTime.Now.ToFileTimeUtc();
DbContextOptions<PrjDbContext> options = new DbContextOptionsBuilder<PrjDbContext>()
.UseInMemoryDatabase(databaseName)
.EnableSensitiveDataLogging()
.Options;
DbContext = new PrjDbContext(options);
// Load Companies
DbContext.Companies.Add(new Companies { Id = 1, Name = "Customer 1", Status = 0, Created = DateTime.Now, LogoName = "FakeLogo.jpg", LogoPath = "/LogoPath/SecondFolder/", ModifiedBy = "Admin" });
DbContext.Companies.AsNoTracking();
DbContext.SaveChanges();
}
public void Dispose()
{
DbContext.Dispose();
}
}
The test method "Update_WhenCalled_UpdateACompanyObject", is not working for me.
// And finally, this is my test class, Create_WhenCalled_CreatesNewCompanyObject pass the test, but Update_WhenCalled_UpdateACompanyObject isn't passing the test.
public class RepositoryBaseCompanyTests
{
private Companies _newCompany;
private PrjDbContext _databaseContext;
private RepositoryBase<Companies> _sut;
public RepositoryBaseCompanyTests()
{
_newCompany = new Companies {Id = 2};
_databaseContext = new PrjSeedDataFixture("RepositoryBase").DbContext;
_sut = new RepositoryBase<Companies>(_databaseContext);
}
[Fact]
public void Create_WhenCalled_CreatesNewCompanyObject()
{
//Act
_sut.Create(_newCompany);
_databaseContext.SaveChanges();
//Assert
Assert.Equal(2, _databaseContext.Companies.Where( x => x.Id == 2).FirstOrDefault().Id);
}
[Fact]
public async void Update_WhenCalled_UpdateACompanyObject()
{
//Arrange
var company = await _sut.ReadByIdAsync(1);
company.Name = "Customer 2";
//_databaseContext.Entry(company).State = EntityState.Detached;
//_databaseContext.Attach(company);
//_databaseContext.Entry(company).State = EntityState.Modified;
//Act
_sut.Update(company);
await _databaseContext.SaveChangesAsync();
//Assert
Assert.Equal("Customer 2", _databaseContext.Companies.Where(x => x.Id == 1).FirstOrDefault().Name);
}
}
If you are using EF Core 5.0 then call DbContext.ChangeTracker.Clear() (or go through DbContext.Entries collection and set state to Detached for earlier ones) after DbContext.SaveChanges(); in PrjSeedDataFixture ctor. Adding/Updating an entry makes it tracked and you are reusing the context that created an entry with Id = 1, so when _sut.Update(company); is called it will try to track it again (since ReadByIdAsync should return an untracked one).
P.S.
Adding an extra repository abstraction layer around EF can be considered as antipattern (because EF already implements repository/UoW patterns) and the issue you are having can be one of the examples of why that is true and why this abstraction can be a leaky one. So if you still decide that having one is a good idea - you need to proceed with caution.

it was not found in the ObjectStateManager

I have this code for deleted data from MSSQL database.
[HttpPost]
public ActionResult DeleteData(PartnerStudy partner)
{
var partnerStudy = GetByID(partner.Idetity);
DomainRepository repository = new DomainRepository();
repository.Delete(partnerStudy);
repository.SaveChanges();
return RedirectToAction("ShowData");
}
public PartnerStudy GetByID(int id)
{
DomainRepository repository = new DomainRepository();
//PartnerStudy partner = repository.GetItem<PartnerStudy>(id);
var partner = repository.GetItem<PartnerStudy>(id);
repository.Dispose();
return partner;
}
In DomainRepository have this
public void Delete<T>(T item) where T : class, IEntity
{
Set<T>().Remove(item);
}
here I have a problem.
If I try to delete have a error in line Set<T>().Remove(item);
{"The object cannot be deleted because it was not found in the
ObjectStateManager."}
What does mean ?
Why not found and where is ObjectStateManager ?
Thanks for answers!
You need to send the repository variable as a parameter to the GetByID() method, and use the same repository for getting and deleting the item.
[HttpPost]
public ActionResult DeleteData(PartnerStudy partner)
{
using(var repository = new DomainRepository())
{
var partnerStudy = GetByID(partner.Idetity, repository);
repository.Delete(partnerStudy);
repository.SaveChanges();
}
return RedirectToAction("ShowData");
}
private PartnerStudy GetByID(int id, DomainRepository repository)
{
var partner = repository.GetItem<PartnerStudy>(id);
return partner;
}
I even added a using block, as it seems DomainRepository implements IDisposable.

How to update an entity using Entity Framework from Business Layer?

I my web application structured with 3 layers, controller, business logic and repository.
From the BL layer I am updating an entity with the following code. As you can see I am updating property by property.
I would like to know if there is a better way to do it, removing this manual mapping.
---------------------- CONTROLLER
public IHttpActionResult Put(int id, DeviceDTO dto)
{
GaDevice device = Mapper.Map<GaDevice>(dto);
deviceBLL.Update(id, device);
return Ok();
}
---------------------- BL
public void Update(int id, GaDevice entity)
{
bool hasValidId = GetById(id) != null ? true : false;
if (hasValidId == true)
{
GaDevice device = deviceRepo.GetById(id);
device.CanNotifyPc = entity.CanNotifyPc; // NOT SURE HERE
device.CanNotifyPrinter = entity.CanNotifyPrinter;
device.LocationId = entity.LocationId;
device.Name = entity.Name;
device.Note = entity.Note;
device.OperativeFromTime = entity.OperativeFromTime;
device.OperativeToTime = entity.OperativeToTime;
deviceRepo.Update(device );
deviceRepo.Save();
}
---------------- Repository
public void Update(GaDevice entity)
{
context.Entry(entity).State = EntityState.Modified;
}
What about saving the changes made to the context in the Update()?
Otherwise, what does your code in the Save() do?
public void Update(GaDevice entity)
{
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
}

DbContext has been disposed

I developed a web application with ASP.NET MVC 4 and SQL Server 2008, I create ContextManager class to have only one database context in all pages.
public static class ContextManager
{
public static HotelContext Current
{
get
{
var key = "Hotel_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as HotelContext;
if (context == null)
{
context = new HotelContext();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
It works properly in most of the pages, but in registration page something goes wrong and my context gone deposed with following error:
The operation cannot be completed because the DbContext has been disposed.
public ActionResult Register ( RegisterModel model )
{
if ( ModelState.IsValid )
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount( model.UserName, model.Password,
new
{
Email = model.Email,
IsActive = true,
Contact_Id = Contact.Unknown.Id
} );
//Add Contact for this User.
var contact = new Contact { Firstname = model.FirstName, LastName = model.Lastname };
_db.Contacts.Add( contact );
var user = _db.Users.First( u => u.Username == model.UserName );
user.Contact = contact;
_db.SaveChanges();
WebSecurity.Login( model.UserName, model.Password );
at the line _db.Contacts.Add( contact ); I got the exception.
But without using ContextManager by changing
HotelContext _db = ContextManager.Current;
into:
HotelContext _db = new HotelContext();
the problem was solved. But I need to use my own ContextManager. What is the problem?
Your context has been disposed somewhere else (not in the code you've shown), so basically when you access it from your Register action, it throws the exception.
Actually, you shouldn't use a static singleton to access to your context. Do instantiate a new DbContext instance for each request. See c# working with Entity Framework in a multi threaded server
In my case, my GetAll method was not calling ToList() method after where clause in lambda expression. After using ToList() my problem was solved.
Where(x => x.IsActive).ToList();
You are probably 'lazy-loading' a navigation property of User in your registration view. Make sure you include it by using the Include method on your DbSet before sending it to the view:
_db.Users.Include(u => u.PropertyToInclude);
Also, sharing DbContexts with a static property may have unexpected side effects.
I used to have the same problem. I solved it doing as it was said above. Instantiate a new instance of your context.
Try using this:
using (HotelContextProductStoreDB = new ProductStoreEntities())
{
//your code
}
This way it'll be created a new instance everytime you use your code and your context will not be disposed.
Why override the Dispose(bool)?
public partial class HotelContext : DbContext
{
public bool IsDisposed { get; set; }
protected override void Dispose(bool disposing)
{
IsDisposed = true;
base.Dispose(disposing);
}
}
And, then check IsDisposed
public static class ContextManager
{
public static HotelContext Current
{
get
{
var key = "Hotel_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as HotelContext;
if (context == null || context.IsDisposed)
{
context = new HotelContext();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
Maybe, can be an option.

Categories