I'm trying to create a test in .Net 7EFCore to generate a DbUpdateConcurrencyException but not succeding in doing that. Basically trying to modify it using different context instances. Apparantly there is something I have misunderstood, why does this not throw an exception when changes have been made in a second context and I then try to modify and save it with the first again? What is the correct way of writing a test for this? The goal is to be able to test implementation of handling of concurrency issues so just throwing the exception is not enough, I want to get it into a state where there are changes and I get an error message like Modified Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace UnitTests;
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public virtual DbSet<MyObject> MyObjects { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyObject>(entity => entity.ToTable("MyObject"));
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
try
{
return await base.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException ex)
{
// Implement handling
}
}
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ExceptionTest
{
[Fact]
public async Task DbUpdateConcurrencyExceptionTest()
{
// Arrange
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "SaveChangesAsyncTest")
.Options;
await using var context = new MyContext(options);
var myObject = new MyObject { Id = 1, Name = "name when added" };
context.MyObjects.Add(myObject);
var result = await context.SaveChangesAsync();
// Modify the object using a new context instance
await using var context2 = new MyContext(options);
var myObject2 = await context2.MyObjects.FindAsync(myObject.Id);
myObject2.Name = "New name";
await context2.SaveChangesAsync();
// Modify the original object using the original context instance
myObject.Name = "Renamed again";
await context.SaveChangesAsync();
// Modify the same entity from the second context instance
myObject2.Name = "Changed by second context";
await context2.SaveChangesAsync();
// Try to save the changes to the first context instance, which should result in a DbUpdateConcurrencyException
myObject.Name = "Changed by first context";
await Assert.ThrowsAsync<DbUpdateConcurrencyException>(() => context.SaveChangesAsync());
}
}
The next piece of code will throw a DbUpdateConcurrencyException.
using var db = new MyContext();
var obj = db.MyObjects.First();
obj.Name = "new value";
using var db2 = new MyContext();
var obj2 = db2.MyObjects.First();
db2.MyObjects.Remove(obj2);
db2.SaveChanges();
db.SaveChanges();
In real code, this can happen when one client deletes an entity that another client is currently working with.
Related
I am using EF Core 5.0.
I have a class called JobApplicationManager that OwnsMany() of JobApplications.
modelBuilder.Entity<JobApplicationManager>().OwnsMany(s => s.JobApplicationCategories, a =>
{
a.Property<DateTime>("CreatedDate");
a.Property<DateTime>("UpdatedDate");
a.ToTable("JobApplicationManagerCategories");
});
modelBuilder.Entity<JobApplication>().OwnsMany(s => s.JobApplicationCategories, a =>
{
a.Property<DateTime>("CreatedDate");
a.Property<DateTime>("UpdatedDate");
a.ToTable("JobApplicationCategories");
});
Current state: There is one JobApplicationManager in the DB with one JobApplication.
Action: I add another JobApplication to the JobApplicationManager.
In my DBContext.SaveChangesAsync() I call ChangeTracker.Entries() and the following is the entities:
As you can see the first entry is the JobApplication with status of Added.
Also in this DBContect you can see the JobApplicationManager has two JobApplications:
The next step I can ChangeTracker.HasChanges() which returns false so therefor base.SaveChangesAsync() returns 0 number of entities written.
I dont know what to do.
Here is the code that creates my entity and calls my repository:
var mgr = await AMRepository.GetJobApplicationManagerAsync(command.JobApplicationManagerID);
var newJobApplication = mgr.AddJobApplication(command.JobDescriptionURL, command.JobTitle, command.FileName, command.CompanyName, command.GeographicLocation, command.RecruiterName, command.JobApplicationCategories);
// Update the Repository
AMRepository.Update(mgr);
commandResult = await AMRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
Here is the code in my Repository:
public void Update(JobApplicationManager applicationManager)
{
try
{
AppManagerContext.Entry(applicationManager).State = EntityState.Modified;
}
Here is the code in my DBContext for SaveEntitiesAsync():
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var changesFlag1 = ChangeTracker.HasChanges();
// Dont save the enumeration Value Object types
try
{
var enumerationEntries = ChangeTracker.Entries()
.Where(x => EnumerationTypes.Contains(x.Entity.GetType()));
foreach (var enumerationEntry in enumerationEntries)
{
enumerationEntry.State = EntityState.Unchanged;
}
}
catch (Exception exc)
{
Console.WriteLine(exc.ToString());
}
UpdateShadowProperties();
var numberOfStateEntitiesWritten = 0;
try
{
numberOfStateEntitiesWritten = await base.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateException exc)
{
Console.WriteLine(exc.ToString());
}
if (numberOfStateEntitiesWritten == 0)
{
return false;
}
return true;
}
Here is the code from the Repository for GetJobApplicationManagersAsync():
public async Task<IEnumerable<JobApplicationManager>> GetJobApplicationManagersAsync() => await AppManagerContext.JobApplicationManagers.ToListAsync();
Within mgr.AddApplication() all I do is add POCO classes to the mgr. There is no infrastructure related activities (i.e. using DBContext)>
I am having an issue where while looping through a data set and looking in the db to see if it already exists, it works the first run but the second item causes an error.
The following method builds up a List from a raw data file
private async Task<List<Vehicle>> CreateListOfVehiclesFromAuctionDataFile(IEnumerable<string> rows)
{
var cars = new List<Vehicle>();
var vinList = new List<string>();
foreach (var dataRow in rows)
{
var data = dataRow.Split(",");
var dto = GetCarModelInfoFromAuctionData(dataRow);
if (vinList.Contains(data[14]))
{
continue;
}
vinList.Add(data[14]);
var car = new Vehicle
{
Vin = data[14],
InteriorColor = data[15],
ExteriorColor = data[16],
VehicleImageUrl = data[17],
Notes = data[18],
ModelId = await GetModelIdFromCarModelDto(dto)
};
cars.Add(car);
}
return cars;
}
That method calls this method within it
private async Task<int> GetModelIdFromCarModelDto(CarModelDto dto)
{
var modelId =0;
try
{
modelId = await _context.Models.Where(u => u.ModelYear == dto.ModelYear)
.Where(u => u.ModelType == dto.ModelType)
.Where(u => u.BodyStyle.Contains(dto.BodyStyle))
.Select(u => u.ModelId)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
//log error here
var errorMessage = $"Model id not found: {ex.Message}- Exception: {ex.InnerException}";
}
return modelId;
}
Those private method calls come from this public method
public async Task<int> AddVehicleDataFromAuctionFileAsync()
{
var currentRecords = _context.AuctionDatum.Count();
var data = await AzureDataRetrieval.GetDataAsStreamAsync(AzureService.AzureContainer,
AzureFilePathsFromMain.VehicleAuctionData);
var rows = ConvertAuctionDataStreamToDataArray(data);
**var cars = await CreateListOfVehiclesFromAuctionDataFile(rows);**
var datum = CreateListOfVehicleAuctionData(rows);
await _context.AuctionDatum.AddRangeAsync(datum);
await _context.Vehicles.AddRangeAsync(cars);
await _context.SaveChangesAsync();
return await _context.AuctionDatum.CountAsync() - currentRecords;
}
The main method call comes from the controller
public VehiclesController(CarCollectionDataContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
public async Task<IActionResult> Test()
{
var vm = new VehicleDataViewModel(VehicleView.Models);
var mgr = new VehicleDataManager(_context, _configuration);
**var datum = mgr.AddVehicleDataFromAuctionFileAsync();**
vm.Models = await mgr.GetModelListAsync();
return View(vm);
}
The first time through this method it works fine and retrieve's the ModelId. The second time through it fails with the following error message. The where clause parameters, when checked manually, show there is an item that should be retrieved.
Model id not found: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection
and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context,
or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of
disposing context instances.
Object name: 'CarCollectionDataContext'.- Exception:
I am not disposing the context anywhere in the code. The context IS brought in via DI
private readonly CarCollectionDataContext _context;
private readonly IConfiguration _configuration;
public VehicleDataManager(CarCollectionDataContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
AzureService = new AzureService.AzureService(_configuration);
}
I am at the end of my knowledge base on how to resolve this, any help appreciated.
I have a service for adding city in DB. It works good, now I need test it. I have never worked with tests before. I'll be grateful for your recommendations. I would test case - If it isn't null - test passed(I mean if User insert anything - test is passed, if he doesn't insert data - test failed). I need just check townName null or no. It is my TestClass
[TestClass]
public partial class TownRepositoryTests
{
[TestMethod]
public async Task InsertTownName_ReturnTownName()
{
var builder = new RepositoryBuilder<TownRepository>().SetupManager();
using (builder.CreateIsolationScope())
{
var expectedTown = await TestBuilder.SetupTownNameAsync(builder);
var repository = builder.Build();
var townName = expectedTown.Name;
var result = await repository.InsertTown(townName);
Assert.IsNotNull(result);
}
}
}
SetupTownNameAsync method(here I add data). I think it works good, because when I used debug test, I checked that this method return (id=1, name= "TestTown")
public static class TestBuilder
{
public static async Task<Town> SetupTownNameAsync(RepositoryBuilder<TownRepository> builder)
{
var town = await builder.CreateTownAsync(name: "TestTown");
var setupResult = new Town
{
Id = town.Id,
Name = town.Name
};
return setupResult;
}
}
And here my InsertTown method
public async Task<int> InsertTown(string townName)
{
var parameters = new { townName };
return await Connection.QueryFirstOrDefaultAsync<int>(Adhoc["AddTown"], parameters, Transaction);
}
When I checked problem with debug test - in this line returns "TestTown", but when I check it in Assert - my test is failed
var result = await repository.InsertTown(townName);
Assert.IsNotNull(result);
I have a Players table in a SQL Server database and I'm trying to link it to the AspNetUsers table with a stored procedure during registration. However, when I attempt to call the procedure, this variable p is null. I'm new to this kind of coding, so please keep that in mind when responding.
Here's the section where the variable p is null from the Register.cshtml.cs file -
readonly PlayerInitClass p;
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationIdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
int pID = await p.InitPlayerAsync(user);
}
}
Here is the class I created -
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Scorekeeper.Data;
namespace Scorekeeper.Areas.Identity
{
public class PlayerInitClass
{
private readonly ApplicationDbContext _db;
public PlayerInitClass(ApplicationDbContext db)
{
_db = db;
}
public async Task<Int32> InitPlayerAsync(ApplicationIdentityUser user)
{
int AspNetUserId = user.Id;
SqlParameter[] #params =
{
new SqlParameter("#returnVal", SqlDbType.Int) {Direction = ParameterDirection.Output},
new SqlParameter("#AspNetUserId", SqlDbType.Int) {Direction = ParameterDirection.Input, Value = AspNetUserId},
};
await _db.Database.ExecuteSqlRawAsync("EXEC #returnVal=[dbo].[InitializeNewRegistrationPlayer] #AspNetUserId", #params);
int result = Convert.ToInt32(#params[0].Value);
await Task.FromResult(result);
return result;
}
}
}
Here is the warning message I'm seeing about the Field p being null.
I ended up finding a way to not be null. As you can probably already tell, I am a newb and am blindly stumbling my way through this. That being said, I wouldn't feel comfortable saying this is the "right" answer to the question. Also, even though I got it to not be null it throws a different error now. If I can't figure it out I'll post a separate question.
Here is what I changed the PlayerInitClass to:
public class PlayerInitClass : Microsoft.EntityFrameworkCore.DbContext
{
public PlayerInitClass()
{
this.context = context;
}
public PlayerInitClass context { get; set; }
And this is how I instantiated it (not sure if that's the right way to say it):
PlayerInitClass p = new PlayerInitClass();
Background: I am writing tests around services that make use of ef core. I want to use sqllite as it is relational.
I have written a base class for tests that will use a mock db factory I wrote to setup basic generic things like http mocking and the DAL.
namespace Bll.UnitTests
{
public class TestBase : IDisposable
{
// pass httpclient as dependency, setup messageHandler for stubbing
protected HttpClient httpClient;
protected Mock<HttpMessageHandlerFake> fakeHttpMessageHandler = new Mock<HttpMessageHandlerFake> { CallBase = true };
protected Mock<Logger> loggerMock;
protected DalContext dataContext;
protected MockDbFactory mockDbFactory;
public TestBase()
{
mockDbFactory = new MockDbFactory();
httpClient = new HttpClient(fakeHttpMessageHandler.Object);
dataContext = mockDbFactory.testDb;
loggerMock = new Mock<Logger>(dataContext);
}
public void Dispose()
{
mockDbFactory.Dispose();
}
}
}
Here is my mock db factory that should just setup a connection in memory and seems to work.
using Dal;
using Microsoft.EntityFrameworkCore;
using Moq;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
namespace Bll.UnitTests.Factories
{
// In-memory database only exists while the connection is open
public class MockDbFactory : IDisposable
{
private SqliteConnection connection;
public DalContext testDb;
public MockDbFactory()
{
OpenConnection();
testDb = GetTestDb();
}
public void Dispose()
{
CloseConnection();
}
private void OpenConnection()
{
connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
}
private void CloseConnection()
{
connection.Close();
}
private DalContext GetTestDb()
{
var options = new DbContextOptionsBuilder<DalContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new DalContext(options))
{
context.Database.EnsureCreated();
return context;
}
}
}
}
In my test class datacontext is disposed when I debug my service under test.
public class LocationServiceTest : TestBase
{
private LocationService sut;
public LocationServiceTest(): base()
{
sut = new LocationService(
httpClient,
loggerMock.Object,
dataContext
);
}
[Fact]
public async Task UpdateCountriesAsync_CallsCountryApiAndUpdatesDatabase()
{
// arrange
// setup get country data to return 2 countries
var temp = BuildCountryApiReturnable(2);
fakeHttpMessageHandler.Setup(f => f.Send(It.IsAny<HttpRequestMessage>())).Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(temp)
});
// act
try
{
var result = await sut.UpdateCountriesAsync();
// assert
Assert.True(dataContext.Country.Count() == 2);
Assert.True(dataContext.Location.Count() == 2);
}
catch(Exception e)
{
throw e;
}
}
I think I understand that a using statement is necessary as this will create my connection and dispose of it, but I am trying to do that manually so that I can inject the data context into my service. If I have to wrap everything in a using statement I will be forced to change my service..
To answer your question:
In your MockDbFactory, you already disposed the context by the using clause:
private DalContext GetTestDb()
{
var options = new DbContextOptionsBuilder<DalContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new DalContext(options))
{
context.Database.EnsureCreated();
return context; // context will already be disposed after returning
}
}
You should initiate a new instance of DalContext and handle its disposal in your MockDbFactory.Dispose method instead:
private DalContext GetTestDb()
{
...
testDb = new DalContext(options);
//Other configurations
}
...
public void Dispose()
{
CloseConnection();
testDb.Dispose();
}