Skip to content

MongoDb With .Net

The Nuget package is MongoDb.Driver.

App Settings:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"MongoDbConnection": {
"ConnectionString": "mongodb://admin:TokyoJoe138!@localhost:27017/",
"Database": "employees_db",
"Collection": "employees",
"LogCommands": true
}
}

MongoConnectionOptions.cs

namespace EmployeesApi;
public class MongoConnectionOptions
{
public static string SectionName = "MongoDbConnection";
public string ConnectionString
{
get; set;
} = "";
public string Database
{
get; set;
} = "";
public string Collection
{
get; set;
} = "";
public bool LogCommands
{
get; set;
}
}

In Program.cs

builder.Services.Configure<MongoConnectionOptions>(builder.Configuration.GetSection(MongoConnectionOptions.SectionName));
``

The Route Constraint

using MongoDB.Bson;
namespace EmployeesApi;
// "bsonid"
public class BsonIdConstaint : IRouteConstraint
{
public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if(values.TryGetValue(routeKey, out var routeValue))
{
var parameterValue = Convert.ToString(routeValue);
if(ObjectId.TryParse(parameterValue, out var _))
{
return true;
} else
{
return false;
}
} else
{
return false;
}
}
}

Add it in Program.cs:

builder.Services.AddRouting(options =>
{
options.ConstraintMap.Add("bsonid", typeof(BsonIdConstaint));
});

Domain Entity

Here is an example Domain entity:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace EmployeesApi.Domain;
public class Employee
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public ObjectId Id
{
get; set;
}
[BsonElement("department")]
public string Department
{
get; set;
} = "";
[BsonElement("name")]
public NameInformation Name
{
get; set;
} = new();
[BsonElement("salary")]
public decimal? Salary
{
get; set;
}
[BsonElement("removed")]
public bool? Removed
{
get; set;
} = false;
}
public class NameInformation
{
[BsonElement("firstName")]
public string FirstName
{
get; set;
} = "";
[BsonElement("lastName")]
public string LastName
{
get; set;
} = "";
}

Adapter

using EmployeesApi.Domain;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Core.Events;
namespace EmployeesApi.Adapters;
public class EmployeesMongoDbAdapter
{
private readonly IMongoCollection<Employee> _employeeCollection;
private readonly ILogger<EmployeesMongoDbAdapter> _logger;
public EmployeesMongoDbAdapter(ILogger<EmployeesMongoDbAdapter> logger, IOptions<MongoConnectionOptions> options)
{
_logger = logger;
var clientSettings = MongoClientSettings.FromConnectionString(options.Value.ConnectionString);
//clientSettings.LinqProvider = MongoDB.Driver.Linq.LinqProvider.V3;
if(options.Value.LogCommands)
{
clientSettings.ClusterConfigurator = db =>
{
db.Subscribe<CommandStartedEvent>(e =>
{
_logger.LogInformation($"Running {e.CommandName} - the command looks like this {e.Command.ToJson()}");
});
};
}
var conn = new MongoClient(clientSettings);
var db = conn.GetDatabase(options.Value.Database);
_employeeCollection = db.GetCollection<Employee>(options.Value.Collection);
}
public IMongoCollection<Employee> GetEmployeeCollection() => _employeeCollection;
}

The Domain Service

[!NOTE] I’ve found that using the Projection and Filter stuff is way better than the LINQ stuff. The LINQ (v2 at least) provider isn’t very intelligent.

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace EmployeesApi.Domain;
public class EmployeeLookup : ILookupEmployees, IManageEmployees
{
private readonly EmployeesMongoDbAdapter _adapter;
public EmployeeLookup(EmployeesMongoDbAdapter adapter)
{
_adapter = adapter;
}
public async Task<EmployeeDocumentResponse> CreateEmployeeAsync(EmployeeCreateRequest request)
{
var employeeToAdd = new Employee
{
Department = request.Department,
Name = new NameInformation { FirstName = request.FirstName, LastName = request.LastName },
Salary = request.StartingSalary
};
// This is a "side effect producing call"
await _adapter.GetEmployeeCollection().InsertOneAsync(employeeToAdd);
var response = new EmployeeDocumentResponse
{
Id = employeeToAdd.Id.ToString(),
Department = employeeToAdd.Department,
Name = new EmployeeNameInformation
{
FirstName = employeeToAdd.Name.FirstName,
LastName = employeeToAdd.Name.LastName
}
};
return response;
}
public async Task FireAsync(string id)
{
var bId = ObjectId.Parse(id);
var filter = Builders<Employee>.Filter.Where(e => e.Id == bId);
var update = Builders<Employee>.Update.Set(e => e.Removed, true);
await _adapter.GetEmployeeCollection().UpdateOneAsync(filter, update);
}
public async Task<List<EmployeeSummaryResponse>> GetAllEmployeeSummariesAsync()
{
var query = _adapter.GetEmployeeCollection().AsQueryable()
.Where(e => !e.Removed.HasValue || e.Removed.Value == false)
.OrderBy(e => e.Name.LastName)
.Select(e => new EmployeeSummaryResponse
{
Id = e.Id.ToString(),
Name = new EmployeeNameInformation { FirstName = e.Name.FirstName, LastName = e.Name.LastName }
});
// a thing we add here later.
var response = await query.ToListAsync();
return response;
}
public async Task<EmployeeDocumentResponse> GetEmployeeByIdAsync(string id)
{
// if we get here, that sucker is an ObjectID
var bId = ObjectId.Parse(id);
var projection = Builders<Employee>.Projection.Expression(emp => new EmployeeDocumentResponse
{
Id = emp.Id.ToString(),
Department = emp.Department,
Name = new EmployeeNameInformation
{
FirstName = emp.Name.FirstName,
LastName = emp.Name.LastName
}
});
var response = await _adapter.GetEmployeeCollection()
.Find(e => e.Id == bId && !e.Removed.HasValue || e.Removed!.Value == false)
.Project(projection).SingleOrDefaultAsync();
return response;
}
public async Task<bool> UpdateDepartmentAsync(string id, string department)
{
var bId = ObjectId.Parse(id);
var filter = Builders<Employee>.Filter.Where(e => e.Id == bId); // TODO: Only update employees that haven't been removed.
var update = Builders<Employee>.Update.Set(e => e.Department, department);
var changes = await _adapter.GetEmployeeCollection().UpdateOneAsync(filter, update);
//return changes.ModifiedCount == 1;
return changes.MatchedCount == 1;
}
public async Task<bool> UpdateNameAsync(string id, EmployeeNameInformation name)
{
var updatedName = new NameInformation { FirstName = name.FirstName, LastName = name.LastName };
var bId = ObjectId.Parse(id);
var filter = Builders<Employee>.Filter.Where(e => e.Id == bId); // TODO: Only update employees that haven't been removed.
var update = Builders<Employee>.Update.Set(e => e.Name, updatedName);
var changed = await _adapter.GetEmployeeCollection().UpdateOneAsync(filter, update);
return changed.MatchedCount == 1;
}
}