public void SendWelcomeEmail(Guid userId) { /* * Demo of forcing the creation of a new DbContextScope * to ensure that changes made to the model in this service * method are persisted even if that method happens to get * called within the scope of a wider business transaction * that eventually fails for any reason. * * This is an advanced feature that should be used as rarely * as possible (and ideally, never). */ // We're going to send a welcome email to the provided user // (if one hasn't been sent already). Once sent, we'll update // that User entity in our DB to record that its Welcome email // has been sent. // Emails can't be rolled-back. Once they're sent, they're sent. // So once the email has been sent successfully, we absolutely // must persist this fact in our DB. Even if that method is called // by another busines logic service method as part of a wider // business transaction and even if that parent business transaction // ends up failing for any reason, we still must ensure that // we have recorded the fact that the Welcome email has been sent. // Otherwise, we would risk spamming our users with repeated Welcome // emails. // Force the creation of a new DbContextScope so that the changes we make here are // guaranteed to get persisted regardless of what happens after this method has completed. using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create(DbContextScopeOption.ForceCreateNew)) { UserManagementDbContext dbContext = dbContextScope.DbContexts.Get <UserManagementDbContext>(); #if EF6 User user = dbContext.Users.Find(userId); #elif EFCore var user = dbContext.Users.SingleOrDefault(x => x.Id == userId); #endif if (user == null) { throw new ArgumentException($"Invalid userId provided: {userId}. Couldn't find a User with this ID."); } if (!user.WelcomeEmailSent) { SendEmail(user.Email); user.WelcomeEmailSent = true; } dbContextScope.SaveChanges(); // When you force the creation of a new DbContextScope, you must force the parent // scope (if any) to reload the entities you've modified here. Otherwise, the method calling // you might not be able to see the changes you made here. dbContextScope.RefreshEntitiesInParentScope(new List <User> { user }); } }
public void UpdateCreditScoreForAllUsers() { /* * Demo of DbContextScope + parallel programming. */ using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create()) { //-- Get all users UserManagementDbContext dbContext = dbContextScope.DbContexts.Get <UserManagementDbContext>(); List <Guid> userIds = dbContext.Users.Select(u => u.Id).ToList(); Console.WriteLine("Found {0} users in the database. Will calculate and store their credit scores in parallel.", userIds.Count); //-- Calculate and store the credit score of each user // We're going to imagine that calculating a credit score of a user takes some time. // So we'll do it in parallel. // You MUST call SuppressAmbientContext() when kicking off a parallel execution flow // within a DbContextScope. Otherwise, this DbContextScope will remain the ambient scope // in the parallel flows of execution, potentially leading to multiple threads // accessing the same DbContext instance. using (_dbContextScopeFactory.SuppressAmbientContext()) { Parallel.ForEach(userIds, UpdateCreditScore); } // Note: SaveChanges() isn't going to do anything in this instance since all the changes // were actually made and saved in separate DbContextScopes created in separate threads. dbContextScope.SaveChanges(); } }
/// <summary> /// Create a new game /// </summary> /// <param name="playerName"></param> /// <param name="numberOfPlayers"></param> /// <returns>Game info</returns> public Game Create(string playerName, int numberOfPlayers) { using (IDbContextScope contextScope = _dbContextFactory.Create()) { //create player var player = new Player() { Name = playerName }; //create game var game = new Game() { NumberOfPlayers = numberOfPlayers, Status = numberOfPlayers > 1 ? 0 : 1 }; //add player to game game.Players.Add(player); //add game and player to database _gameService.Add(game); contextScope.SaveChanges(); game.PlayerId = player.Id; return(game); } }
public void CreateUser(UserCreationSpec userToCreate) { if (userToCreate == null) { throw new ArgumentNullException(nameof(userToCreate)); } userToCreate.Validate(); /* * Typical usage of DbContextScope for a read-write business transaction. * It's as simple as it looks. */ using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create()) { //-- Build domain model User user = new User() { Id = userToCreate.Id, Name = userToCreate.Name, Email = userToCreate.Email, WelcomeEmailSent = false, CreatedOn = DateTime.UtcNow }; //-- Persist _userRepository.Add(user); dbContextScope.SaveChanges(); } }
public void CreateListOfUsersWithIntentionalFailure(params UserCreationSpec[] usersToCreate) { /* * Here, we'll verify that inner DbContextScopes really join the parent scope and * don't persist their changes until the parent scope completes successfully. */ bool firstUser = true; using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create()) { foreach (UserCreationSpec toCreate in usersToCreate) { if (firstUser) { CreateUser(toCreate); Console.WriteLine("Successfully created a new User named '{0}'.", toCreate.Name); firstUser = false; } else { // OK. So we've successfully persisted one user. // We're going to simulate a failure when attempting to // persist the second user and see what ends up getting // persisted in the DB. throw new Exception($"Oh no! An error occurred when attempting to create user named '{toCreate.Name}' in our database."); } } dbContextScope.SaveChanges(); } }
protected T RunInNewContextScope <T>(Func <T> function) { using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create(DbContextScopeOption.ForceCreateNew)) { T result = function(); dbContextScope.SaveChanges(); return(result); } }
protected virtual void Dispose(bool disposing) { if (disposing) { scope.DbContexts.Get <TDbContext>().ChangeTracker.DetectChanges(); scope.SaveChanges(); scope.Dispose(); } }
protected T RunInContextScope <T>(Func <T> function, bool isReadOnly = false) { if (isReadOnly) { using (IDbContextReadOnlyScope dbContextScope = _dbContextScopeFactory.CreateReadOnly()) { return(function()); } } else { using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create()) { T result = function(); dbContextScope.SaveChanges(); return(result); } } }
/// <summary> /// Join a new player to an existing game /// </summary> /// <param name="playerName"></param> /// <param name="gameId"></param> /// <returns>Game info</returns> public async Task <Game> Join(string playerName, Guid gameId) { using (IDbContextScope contextScope = _dbContextFactory.Create()) { var game = await _gameService.GetAsync(gameId); //check if game exists if (game == null) { throw new ApplicationException("Invalid game"); } //check status and number of players allowed if (game.NumberOfPlayers == 1 || game.Status != 0) { throw new ApplicationException("This game is not available for new players"); } //add player to game var player = new Player() { Name = playerName, GameId = gameId }; game.Players.Add(player); //if game reach the number of player, change game status if (game.NumberOfPlayers == game.Players.Count) { game.Status = 1; } //update database _gameService.Update(game); contextScope.SaveChanges(); game.PlayerId = player.Id; return(game); } }
public void CreateListOfUsers(params UserCreationSpec[] usersToCreate) { /* * Example of DbContextScope nesting in action. * * We already have a service method - CreateUser() - that knows how to create a new user * and implements all the business rules around the creation of a new user * (e.g. validation, initialization, sending notifications to other domain model objects...). * * So we'll just call it in a loop to create the list of new users we've * been asked to create. * * Of course, since this is a business logic service method, we are making * an implicit guarantee to whoever is calling us that the changes we make to * the system will be either committed or rolled-back in an atomic manner. * I.e. either all the users we've been asked to create will get persisted * or none of them will. It would be disastrous to have a partial failure here * and end up with some users but not all having been created. * * DbContextScope makes this trivial to implement. * * The inner DbContextScope instance that the CreateUser() method creates * will join our top-level scope. This ensures that the same DbContext instance is * going to be used throughout this business transaction. * */ using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create()) { foreach (UserCreationSpec toCreate in usersToCreate) { CreateUser(toCreate); } // All the changes will get persisted here dbContextScope.SaveChanges(); } }
public void UpdateCreditScore(Guid userId) { using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create()) { UserManagementDbContext dbContext = dbContextScope.DbContexts.Get <UserManagementDbContext>(); #if EF6 User user = dbContext.Users.Find(userId); #elif EFCore var user = dbContext.Users.SingleOrDefault(x => x.Id == userId); #endif if (user == null) { throw new ArgumentException($"Invalid userId provided: {userId}. Couldn't find a User with this ID."); } // Simulate the calculation of a credit score taking some time Random random = new Random(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(random.Next(300, 1000)); user.CreditScore = random.Next(1, 100); dbContextScope.SaveChanges(); } }
/// <summary> /// Verify the player's guess /// </summary> /// <param name="playerId"></param> /// <param name="gameId"></param> /// <param name="sequence">the player's guess</param> /// <returns>Game info</returns> public async Task <Game> Guess(Guid playerId, Guid gameId, string sequence) { using (IDbContextScope contextScope = _dbContextFactory.Create()) { var game = await _gameService.GetAsync(gameId); //check if game exists if (game == null) { throw new ApplicationException("Invalid game"); } //check if player belongs to the game if (!game.Players.Any(p => p.Id == playerId)) { throw new ApplicationException("Player dont belong to this game"); } var playerGuesses = game.Players.First(p => p.Id == playerId).Guesses; if (game.Guesses.Count() > 0) { //check if there are players that didnt play if (game.Players.Select(p => p.Guesses.Count).Min() < playerGuesses.Count()) { throw new ApplicationException("Wait for other players guess"); } //check if the game is finished if (game.Players.Select(p => p.Guesses.Count).Min() == game.Players.Select(p => p.Guesses.Count).Max() && game.Status == 2) { throw new ApplicationException("Game is finished"); } } var guess = new Guess() { GameId = gameId, PlayerId = playerId, Sequence = sequence }; //check the guess hits guess.CheckHits(game.Sequence); //if the player founds the sequence, change game status if (guess.ExactCount == game.SequenceLength) { game.Status = 2; game.Players.First(p => p.Id == playerId).Winner = true; } //add guess to game game.Guesses.Add(guess); game.PlayerId = playerId; _gameService.Update(game); contextScope.SaveChanges(); return(game); } }
public int SaveChanges() { var i = _dbContextScope.SaveChanges(); return(i); }