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

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();
}

Related

EF Core transaction rollback not affecting context

I am trying to implement the Unit of Work pattern and have hit an unexpected behavior. My controller below successfully gets the user from the db, updates the name, and rolls back the transaction. After running this method, no change is made to the db as expected. However, the db query after the rollback still gets the user with the changed name and I don't understand why. Does EF perform some kind of caching?
public ActionResult GetTest()
{
_unitOfWork.BeginTransaction();
var user = _unitOfWork.UserRepository.GetByID(123);
// current name is "Chris"
user.Name = "Adam";
_unitOfWork.Save();
_unitOfWork.RollbackTransaction();
var user2 = _unitOfWork.LarRepository.GetByID(123);
// user2.Name is equal to "Adam" but the DB was never updated and I expected "Chris"
return Ok(user2) ;
}
Here are the applicable Unit of Work methods
public void Save()
{
_context.SaveChanges();
}
public void BeginTransaction()
{
_transaction = _context.Database.BeginTransaction();
}
public void CommitTransaction()
{
if (_transaction != null)
_transaction.Commit();
}
public void RollbackTransaction()
{
if (_transaction != null)
_transaction.Rollback();
}
private IDbContextTransaction _transaction;

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.

Entity Framework 6 - update fails (disconnected scenario)

I'm trying to update an instance with Entity Framework 6. I suppose it's a disconnected scenario. And the update fails - no errors but the properties I change do not save in DB.
Method in controller
var managers = _iManagerRepository.Managers.ToList();
var manager = managers.FirstOrDefault(m => m.Id == currentUserId);
if (manager != null)
{
manager.ContactDetail.FirstName = withTokenDto.managerInfoModel.FirstName;
manager.ContactDetail.SecondName = withTokenDto.managerInfoModel.SecondName;
manager.ContactDetail.LastName = withTokenDto.managerInfoModel.LastName;
_iManagerRepository.UpdateManager(manager);
return ResponseMessage(Request.CreateResponse(HttpStatusCode.OK));
}
Method in repository:
public void UpdateManager(Manager manager)
{
using (LightCRMEntities context = new LightCRMEntities())
{
context.Managers.Attach(manager);
context.Entry<Manager>(manager).State = EntityState.Modified;
context.SaveChanges();
}
}

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.

Update all fields of an object using entity framework

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.

Categories