using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Master; using Marco.Pms.Model.MongoDBModels.Masters; using Marco.Pms.Model.MongoDBModels.Project; using Marco.Pms.Model.Projects; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using MongoDB.Bson; using MongoDB.Driver; namespace Marco.Pms.Helpers { public class ProjectCache { private readonly IMongoCollection _projectCollection; private readonly IMongoCollection _taskCollection; public ProjectCache(ApplicationDbContext context, IConfiguration configuration) { var connectionString = configuration["MongoDB:ConnectionString"]; var mongoUrl = new MongoUrl(connectionString); var client = new MongoClient(mongoUrl); // Your MongoDB connection string var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name _projectCollection = mongoDB.GetCollection("ProjectDetails"); _taskCollection = mongoDB.GetCollection("WorkItemDetails"); } #region=================================================================== Project Cache Helper =================================================================== public async Task AddProjectDetailsToCache(ProjectMongoDB projectDetails) { await _projectCollection.InsertOneAsync(projectDetails); var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); var indexOptions = new CreateIndexOptions { ExpireAfter = TimeSpan.Zero // required for fixed expiration time }; var indexModel = new CreateIndexModel(indexKeys, indexOptions); await _projectCollection.Indexes.CreateOneAsync(indexModel); } public async Task AddProjectDetailsListToCache(List projectDetailsList) { // 1. Add a guard clause to avoid an unnecessary database call for an empty list. if (projectDetailsList == null || !projectDetailsList.Any()) { return; } // 2. Perform the insert operation. This is the only responsibility of this method. await _projectCollection.InsertManyAsync(projectDetailsList); await InitializeCollectionAsync(); } private async Task InitializeCollectionAsync() { // 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field. var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); var indexOptions = new CreateIndexOptions { // This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached. ExpireAfter = TimeSpan.FromSeconds(0) }; var indexModel = new CreateIndexModel(indexKeys, indexOptions); // 2. Create the index. This is an idempotent operation if the index already exists. // Use CreateOneAsync since we are only creating a single index. await _projectCollection.Indexes.CreateOneAsync(indexModel); } public async Task UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus) { // Build the update definition var updates = Builders.Update.Combine( Builders.Update.Set(r => r.Name, project.Name), Builders.Update.Set(r => r.ProjectAddress, project.ProjectAddress), Builders.Update.Set(r => r.ShortName, project.ShortName), Builders.Update.Set(r => r.ProjectStatus, new StatusMasterMongoDB { Id = projectStatus.Id.ToString(), Status = projectStatus.Status }), Builders.Update.Set(r => r.StartDate, project.StartDate), Builders.Update.Set(r => r.EndDate, project.EndDate), Builders.Update.Set(r => r.ContactPerson, project.ContactPerson) ); // Perform the update var result = await _projectCollection.UpdateOneAsync( filter: r => r.Id == project.Id.ToString(), update: updates ); if (result.MatchedCount == 0) { return false; } return true; } public async Task GetProjectDetailsFromCache(Guid projectId) { // Build filter and projection to exclude large 'Buildings' list var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); var projection = Builders.Projection.Exclude(p => p.Buildings); // Perform query var project = await _projectCollection .Find(filter) .Project(projection) .FirstOrDefaultAsync(); return project; } public async Task GetProjectDetailsWithBuildingsFromCache(Guid projectId) { // Build filter and projection to exclude large 'Buildings' list var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); // Perform query var project = await _projectCollection .Find(filter) .FirstOrDefaultAsync(); return project; } public async Task> GetProjectDetailsListFromCache(List projectIds) { List stringProjectIds = projectIds.Select(p => p.ToString()).ToList(); var filter = Builders.Filter.In(p => p.Id, stringProjectIds); var projection = Builders.Projection.Exclude(p => p.Buildings); var projects = await _projectCollection .Find(filter) .Project(projection) .ToListAsync(); return projects; } public async Task DeleteProjectByIdFromCacheAsync(Guid projectId) { var filter = Builders.Filter.Eq(e => e.Id, projectId.ToString()); var result = await _projectCollection.DeleteOneAsync(filter); return result.DeletedCount > 0; } public async Task RemoveProjectsFromCacheAsync(List projectIds) { var stringIds = projectIds.Select(id => id.ToString()).ToList(); var filter = Builders.Filter.In(p => p.Id, stringIds); var result = await _projectCollection.DeleteManyAsync(filter); return result.DeletedCount > 0; } #endregion #region=================================================================== Project infrastructure Cache Helper =================================================================== public async Task AddBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId) { var stringProjectId = projectId.ToString(); // Add Building if (building != null) { var buildingMongo = new BuildingMongoDB { Id = building.Id.ToString(), BuildingName = building.Name, Description = building.Description, PlannedWork = 0, CompletedWork = 0, Floors = new List() }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); var update = Builders.Update.Push("Buildings", buildingMongo); var result = await _projectCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { return; } return; } // Add Floor if (floor != null) { var floorMongo = new FloorMongoDB { Id = floor.Id.ToString(), FloorName = floor.FloorName, PlannedWork = 0, CompletedWork = 0, WorkAreas = new List() }; var filter = Builders.Filter.And( Builders.Filter.Eq(p => p.Id, stringProjectId), Builders.Filter.Eq("Buildings._id", floor.BuildingId.ToString()) ); var update = Builders.Update.Push("Buildings.$.Floors", floorMongo); var result = await _projectCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { return; } return; } // Add WorkArea if (workArea != null && buildingId != null) { var workAreaMongo = new WorkAreaMongoDB { Id = workArea.Id.ToString(), AreaName = workArea.AreaName, PlannedWork = 0, CompletedWork = 0 }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); var arrayFilters = new List { new JsonArrayFilterDefinition("{ 'b._id': '" + buildingId + "' }"), new JsonArrayFilterDefinition("{ 'f._id': '" + workArea.FloorId + "' }") }; var update = Builders.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo); var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { return; } return; } } public async Task UpdateBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId) { var stringProjectId = projectId.ToString(); // Update Building if (building != null) { var filter = Builders.Filter.And( Builders.Filter.Eq(p => p.Id, stringProjectId), Builders.Filter.Eq("Buildings._id", building.Id.ToString()) ); var update = Builders.Update.Combine( Builders.Update.Set("Buildings.$.BuildingName", building.Name), Builders.Update.Set("Buildings.$.Description", building.Description) ); var result = await _projectCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { return false; } return true; } // Update Floor if (floor != null) { var arrayFilters = new List { new JsonArrayFilterDefinition("{ 'b._id': '" + floor.BuildingId + "' }"), new JsonArrayFilterDefinition("{ 'f._id': '" + floor.Id + "' }") }; var update = Builders.Update.Set("Buildings.$[b].Floors.$[f].FloorName", floor.FloorName); var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { return false; } return true; } // Update WorkArea if (workArea != null && buildingId != null) { var arrayFilters = new List { new JsonArrayFilterDefinition("{ 'b._id': '" + buildingId + "' }"), new JsonArrayFilterDefinition("{ 'f._id': '" + workArea.FloorId + "' }"), new JsonArrayFilterDefinition("{ 'a._id': '" + workArea.Id + "' }") }; var update = Builders.Update.Set("Buildings.$[b].Floors.$[f].WorkAreas.$[a].AreaName", workArea.AreaName); var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { return false; } return true; } return false; } public async Task?> GetBuildingInfraFromCache(Guid projectId) { // Filter by project ID var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); // Project only the "Buildings" field from the document var buildings = await _projectCollection .Find(filter) .Project(p => p.Buildings) .FirstOrDefaultAsync(); return buildings; } public async Task UpdatePlannedAndCompleteWorksInBuildingFromCache(Guid workAreaId, double plannedWork, double completedWork) { var filter = Builders.Filter.Eq("Buildings.Floors.WorkAreas._id", workAreaId.ToString()); var project = await _projectCollection.Find(filter).FirstOrDefaultAsync(); string? selectedBuildingId = null; string? selectedFloorId = null; string? selectedWorkAreaId = null; foreach (var building in project.Buildings) { foreach (var floor in building.Floors) { foreach (var area in floor.WorkAreas) { if (area.Id == workAreaId.ToString()) { selectedWorkAreaId = area.Id; selectedFloorId = floor.Id; selectedBuildingId = building.Id; } } } } var arrayFilters = new List { new JsonArrayFilterDefinition("{ 'b._id': '" + selectedBuildingId + "' }"), new JsonArrayFilterDefinition("{ 'f._id': '" + selectedFloorId + "' }"), new JsonArrayFilterDefinition("{ 'a._id': '" + selectedWorkAreaId + "' }") }; var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var update = Builders.Update .Inc("Buildings.$[b].Floors.$[f].WorkAreas.$[a].PlannedWork", plannedWork) .Inc("Buildings.$[b].Floors.$[f].WorkAreas.$[a].CompletedWork", completedWork) .Inc("Buildings.$[b].Floors.$[f].PlannedWork", plannedWork) .Inc("Buildings.$[b].Floors.$[f].CompletedWork", completedWork) .Inc("Buildings.$[b].PlannedWork", plannedWork) .Inc("Buildings.$[b].CompletedWork", completedWork) .Inc("PlannedWork", plannedWork) .Inc("CompletedWork", completedWork); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); } public async Task GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId) { var pipeline = new[] { new BsonDocument("$unwind", "$Buildings"), new BsonDocument("$unwind", "$Buildings.Floors"), new BsonDocument("$unwind", "$Buildings.Floors.WorkAreas"), new BsonDocument("$match", new BsonDocument("Buildings.Floors.WorkAreas._id", workAreaId.ToString())), new BsonDocument("$project", new BsonDocument { { "_id", 0 }, { "ProjectId", "$_id" }, { "ProjectName", "$Name" }, { "PlannedWork", "$PlannedWork" }, { "CompletedWork", "$CompletedWork" }, { "Building", new BsonDocument { { "_id", "$Buildings._id" }, { "BuildingName", "$Buildings.BuildingName" }, { "Description", "$Buildings.Description" }, { "PlannedWork", "$Buildings.PlannedWork" }, { "CompletedWork", "$Buildings.CompletedWork" } } }, { "Floor", new BsonDocument { { "_id", "$Buildings.Floors._id" }, { "FloorName", "$Buildings.Floors.FloorName" }, { "PlannedWork", "$Buildings.Floors.PlannedWork" }, { "CompletedWork", "$Buildings.Floors.CompletedWork" } } }, { "WorkArea", "$Buildings.Floors.WorkAreas" } }) }; var result = await _projectCollection.Aggregate(pipeline).FirstOrDefaultAsync(); if (result == null) return null; return result; } #endregion #region=================================================================== WorkItem Cache Helper =================================================================== public async Task> GetWorkItemsByWorkAreaIdsFromCache(List workAreaIds) { var stringWorkAreaIds = workAreaIds.Select(wa => wa.ToString()).ToList(); var filter = Builders.Filter.In(w => w.WorkAreaId, stringWorkAreaIds); var workItems = await _taskCollection // replace with your actual collection name .Find(filter) .ToListAsync(); return workItems; } public async Task ManageWorkItemDetailsToCache(List workItems) { foreach (WorkItemMongoDB workItem in workItems) { var filter = Builders.Filter.Eq(p => p.Id, workItem.Id.ToString()); var updates = Builders.Update.Combine( Builders.Update.Set(r => r.WorkAreaId, workItem.WorkAreaId.ToString()), Builders.Update.Set(r => r.ParentTaskId, (workItem.ParentTaskId != null ? workItem.ParentTaskId.ToString() : null)), Builders.Update.Set(r => r.PlannedWork, workItem.PlannedWork), Builders.Update.Set(r => r.TodaysAssigned, workItem.TodaysAssigned), Builders.Update.Set(r => r.CompletedWork, workItem.CompletedWork), Builders.Update.Set(r => r.Description, workItem.Description), Builders.Update.Set(r => r.TaskDate, workItem.TaskDate), Builders.Update.Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1)), Builders.Update.Set(r => r.ActivityMaster, workItem.ActivityMaster), Builders.Update.Set(r => r.WorkCategoryMaster, workItem.WorkCategoryMaster) ); var options = new UpdateOptions { IsUpsert = true }; var result = await _taskCollection.UpdateOneAsync(filter, updates, options); if (result.UpsertedId != null) { var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); var indexOptions = new CreateIndexOptions { ExpireAfter = TimeSpan.Zero // required for fixed expiration time }; var indexModel = new CreateIndexModel(indexKeys, indexOptions); await _taskCollection.Indexes.CreateOneAsync(indexModel); } } } public async Task> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId) { var filter = Builders.Filter.Eq(p => p.WorkAreaId, workAreaId.ToString()); var options = new UpdateOptions { IsUpsert = true }; var workItems = await _taskCollection .Find(filter) .ToListAsync(); return workItems; } public async Task GetWorkItemDetailsByIdFromCache(Guid id) { var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); var options = new UpdateOptions { IsUpsert = true }; var workItem = await _taskCollection .Find(filter) .FirstOrDefaultAsync(); return workItem; } public async Task UpdatePlannedAndCompleteWorksInWorkItemToCache(Guid id, double plannedWork, double completedWork, double todaysAssigned) { var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); var updates = Builders.Update .Inc("PlannedWork", plannedWork) .Inc("CompletedWork", completedWork) .Inc("TodaysAssigned", todaysAssigned); var result = await _taskCollection.UpdateOneAsync(filter, updates); if (result.ModifiedCount > 0) { return true; } return false; } public async Task DeleteWorkItemByIdFromCacheAsync(Guid workItemId) { var filter = Builders.Filter.Eq(e => e.Id, workItemId.ToString()); var result = await _taskCollection.DeleteOneAsync(filter); return result.DeletedCount > 0; } #endregion } }