From e191cec4a445cd53769aed49806be605939f9fa2 Mon Sep 17 00:00:00 2001 From: tereshkovyv Date: Mon, 25 Dec 2023 23:45:04 +0500 Subject: [PATCH 1/3] 1 --- Game/Domain/GameEntity.cs | 5 +++++ Game/Domain/Player.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/Game/Domain/GameEntity.cs b/Game/Domain/GameEntity.cs index ec7b5ec..abe4a2a 100644 --- a/Game/Domain/GameEntity.cs +++ b/Game/Domain/GameEntity.cs @@ -1,18 +1,22 @@ using System; using System.Collections.Generic; using System.Linq; +using MongoDB.Bson.Serialization.Attributes; namespace Game.Domain { public class GameEntity { + [BsonElement] private readonly List players; + public GameEntity(int turnsCount) : this(Guid.Empty, GameStatus.WaitingToStart, turnsCount, 0, new List()) { } + [BsonConstructor] public GameEntity(Guid id, GameStatus status, int turnsCount, int currentTurnIndex, List players) { Id = id; @@ -31,6 +35,7 @@ public Guid Id public IReadOnlyList Players => players.AsReadOnly(); + [BsonElement] public int TurnsCount { get; } public int CurrentTurnIndex { get; private set; } diff --git a/Game/Domain/Player.cs b/Game/Domain/Player.cs index 9c4f838..2e13cd5 100644 --- a/Game/Domain/Player.cs +++ b/Game/Domain/Player.cs @@ -1,4 +1,5 @@ using System; +using MongoDB.Bson.Serialization.Attributes; namespace Game.Domain { @@ -7,17 +8,20 @@ namespace Game.Domain /// public class Player { + [BsonConstructor] public Player(Guid userId, string name) { UserId = userId; Name = name; } + [BsonElement] public Guid UserId { get; } /// /// Снэпшот имени игрока на момент старта игры. Считайте, что это такое требование к игре. /// + [BsonElement] public string Name { get; } /// From 0475fd2d8ae5abfb67051e34cf281db5c2327827 Mon Sep 17 00:00:00 2001 From: tereshkovyv Date: Mon, 25 Dec 2023 23:59:08 +0500 Subject: [PATCH 2/3] 2 --- ConsoleApp/Program.cs | 7 ++- Game/Domain/MongoUserRepositoty.cs | 87 +++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/ConsoleApp/Program.cs b/ConsoleApp/Program.cs index cf39300..c8660c1 100644 --- a/ConsoleApp/Program.cs +++ b/ConsoleApp/Program.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Game.Domain; +using MongoDB.Driver; namespace ConsoleApp { @@ -12,7 +13,11 @@ class Program private Program(string[] args) { - userRepo = new InMemoryUserRepository(); + var mongoConnectionString = + Environment.GetEnvironmentVariable("GAME_MONGO_CONNECTION_STRING") + ?? "mongodb://localhost:27017"; + var db = new MongoClient(mongoConnectionString).GetDatabase("game"); + userRepo = new MongoUserRepository(db); gameRepo = new InMemoryGameRepository(); } diff --git a/Game/Domain/MongoUserRepositoty.cs b/Game/Domain/MongoUserRepositoty.cs index 51aeba5..404ea60 100644 --- a/Game/Domain/MongoUserRepositoty.cs +++ b/Game/Domain/MongoUserRepositoty.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MongoDB.Driver; namespace Game.Domain @@ -7,53 +8,103 @@ public class MongoUserRepository : IUserRepository { private readonly IMongoCollection userCollection; public const string CollectionName = "users"; - public MongoUserRepository(IMongoDatabase database) { userCollection = database.GetCollection(CollectionName); + userCollection.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(u => u.Login), + new CreateIndexOptions { Unique = true })); } public UserEntity Insert(UserEntity user) { - //TODO: Ищи в документации InsertXXX. - throw new NotImplementedException(); + userCollection.InsertOne(user); + return user; } public UserEntity FindById(Guid id) { - //TODO: Ищи в документации FindXXX - throw new NotImplementedException(); + return userCollection.Find(u => u.Id == id).SingleOrDefault(); } public UserEntity GetOrCreateByLogin(string login) { - //TODO: Это Find или Insert - throw new NotImplementedException(); - } + // Возможен data-race двух параллельных запросов GetOrCreate + // В один запрос c Upsert-ом + try + { + return userCollection.FindOneAndUpdate( + u => u.Login == login, + Builders.Update + .SetOnInsert(u => u.Id, Guid.NewGuid()), + new FindOneAndUpdateOptions + { + IsUpsert = true, + ReturnDocument = ReturnDocument.After + }); - public void Update(UserEntity user) - { - //TODO: Ищи в документации ReplaceXXX - throw new NotImplementedException(); + } + catch (MongoCommandException e) when (e.Code == 11000) + { + return userCollection.FindSync(u => u.Login == login).First(); + } + + //А вот без изысков в два запроса. При создании работает медленнее. + var userEntity = userCollection.FindSync(u => u.Login == login).FirstOrDefault(); + if (userEntity != null) return userEntity; + var newUser = new UserEntity(Guid.NewGuid()) { Login = login }; + Insert(newUser); + return newUser; } - public void Delete(Guid id) + public void Update(UserEntity user) { - throw new NotImplementedException(); + userCollection.ReplaceOne(u => u.Id == user.Id, user); } // Для вывода списка всех пользователей (упорядоченных по логину) // страницы нумеруются с единицы public PageList GetPage(int pageNumber, int pageSize) { - //TODO: Тебе понадобятся SortBy, Skip и Limit - throw new NotImplementedException(); + var totalCount = userCollection.CountDocuments(u => true); + var users = userCollection.Find(u => true) + .SortBy(u => u.Login) + .Skip((pageNumber - 1) * pageSize) + .Limit(pageSize) + .ToList(); + return new PageList( + users, totalCount, pageNumber, pageSize); + } + + public void Delete(Guid id) + { + userCollection.DeleteOne(u => u.Id == id); } - // Не нужно реализовывать этот метод public void UpdateOrInsert(UserEntity user, out bool isInserted) { - throw new NotImplementedException(); + var result = userCollection.ReplaceOne( + u => u.Id == user.Id, + user, + new ReplaceOptions + { + IsUpsert = true, + }); + isInserted = result.IsAcknowledged && result.ModifiedCount == 0; + } + + // Пример одного специализированного метода репозитория, вместо серии более стандартных. + // Пример частичного обновления нескольких сущностей в БД + // Сейчас вместо этого кода работает program.UpdatePlayersWhenGameFinished + public void UpdatePlayersWhenGameIsFinished(IEnumerable userIds) + { + var updateBuilder = Builders.Update; + userCollection.UpdateMany( + Builders.Filter.In(u => u.Id, userIds), + updateBuilder.Combine( + updateBuilder.Inc(u => u.GamesPlayed, 1), + updateBuilder.Set(u => u.CurrentGameId, null) + )); } } } \ No newline at end of file From 0058449f3190a6d64b6c84249ad84b958a0f1087 Mon Sep 17 00:00:00 2001 From: tereshkovyv Date: Tue, 26 Dec 2023 00:02:03 +0500 Subject: [PATCH 3/3] 3 --- ConsoleApp/Program.cs | 2 +- Game/Domain/MongoGameRepository.cs | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ConsoleApp/Program.cs b/ConsoleApp/Program.cs index c8660c1..0a5aaf3 100644 --- a/ConsoleApp/Program.cs +++ b/ConsoleApp/Program.cs @@ -18,7 +18,7 @@ private Program(string[] args) ?? "mongodb://localhost:27017"; var db = new MongoClient(mongoConnectionString).GetDatabase("game"); userRepo = new MongoUserRepository(db); - gameRepo = new InMemoryGameRepository(); + gameRepo = new MongoGameRepository(db); } public static void Main(string[] args) diff --git a/Game/Domain/MongoGameRepository.cs b/Game/Domain/MongoGameRepository.cs index 86873d4..6195580 100644 --- a/Game/Domain/MongoGameRepository.cs +++ b/Game/Domain/MongoGameRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using MongoDB.Driver; - namespace Game.Domain { // TODO Сделать по аналогии с MongoUserRepository @@ -9,37 +8,42 @@ public class MongoGameRepository : IGameRepository { public const string CollectionName = "games"; + private readonly IMongoCollection collection; + public MongoGameRepository(IMongoDatabase db) { + collection = db.GetCollection(CollectionName); } public GameEntity Insert(GameEntity game) { - throw new NotImplementedException(); + collection.InsertOne(game); + return game; } public GameEntity FindById(Guid gameId) { - throw new NotImplementedException(); + return collection.Find(g => g.Id == gameId).SingleOrDefault(); } public void Update(GameEntity game) { - throw new NotImplementedException(); + collection.ReplaceOne(g => g.Id == game.Id, game); } // Возвращает не более чем limit игр со статусом GameStatus.WaitingToStart public IList FindWaitingToStart(int limit) { - //TODO: Используй Find и Limit - throw new NotImplementedException(); + return collection.Find(g => g.Status == GameStatus.WaitingToStart) + .Limit(limit) + .ToList(); } // Обновляет игру, если она находится в статусе GameStatus.WaitingToStart public bool TryUpdateWaitingToStart(GameEntity game) { - //TODO: Для проверки успешности используй IsAcknowledged и ModifiedCount из результата - throw new NotImplementedException(); + var result = collection.ReplaceOne(g => g.Id == game.Id && g.Status == GameStatus.WaitingToStart, game); + return result.IsAcknowledged && result.ModifiedCount > 0; } } } \ No newline at end of file