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");
        }
Пример #3
0
        // 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();
        }
Пример #4
0
        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'.");
        }
Пример #6
0
        // 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();
        }
Пример #7
0
        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);
            }
        }
Пример #8
0
        // 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();
        }