public void TestAddReviewConcurrencyFixed() { //SETUP var options = SqliteInMemory.CreateOptions <SqlEventsDbContext>(); var config = new GenericEventRunnerConfig(); config.RegisterSaveChangesExceptionHandler <SqlEventsDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); var context = options.CreateDbWithDiForHandlers <SqlEventsDbContext, ReviewAddedHandler>(config: config); context.Database.EnsureCreated(); var book = WithEventsEfTestData.CreateDummyBookOneAuthor(); context.Add(book); context.SaveChanges(); book.AddReview(4, "OK", "me"); //This simulates adding a review with NumStars of 2 before the AddReview context.Database.ExecuteSqlRaw( "UPDATE Books SET ReviewsCount = @p0, ReviewsAverageVotes = @p1 WHERE BookId = @p2", 1, 2, book.BookId); //ATTEMPT context.SaveChanges(); //VERIFY var foundBook = context.Find <BookWithEvents>(book.BookId); foundBook.ReviewsCount.ShouldEqual(2); foundBook.ReviewsAverageVotes.ShouldEqual(6.0 / 2.0); }
public void TestBookDeletedConcurrencyFixed() { //SETUP var options = SqliteInMemory.CreateOptions <SqlEventsDbContext>(); var config = new GenericEventRunnerConfig(); config.RegisterSaveChangesExceptionHandler <SqlEventsDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); var context = options.CreateDbWithDiForHandlers <SqlEventsDbContext, ReviewAddedHandler>(config: config); context.Database.EnsureCreated(); var books = WithEventsEfTestData.CreateDummyBooks(2); context.AddRange(books); context.SaveChanges(); //ATTEMPT books.First().AuthorsLink.Last().Author.ChangeName("New common name"); //This simulates changing the AuthorsOrdered value context.Database.ExecuteSqlRaw("DELETE FROM Books WHERE BookId = @p0", books.First().BookId); //ATTEMPT context.SaveChanges(); //VERIFY var readBooks = context.Books.ToList(); readBooks.Count().ShouldEqual(1); readBooks.First().AuthorsOrdered.ShouldEqual("Author0001, New common name"); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) //#A { services.AddControllersWithViews() //#B //.AddRazorRuntimeCompilation() //This recompile a razor page if you edit it while the app is running //Added this because my logs display needs the enum as a string .AddJsonOptions(opts => { opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); var bookAppSettings = Configuration.GetBookAppSettings(); services.AddSingleton(bookAppSettings); //This gets the correct sql connection string based on the BookAppSettings var sqlConnection = Configuration.GetCorrectSqlConnection(bookAppSettings); //This registers both DbContext. Each MUST have a unique MigrationsHistoryTable for Migrations to work services.AddDbContext <BookDbContext>( options => options.UseSqlServer(sqlConnection, dbOptions => dbOptions.MigrationsHistoryTable("BookMigrationHistoryName"))); services.AddDbContext <OrderDbContext>( options => options.UseSqlServer(sqlConnection, dbOptions => dbOptions.MigrationsHistoryTable("OrderMigrationHistoryName"))); services.AddHttpContextAccessor(); services.Configure <BookAppSettings>(options => Configuration.GetSection(nameof(BookAppSettings)).Bind(options)); services.AddSingleton <IMenuBuilder, MenuBuilder>(); //This registers all the services across all the projects in this application var diLogs = services.RegisterAssemblyPublicNonGenericClasses( Assembly.GetAssembly(typeof(IDisplayOrdersService)), Assembly.GetAssembly(typeof(IPlaceOrderBizLogic)), Assembly.GetAssembly(typeof(IPlaceOrderDbAccess))) .AsPublicImplementedInterfaces(); //BookApp.Books startup var test = services.RegisterBooksServices(Configuration); //Register EfCore.GenericEventRunner var eventConfig = new GenericEventRunnerConfig(); eventConfig.RegisterSaveChangesExceptionHandler <BookDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); eventConfig.AddActionToRunAfterDetectChanges <BookDbContext>(BookDetectChangesExtensions.ChangeChecker); var logs = services.RegisterGenericEventRunner(eventConfig, Assembly.GetAssembly(typeof(ReviewAddedHandler)) //SQL cached values event handlers ); //Register EfCoreGenericServices services.ConfigureGenericServicesEntities(typeof(BookDbContext)) .ScanAssemblesForDtos( BooksStartupInfo.GenericServiceAssemblies.ToArray() ).RegisterGenericServices(); var softLogs = services.RegisterSoftDelServicesAndYourConfigurations(); }
public static void RegisterInfrastructureDi(this IServiceCollection services) { //This provides a SaveChangesExceptionHandler which handles concurrency issues around ReviewsCount and ReviewsAverageVotes var config = new GenericEventRunnerConfig(); config.RegisterSaveChangesExceptionHandler <SqlEventsDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); //Because I haven't provided any assemblies this will scan this assembly for event handlers services.RegisterGenericEventRunner(config); }
public void TestAddSaveChangesExceptionHandlerButStillFailsOnOtherDbExceptions() { //SETUP var options = SqliteInMemory.CreateOptions <SqlEventsDbContext>(); var config = new GenericEventRunnerConfig(); config.RegisterSaveChangesExceptionHandler <SqlEventsDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); var context = options.CreateDbWithDiForHandlers <SqlEventsDbContext, ReviewAddedHandler>(config: config); context.Database.EnsureCreated(); var review = new ReviewWithEvents(1, "hello", "Me", Guid.NewGuid()); context.Add(review); //ATTEMPT var ex = Assert.Throws <DbUpdateException>(() => context.SaveChanges()); //VERIFY ex.InnerException.Message.ShouldEqual("SQLite Error 19: 'FOREIGN KEY constraint failed'."); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) //#A { services.AddControllersWithViews() //#B //.AddRazorRuntimeCompilation() //This recompile a razor page if you edit it while the app is running //Added this because my logs display needs the enum as a string .AddJsonOptions(opts => { opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); var connectionString = Configuration.GetConnectionString("DefaultConnection"); //This registers both DbContext. Each MUST have a unique MigrationsHistoryTable for Migrations to work services.AddDbContext <BookDbContext>( options => options.UseSqlServer(connectionString, dbOptions => dbOptions.MigrationsHistoryTable("BookMigrationHistoryName"))); services.AddHttpContextAccessor(); //ModMod.Books startup var test = services.RegisterBooksServices(Configuration); //Register EfCore.GenericEventRunner var eventConfig = new GenericEventRunnerConfig(); eventConfig.RegisterSaveChangesExceptionHandler <BookDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); eventConfig.AddActionToRunAfterDetectChanges <BookDbContext>(BookDetectChangesExtensions.ChangeChecker); var logs = services.RegisterGenericEventRunner(eventConfig, Assembly.GetAssembly(typeof(ReviewAddedHandler)) //SQL cached values event handlers ); //Register EfCoreGenericServices services.ConfigureGenericServicesEntities(typeof(BookDbContext)) .ScanAssemblesForDtos( BooksStartupInfo.GenericServiceAssemblies.ToArray() ).RegisterGenericServices(); var softLogs = services.RegisterSoftDelServicesAndYourConfigurations(); }
public async Task TestUpdateProductStockConcurrencyWithHandlerAsync() { //SETUP var options = SqliteInMemory.CreateOptions <ExampleDbContext>(); var config = new GenericEventRunnerConfig(); config.RegisterSaveChangesExceptionHandler <ExampleDbContext>(CatchAndFixConcurrencyException); var context = options.CreateAndSeedDbWithDiForHandlers <OrderCreatedHandler>(config: config); { var stock = context.ProductStocks.OrderBy(x => x.NumInStock).First(); //ATTEMPT stock.NumAllocated = 2; context.Database.ExecuteSqlRaw( "UPDATE ProductStocks SET NumAllocated = @p0 WHERE ProductName = @p1", 3, stock.ProductName); await context.SaveChangesAsync(); //VERIFY var foundStock = context.Find <ProductStock>(stock.ProductName); foundStock.NumAllocated.ShouldEqual(5); } }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) //#A { services.AddControllersWithViews() //#B .AddRazorRuntimeCompilation() //This recompile a razor page if you edit it while the app is running //Added this because my logs display needs the enum as a string .AddJsonOptions(opts => { opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); var bookAppSettings = Configuration.GetBookAppSettings(); services.AddSingleton(bookAppSettings); //This gets the correct sql connection string based on the BookAppSettings var sqlConnection = Configuration.GetCorrectSqlConnection(bookAppSettings); //This registers both DbContext. Each MUST have a unique MigrationsHistoryTable for Migrations to work services.AddDbContext <BookDbContext>( options => options.UseSqlServer(sqlConnection, dbOptions => dbOptions.MigrationsHistoryTable("BookMigrationHistoryName"))); services.AddDbContext <OrderDbContext>( options => options.UseSqlServer(sqlConnection, dbOptions => dbOptions.MigrationsHistoryTable("OrderMigrationHistoryName"))); var cosmosSettings = Configuration.GetCosmosDbSettings(bookAppSettings); if (cosmosSettings != null) { services.AddDbContext <CosmosDbContext>(options => options.UseCosmos( cosmosSettings.ConnectionString, cosmosSettings.DatabaseName)); } else { services.AddSingleton <CosmosDbContext>(_ => null); } services.AddHttpContextAccessor(); services.Configure <BookAppSettings>(options => Configuration.GetSection(nameof(BookAppSettings)).Bind(options)); services.AddSingleton <IMenuBuilder, MenuBuilder>(); //This registers all the services across all the projects in this application var diLogs = services.RegisterAssemblyPublicNonGenericClasses( Assembly.GetAssembly(typeof(ICheckFixCacheValuesService)), Assembly.GetAssembly(typeof(BookListDto)), Assembly.GetAssembly(typeof(IBookToCosmosBookService)), Assembly.GetAssembly(typeof(IBookGenerator)), Assembly.GetAssembly(typeof(IPlaceOrderBizLogic)), Assembly.GetAssembly(typeof(IPlaceOrderDbAccess)), Assembly.GetAssembly(typeof(IListBooksCachedService)), Assembly.GetAssembly(typeof(ICosmosEfListNoSqlBooksService)), Assembly.GetAssembly(typeof(IListBooksService)), Assembly.GetAssembly(typeof(IDisplayOrdersService)), Assembly.GetAssembly(typeof(IListUdfsBooksService)), Assembly.GetAssembly(typeof(IListUdfsBooksService)) ) .AsPublicImplementedInterfaces(); services.AddHostedService <CheckFixCacheBackground>(); //Register EfCore.GenericEventRunner var eventConfig = new GenericEventRunnerConfig { NotUsingDuringSaveHandlers = cosmosSettings == null //This stops any attempts to update cosmos db if not turned on }; eventConfig.RegisterSaveChangesExceptionHandler <BookDbContext>(BookWithEventsConcurrencyHandler.HandleCacheValuesConcurrency); eventConfig.AddActionToRunAfterDetectChanges <BookDbContext>(BookDetectChangesExtensions.ChangeChecker); var logs = services.RegisterGenericEventRunner(eventConfig, Assembly.GetAssembly(typeof(ReviewAddedHandler)), //SQL cached values event handlers Assembly.GetAssembly(typeof(BookChangeHandlerAsync)) //Cosmos Db event handlers ); //Register EfCoreGenericServices services.ConfigureGenericServicesEntities(typeof(BookDbContext), typeof(OrderDbContext)) .ScanAssemblesForDtos( Assembly.GetAssembly(typeof(BookListDto)), Assembly.GetAssembly(typeof(AddReviewDto)) ).RegisterGenericServices(); var softLogs = services.RegisterSoftDelServicesAndYourConfigurations(); }