Repository pattern implementation with distributed collections of MongoDB in .NET Framework
This solution provides a simple and easy way to create distributed collections based on specified properties in a model. You may also use nested levels of collections while keeping a backlink in the source collection.
A master collection, created by default, acts as an reference index to all the collections stored. Master collection maintains the creation date and update date of each record, you may also name a collection if needed.
-
Default
ConnectionStringSettingName
is set to "MongoDocConnection", but this can be configured by setting the property:Settings.ConnectionStringSettingName = "MongoDocConnection";
-
Default
ExpiryAfterSeconds
index is set to "15778463" (approx: 6 months), but this can be configured by setting property:Settings.ExpireAfterSeconds = 2592000;
-
Default
CollectionName
is set to "master", but this can be configured by calling the following static method:MasterSettings.CollectionName = "master";
-
Additional properties may be added to the master collection by using the following property:
MasterSettings.AdditionalProperties = new string[] { "CreatedBy", "UpdatedBy" };
-
Additional properties may be set on
BeforeInsert
&AfterInsert
triggers by calling theMasterSettings.SetProperties()
method anywhere in the application, for example:MasterSettings.SetProperties(new Dictionary<string, object>() { { "CreatedBy", Guid.Parse("FC09E7EE-5E78-E811-80C7-000C29DADC00") } }, MasterSettings.Triggers.BeforeInsert); MasterSettings.SetProperties(new Dictionary<string, object>() { { "UpdatedBy", Guid.Parse("6B9F4B43-5F78-E811-80C7-000C29DADC00") } }, MasterSettings.Triggers.AfterInsert); Message message = new Message() { Text = $"Test message # {DateTime.UtcNow.ToShortTimeString()}", Client = 3, Caterer = 4 }; Message result = await MessageRepository.InsertAsync(message);
-
public class MessageUnitTest { public MessageUnitTest() { Settings.ConnectionStringSettingName = "MongoDocConnection"; MasterSettings.AdditionalProperties = new string[] { "CreatedBy", "UpdatedBy" }; } //.... other methods and properties }
The solution has a predefined attribute Distribute
with an optional value of Level
, adding the attribute triggers the collection distribution based on the property.
Providing Level
makes logical nesting of collections by keeping backlinks, for example in the model below the first entry with Caterer, Client and Order defined will be added to source collection and subsequent entries added to a new collection created. In this way the source collection will always have an entry to corelate with the nested collection.
Creating a document model inheriting BaseEntity
to use in repository.
public class Message : BaseEntity
{
[BsonRequired]
public string Text { get; set; }
[Distribute]
public long Caterer { get; set; }
[Distribute]
public long Client { get; set; }
[Distribute(Level = 1)]
public long? Order { get; set; }
}
Collection Ids can be fetched using Master Repository and needs to be defined for each method in repository, hence, each call may run on a different collection altogether.
-
Find asynchronous (using LINQ Expression)
Message result = await MessageRepository.FindAsync(CollectionId, x => x.Id == new ObjectId("5e36997898d2c15a400f8968"));
-
Get asynchronous (using LINQ Expression)
- Has paged records in a
Tuple<IEnumerable<T>, long>
of records and total count.
var result = await MessageRepository.GetAsync(CollectionId, 1, 10, x => x.Text.Contains("abc") && x.Deleted == false);
- Has paged records in a
-
Get asynchronous (using Filter Definition)
- Has paged records in a
Tuple<IEnumerable<T>, long>
of records and total count.
MessageSearchParameters searchParameters = new MessageSearchParameters() { Text = "Change", Caterer = null, Client = null, Order = null }; var builder = Builders<Message>.Filter; var filter = builder.Empty; if (!string.IsNullOrWhiteSpace(searchParameters.Text)) { var criteriaFilter = builder.Regex(x => x.Text, new BsonRegularExpression($".*{searchParameters.Text}.*")); filter &= criteriaFilter; } if (searchParameters.Caterer.HasValue) { var criteriaFilter = builder.Eq(x => x.Caterer, searchParameters.Caterer.Value); filter &= criteriaFilter; } if (searchParameters.Client.HasValue) { var criteriaFilter = builder.Eq(x => x.Client, searchParameters.Client.Value); filter &= criteriaFilter; } if (searchParameters.Order.HasValue) { var criteriaFilter = builder.Eq(x => x.Order, searchParameters.Order.Value); filter &= criteriaFilter; } var result = await MessageRepository.GetAsync(CollectionId, 1, 10, filter);
- Has paged records in a
-
Insert asynchronous
Message message = new Message() { Text = "xyz", Client = 2, Caterer = 2 }; Message result = await MessageRepository.InsertAsync(message);
-
Update asynchronous
Message message = new Message() { Id = new ObjectId("5e36998998d2c1540ca23894"), Text = "Changed" }; bool result = await MessageRepository.UpdateAsync(CollectionId, message);
-
Bulk Update asynchronous
long updatedCount = 0; //Search messages which are not read by the current user. FilterDefinition<Message> filter = "{ Read : { $not: { $elemMatch : { k : '" + CurrentUser.Id.ToString() + "' } } } }"; IEnumerable<Message> messages = await MessageRepository.GetAsync(channelId, filter); IEnumerable<Message> emptyReads = await MessageRepository.GetAsync(channelId, x => x.Read == new Dictionary<string, DateTime>()); messages = messages.Union(emptyReads); if (messages != null && messages.Count() != 0) { //Bulk update current user to read list messages.ToList().ForEach(x => x.Read.Add(CurrentUser.Id.ToString(), DateTime.UtcNow)); //Bulk write updatedCount = await MessageRepository.BulkUpdateAsync(channelId, messages); }
-
Delete asynchronous (by Id)
bool result = await MessageRepository.DeleteAsync(CollectionId, new ObjectId("5e36998998d2c1540ca23894"));
-
Count asynchronous
long result = await MessageRepository.CountAsync(CollectionId);
-
Exists asynchronous (using LINQ Expression)
bool result = await MessageRepository.ExistsAsync(CollectionId, x => x.Text == "abc");
-
Find asynchronous
var builder = Builders<BsonDocument>.Filter; var collectionFilter = builder.Eq("CollectionId", "c7a7935f1ebd440e9b85003c1b81b3c3"); var result = await MasterRepository.FindAsync(collectionFilter); var record = BsonSerializer.Deserialize<MasterGetViewModel>(result.ToJson());
-
Get asynchronous
- Has paged records in a
Tuple<IEnumerable<BsonDocument>, long>
of records and total count.
var result = await MasterRepository.GetAsync(1, 20); var records = BsonSerializer.Deserialize<IEnumerable<MasterGetViewModel>>(result.Item1.ToJson());
- Has paged records in a
-
Get asynchronous (using dictionary of keys and values)
Dictionary<string, object> keyValuePairs = new Dictionary<string, object> { { "Client", 1 } }; var result = await MasterRepository.GetAsync(keyValuePairs); var records = BsonSerializer.Deserialize<IEnumerable<MasterGetViewModel>>(result.Item1.ToJson());
-
Get asynchronous (using Filter Definition)
var builder = Builders<BsonDocument>.Filter; var clientFilter = builder.Eq("Client", 1); var catererFilter = builder.Eq("Caterer", 1); var filter = clientFilter & catererFilter; var result = await MasterRepository.GetAsync(1, 20, filter); var records = BsonSerializer.Deserialize<IEnumerable<MasterGetViewModel>>(result.Item1.ToJson());
-
Update asynchronous
bool result = await MasterRepository.UpdateAsync("53df73c45d7e493b86746066a693534c", "Untitled-3");
Refer to TK.MongoDB.Test project for Unit Tests for:
- Repository
- Master Repository