I have three tables that I am working with User, Application, ApplicationAdministrator. ApplicationAdministrator is a mapping table to link User to Application which has a many-to-many relationship. I get the following error when I try to save off a new Application with a User added as an Administrator:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
So my next step was to create a BaseRepository that has a common context to pull from. However, now I get the following error when I try to modify an entity that is already attached to the context:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Why is this such a difficult process? I have seen the solutions to attach and reattach and detach and spin around on your head 5 times and then everything will work. Attaching the entities to one context ends up duplication one of the entities depending on which context I attach it to.
All help is greatly appreciated!
UserRepository.cs:
public class UserRepository : BaseRepository<User>, IUserRepository
{
// private ManagerDbContext _context = new ManagerDbContext();
public UserRepository(ManagerDbContext context)
: base(context) { }
public IQueryable<User> Users
{
get { return _context.Users.Include("Administrates").Include("Company"); }
}
public void SaveUser(User user)
{
_context.Entry(user).State = user.Id == 0 ? EntityState.Added : EntityState.Modified;
_context.SaveChanges();
}
public void DeleteUser(User user)
{
_context.Users.Remove(user);
_context.SaveChanges();
}
}
ApplicationRepository.cs:
public class ApplicationRepository : BaseRepository<Application>, IApplicationRepository
{
// private ManagerDbContext _context = new ManagerDbContext();
public ApplicationRepository(ManagerDbContext context)
: base(context) { }
public IQueryable<Application> Applications
{
get { return _context.Applications.Include("Administrators"); }
}
public void SaveApplication(Application app)
{
_context.Entry(app).State = app.Id == 0 ? EntityState.Added : EntityState.Modified;
_context.SaveChanges();
}
public void DeleteApplication(Application app)
{
_context.Applications.Remove(app);
_context.SaveChanges();
}
}
UserConfiguration.cs:
public UserConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.FirstName).IsRequired();
this.Property(x => x.LastName).IsRequired();
this.Property(x => x.Username).IsRequired();
this.Property(x => x.CompanyId).IsRequired();
this.HasRequired(user => user.Company).WithMany().HasForeignKey(user => user.CompanyId);
this.HasRequired(user => user.Company).WithMany(company => company.Users).WillCascadeOnDelete(false);
this.HasMany(user => user.Administrates)
.WithMany(application => application.Administrators)
.Map(map => map.MapLeftKey("UserId")
.MapRightKey("ApplicationId")
.ToTable("ApplicationAdministrators"));
}
ApplicationConfiguration.cs:
public ApplicationConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.Name).IsRequired();
this.Property(x => x.Description);
this.HasMany(application => application.Administrators)
.WithMany(user => user.Administrates)
.Map(map => map.MapLeftKey("ApplicationId")
.MapRightKey("UserId")
.ToTable("ApplicationAdministrators"));
}
Snippet for saving the entities.
long appId = Int64.Parse(form["ApplicationId"]);
long userId = Int64.Parse(form["UserId"]);
Application app = appRepository.Applications.FirstOrDefault(a => a.Id == appId);
User user = userRepository.Users.FirstOrDefault(u => u.Id == userId);
app.Administrators.Add(user);
appRepository.SaveApplication(app);
return RedirectToAction("Index");
If you are using two different contexts you must detach entity from the first one and attach it to the second one where you want to perform changes. You must also correctly configure its state. To detach entity in DbContext API you need to call:
context.Entry(entity).State = EntityState.Detached;
If you use the same context for loading all entities and saving their changes you don't need to change their state. It is done automatically by change tracking.
Btw. you should start without repository and start to use repository once you understand how does EF works and how could repository help you.
Here is my solution that I finally came up with.
I created a Func dictionary in order to attach an entity to the correct EntitySet in my context. The one downfall is that you have to hard code the EntitySet name some, so I did it in a static variable within my POCO.
BaseRepository.cs
public class BaseRepository<T> where T : class
{
public static ManagerDbContext baseContext;
public BaseRepository() { }
public BaseRepository(ManagerDbContext context)
{
baseContext = context;
}
private static object _entity;
public void AttachEntity(object entity)
{
_entity = entity;
entityAttachFunctions[entity.GetType().BaseType]();
}
private Dictionary<Type, Func<bool>> entityAttachFunctions = new Dictionary<Type, Func<bool>>()
{
{typeof(User), () => AttachUser()},
{typeof(Application), () => AttachApplication()}
};
private static bool AttachUser()
{
((IObjectContextAdapter)baseContext).ObjectContext.AttachTo(User.TableName, _entity);
return true;
}
private static bool AttachApplication()
{
((IObjectContextAdapter)baseContext).ObjectContext.AttachTo(Application.TableName, _entity);
return true;
}
}
UserRepository.cs
public void AttachEntity(object entity)
{
baseContext = _context;
base.AttachEntity(entity);
}
public void DetachUser(User user)
{
_context.Entry(user).State = EntityState.Detached;
_context.SaveChanges();
}
ApplicationRepository.cs
public void AttachEntity(object entity)
{
baseContext = _context;
base.AttachEntity(entity);
}
public void DetachApplication(Application app)
{
_context.Entry(app).State = EntityState.Detached;
_context.SaveChanges();
}
AdminController.cs
long appId = Int64.Parse(form["ApplicationId"]);
long userId = Int64.Parse(form["UserId"]);
Application app = appRepository.Applications.FirstOrDefault(a => a.Id == appId);
User user = userRepository.Users.FirstOrDefault(u => u.Id == userId);
userRepository.DetachUser(user);
appRepository.AttachEntity(user);
app.Administrators.Add(user);
appRepository.SaveApplication(app);
Related
Overview
I am currently unit/integration testing my repository pattern with the in-memory database EF Core provides. I am on version 6.0.1 for the Microsoft.EntityFrameworkCore.InMemory nuget and using Visual Studio 2022. I am running into issues saving data to the database. Below I have included snippets of my code.
My data model:
public class Example : IExample
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
My base repository class:
public class Repository<T> : IRepository<T> where T : class
private readonly DbSet<T> _dbSet;
private readonly DbContext _db;
public Repository(DbContext db)
{
_dbSet = db.Set<T>();
_db = db;
}
public virtual async Task Add(T entity)
{
await _dbSet.AddAsync(entity);
}
public virtual async Task<int> SaveAsync(CancellationToken token = default)
{
return await _db.SaveChangesAsync(token);
}
My repository class:
public class ExampleRepo : Repository<Example>
public ExampleRepo(ExampleContext db) : base(db)
{
}
My DbContext Class
I can show my IEntityConfiguration class for the ExampleBuilder shown below if it is needed but I don't believe that to be the problem.
public class ExampleContext : DbContext
private readonly IHttpContextAccessor? _httpContextAccessor;
public ExampleContext(DbContextOptions<ExampleContext> options) : base(options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
}
public ExampleContext(DbContextOptions<ExampleContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public DbSet<Example>? Examples { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var assembly = Assembly.GetAssembly(typeof(ExampleBuilder));
if (assembly is null)
{
throw new DataException("Could not find the assembly containing the designated model builder");
}
builder.ApplyConfigurationsFromAssembly(assembly);
foreach (var entity in builder.Model.GetEntityTypes())
{
entity.SetSchema("dbo");
}
}
public override Task<int> SaveChangesAsync(CancellationToken token = default)
{
foreach (var entry in ChangeTracker.Entries()
.Where(x => x.State is EntityState.Added or EntityState.Modified or EntityState.Deleted))
{
var user = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? "User";
entry.Property("ModifiedBy").CurrentValue = user;
if (entry.State == EntityState.Added)
{
entry.Property("CreatedBy").CurrentValue = user;
}
if (entry.State != EntityState.Deleted) continue;
entry.State = EntityState.Modified;
entry.Property("IsActive").CurrentValue = false;
}
return base.SaveChangesAsync(token);
}
My DbContext factory method:
private ExampleContext GenerateDbContext()
{
var options = new DbContextOptionsBuilder<ExampleContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ExampleContext(options);
}
My unit/integration test utilizing xUnit and NET6.0
[Fact]
public async Task GetAllEntities_ShouldReturnEntities_WhenEntitiesExist()
{
// Arrange
// I used Bogus nuget for single source for generating valid models
var entity = ExampleFaker.GetModelFaker()
.Generate();
await using var context = GenerateDbContext();
var repo = new ExampleRepo(context);
await repo.Add(entity);
var changes = await repo.SaveAsync();
// Act
// Consulted this link already. Bottom answer is most related
//https://stackoverflow.com/questions/46184937/dbcontext-not-returning-local-objects
var response = await repo.GetAll();
// Assess
TestOutputHelper.WriteLine($"Added entity to repository: {entity.ToJson()}");
TestOutputHelper.WriteLine("Expected entities saved: 1");
TestOutputHelper.WriteLine($"Actual entities saved: {changes}");
TestOutputHelper.WriteLine($"Response: {response?.ToJson()}");
// Assert
Assert.Equal(1, changes);
Assert.NotNull(response);
Assert.NotEmpty(response);
Assert.IsType<List<Example>>(response.ToList());
}
Analysis & Issue
The changes variable returns 1 so I interpret this as EF does not have any issue with my model as well as I would think it successfully saved my model in the in-memory database. However, during my GetAll retrieval, no data is returned. When I debug and look into the repository private members, it shows the DbSet is empty so it is not the GetAll method causing the issue either. Since this is also just within the scope of the unit test, I don't think my Program.cs configuration has anything to do with the issue I am seeing. I have been looking at this for quite a while and can't figure out the small detail I am probably missing for the life of me.
Thank you for your help in advance.
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.
I have API built with .net core 2, and I am trying to implement change log feature.
I have done basic part, but I am not sure if it's a best way for doing this.
Here is my EntityBaseRepository
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IFullAuditedEntity, new()
{
private readonly ApplicationContext context;
public EntityBaseRepository(ApplicationContext context)
{
this.context = context;
}
public virtual IEnumerable<T> items => context.Set<T>().AsEnumerable().OrderByDescending(m => m.Id);
public virtual T this[int id] => context.Set<T>().FirstOrDefault(m => m.Id == id);
public virtual T GetSingle(int id) => context.Set<T>().FirstOrDefault(x => x.Id == id);
public virtual T Add(T entity) => Operations(entity: entity, state: EntityState.Added);
public virtual T Update(T entity) => Operations(entity: entity, state: EntityState.Modified);
public virtual T Delete(T entity) => Operations(entity: entity, state: EntityState.Deleted);
public virtual T Operations(T entity, EntityState state)
{
EntityEntry dbEntityEntry = context.Entry<T>(entity);
if (state == EntityState.Added)
{
entity.CreationDateTime = DateTime.UtcNow;
entity.CreationUserId = 1;
context.Set<T>().Add(entity);
dbEntityEntry.State = EntityState.Added;
}
else if (state == EntityState.Modified)
{
entity.LastModificationDateTime = DateTime.UtcNow;
entity.LastModificationUserId = 1;
var local = context.Set<T>().Local.FirstOrDefault(entry => entry.Id.Equals(entity.Id));
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}
dbEntityEntry.State = EntityState.Modified;
}
else if (state == EntityState.Deleted)
{
entity.DeletionFlag = true;
entity.DeletionUserId = 1;
entity.DeletionDateTime = DateTime.UtcNow;
dbEntityEntry.State = EntityState.Modified;
}
return entity;
}
Here is one of my controller.
[Produces("application/json")]
[Route("api/Item")]
public class ItemController : Controller
{
private readonly IItemRepository repository;
private readonly IChangeLogRepository changeLogRepository;
private readonly IMapper mapper;
public ItemController(IItemRepository repository, IChangeLogRepository _changeLogRepository, IMapper mapper)
{
this.repository = repository;
this.changeLogRepository = _changeLogRepository;
this.mapper = mapper;
}
[HttpPost]
public IActionResult Post([FromBody]ItemDto transactionItemDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var item = repository.Add(mapper.Map<ItemDto, Item>(source: transactionItemDto));
repository.Commit();
ChangeLog log = new ChangeLog()
{
Log = "New Item Added"
};
changeLogRepository.Add(log);
changeLogRepository.Commit();
return new OkObjectResult(mapper.Map<Item, ItemDto>(source: item));
}
}
if you see in controller, I have added one item, and commited it, then I prepared log for that insertion, added and commited it.
Now, I have few questions, like
I have to commit my transaction twice, is there any way I can optimize it? I don't know if I can handle it on EntityBaseRepository or not.
I also want to check each property, if it gets changed or not. I want to log that to if it's changed. what would be the best way to handle it?
It would be great if anyone can help me with this. really appreciate. thanks.
You can use Action filters for a changelog Like
using System;
public class TrackMyChange : IActionFilter
{
private readonly string _chengeMessage;
private readonly IChangeLogRepository _changeLogRepository;
public TrackMyChange(string changeMessage,IChangeLogRepository changeLogRepository)
{
this._changeLogRepository = changeLogRepository;
this._chengeMessage = chengeMessage;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
ChangeLog log = new ChangeLog()
{Log = this._chengeMessage};
changeLogRepository.Add(log);
changeLogRepository.Commit();
}
}
In your controller, you can use it before actions you want to log like
[TrackMyChange("Your change log here")]
public IActionResult Post()
{
}
Reference :
Action Filter Attributes in .NET core
DbContext is shared between multiple repositories within the same HTTP request scope. You won't need UoW. Just try use one Commit() and see if all changes are saved in one transaction.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implemenation-entity-framework-core
Use yourDbContext.Entry(your_entity_obj).State to get entity state. Don't log if its state is EntityState.Unchanged.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entitystate?view=efcore-2.1
I am trying to create a Web API program that uses EF. Right now, I am having some CRUD functions in my controller, which are working for Creating a new object, Deleting an object and reading an object. The problem comes when I try to Update an object which exists in my database, receiving the error from the title. I already tried some solutions from the people who also had this problem and asked a question here, but nothing worked for now.
Here is my code:
The method from my controller:
[HttpPost]
[Route("UpdateAsset")]
public HttpResponseMessage UpdateAsset(HttpRequestMessage request, FixedAssetsView assets)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;
if (!ModelState.IsValid)
{
response = request.CreateResponse(HttpStatusCode.BadRequest,
ModelState.Keys.SelectMany(k => ModelState[k].Errors)
.Select(m => m.ErrorMessage).ToArray());
}
else if (!service.isInDatabase(assets.ID))
{
ModelState.AddModelError("Error", "Couldn't find the assets you want to update");
response = request.CreateResponse(HttpStatusCode.BadRequest,
ModelState.Keys.SelectMany(k => ModelState[k].Errors)
.Select(m => m.ErrorMessage).ToArray());
}
else if (service.isInDatabaseAfterInventoryNo(assets.inventoryNo) && service.getAssetIdAfterInventoryNumber(assets.inventoryNo) != assets.ID)
{
ModelState.AddModelError("Invalid object", "An assets with the same inventory number already exists");
response = request.CreateResponse(HttpStatusCode.BadRequest,
ModelState.Keys.SelectMany(k => ModelState[k].Errors)
.Select(m => m.ErrorMessage).ToArray());
}
else
{
if (service.wasUpdated(assets))
response = request.CreateResponse(HttpStatusCode.OK, "Success");
else
response = request.CreateResponse(HttpStatusCode.InternalServerError);
}
return response;
});
}
The methods I use from my service:
public bool isInDatabase(int ID)
{
return assetsRepo.FindBy(asset => asset.ID == ID).Any();
}
public bool isInDatabaseAfterInventoryNo(int inventoryNo)
{
return assetsRepo.FindBy(asset => asset.inventoryNo == inventoryNo).Any();
}
public int getAssetIdAfterInventoryNumber(int inventoryNo)
{
return assetsRepo.FindBy(asset => asset.inventoryNo == inventoryNo).First().ID;
}
public bool wasUpdated(FixedAssetsView assets)
{
FixedAssets singleAsset = Mapper.Map<FixedAssetsView, FixedAssets>(assets);
assetsRepo.Edit(singleAsset);
unitOfWork.Commit();
return true;
}
The methods I use from my repository:
public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return DbContext.Set<T>().Where(predicate);
}
public virtual void Edit(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry<T>(entity);
dbEntityEntry.State = EntityState.Modified; /*HERE IS WHERE RECEIVE THE EXCEPTION*/
}
Here is my DBContext method:
private InventoryManagementContext dataContext;
protected InventoryManagementContext DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
My DbFactory method:
protected IDbFactory DbFactory
{
get;
private set;
}
My IDbFactory interface:
public interface IDbFactory : IDisposable
{
InventoryManagementContext Init();
}
My InventoryManagementContext:
public class InventoryManagementContext : DbContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to drop and regenerate your database
// automatically whenever you change your model schema, please use data migrations.
// For more information refer to the documentation:
// http://msdn.microsoft.com/en-us/data/jj591621.aspx
public InventoryManagementContext() : base("name=InventoryManagementContext")
{
Database.SetInitializer<InventoryManagementContext>(null);
}
public System.Data.Entity.DbSet<InventoryManagement.Models.InventoryObjects> InventoryObjects { get; set; }
public System.Data.Entity.DbSet<InventoryManagement.Models.FixedAssets> FixedAssets { get; set; }
public virtual void Commit()
{
base.SaveChanges();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new FixedAssetsConfiguration());
modelBuilder.Configurations.Add(new InventoryObjectsConfiguration());
}
}
Thank you for your help.
I fixed the problem. For the ones who will have it in the future, I updated my FindBy function
public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return DbContext.Set<T>().Where(predicate).AsNoTracking();
}
Notice the "AsNoTracking()" addition.
I'm trying to test my EF repository with MOQ.
My context:
public class PhotoDbContext : DbContext, IDbContext
{
public PhotoDbContext(){} // to enable data migrations
public PhotoDbContext(string connectionString, bool debugOutput = false) : base(connectionString)
{
if (debugOutput)
// Write to output all sql operations
Database.Log = s => Debug.WriteLine(s);
}
public virtual new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
public virtual new void SaveChanges()
{
base.SaveChanges();
}
public new DbEntityEntry Entry<TEntity>(TEntity entity) where TEntity : class
{
return base.Entry(entity);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Configure(modelBuilder);
}
public new void Dispose()
{
base.Dispose();
}
public new DbContextConfiguration Configuration
{
get { return base.Configuration; }
}
My repo:
public class PhotoRepository : IPhotoRepository
{
private readonly IDbContext _context;
public PhotoRepository(IDbContext context)
{
_context = context;
}
public void Save(Photo album)
{
_context.Set<Photo>().Add(album);
_context.SaveChanges();
}
public void Update(Photo entity)
{
_context.Set<Photo>().Attach(entity);
_context.Entry(entity).State = EntityState.Modified; **// null ref exception**
_context.SaveChanges();
}
And finally the test im trying to run.
[TestMethod]
public void UpdatePhoto()
{
var mockSet = new Mock<DbSet<Photo>>();
var mockContext = new Mock<PhotoDbContext>();
mockContext.Setup(m => m.Set<Photo>()).Returns(mockSet.Object);
var service = new PhotoRepository(mockContext.Object);
var photo = new Photo
{
Name = "123",
Order = 2
};
service.Save(photo);
mockSet.Verify(m => m.Add(It.IsAny<Photo>()), Times.Once);
mockContext.Verify(m => m.SaveChanges(), Times.Once);
photo.Name = "updated";
service.Update(photo);
mockSet.Verify(m => m.Add(It.IsAny<Photo>()), Times.Exactly(2));
mockContext.Verify(m => m.SaveChanges(), Times.Exactly(2));
}
The problem is I am getting a Null reference exception in repositories update method where I try to set Entry.State = Entity.Modified
I tried adding level of indirection in my context but it doesn't work.
What am I missing? Any help would be appreciated.
The _context object is your mock object mockContext and the part that is returning null is _context.Entry(entity) because you haven't set this up to return anything within the test.
If you add something like this with the object someObjectThatYouHaveSetup being whatever you want to return from _context.Entry(entity), it should work.
mockContext.Setup(x => x.Entry(photo)).Returns(someObjectThatYouHaveSetup)