Hello guys, this is my first post, so I'm a little nervous.
I would like to talk about making a framework.
First things first. In this post I would like to make a code to database interaction framework using MongoDB.
Using MongoDB with C#, we already have a good framework: mongodriver.
Let's make a abstraction to some operations of mongodriver, so we can code de business faster in other layers of code.
Let's start with the Configuration.
Using mongodb, we used to link the database with a "MongoUrl" class, wich we can build dynamically using a Configuration class to abstract.
Let's code (using C#) a simple configuration class to our framework of MongoDB:
public class Configuration
{
public MongoUrl mongoUrl { get; set; }
public string dbName { get; set; }
}
Let's remember the format of a simple mongodb Url to localhost:
MongoUrl mongoUrl = new MongoUrl("mongodb://localhost:27017/CONTROLE_OD_DB")
In this simple configuration example, we will not care to build dynamically or abstract the MongoUrl, this is not the point of the post.
Now we will get in the real point of this post, the art of write a generic framework, even a small one.
Let's make a class with the generic operations.
Again, first things first. Let's make the configuration.
I will name it class Context.
So:
public class Context
{
#region Configuration
public static Configuration _configuration { get; set; }
static MongoClient client;
static IMongoDatabase db;
public static void Initializer(Configuration configuration)
{
_configuration = new Configuration();
_configuration = configuration;
client = new MongoClient(_configuration.mongoUrl);
db = client.GetDatabase(_configuration.dbName);
}
public static void Dispose()
{
client = null;
}
#endregion
}
Let's understand the post.
We want to make our life easier making a framework.
First thing is to identify the boring parts of writing the code using just the old framework, mongodriver. The first boring part is to deal with the configuration. We had to get the database, make the mongoUrl every time, so we made here a "Context" class to save the configuration in a static variable, so it will not need a new instance all the time.
We will only get the database one time and get the IMongoDatabase one time. Better than that is the fact that we will not worry about them anymore.
So let's start to build a basic "collection" operations region.
#region Collections
public static void CreateCollection(string collectionName)
{
db.CreateCollection(collectionName);
}
public static IMongoCollection<T> GetCollection<T>(string collectionName)
{
return db.GetCollection<T>(collectionName);
}
public static IMongoCollection<T> GetCollection<T>()
{
return GetCollection<T>(typeof(T).Name + "_COLLECTION");
}
#endregion
We can use generic types to get whatever collection we want and set the collection using the type of the "T" class of our method. So, let's remove the problem of set the string name of a collection, and make the framework set the name of the collection using the class name. Just for organization we can write a "_COLLECTION" at the end, so it will be better to read.
Now we have this class.
public class Context
{
#region Configuration
public static Configuration _configuration { get; set; }
static MongoClient client;
static IMongoDatabase db;
public static void Initializer(Configuration configuration)
{
_configuration = new Configuration();
_configuration = configuration;
client = new MongoClient(_configuration.mongoUrl);
db = client.GetDatabase(_configuration.dbName);
}
public static void Dispose()
{
client = null;
}
#endregion
#region Collections
public static void CreateCollection(string collectionName)
{
db.CreateCollection(collectionName);
}
public static IMongoCollection<T> GetCollection<T>(string collectionName)
{
return db.GetCollection<T>(collectionName);
}
public static IMongoCollection<T> GetCollection<T>()
{
return GetCollection<T>(typeof(T).Name + "_COLLECTION");
}
#endregion
}
Here we have the database creation, and the collections creation, just what we need to start to write a operations region, CRUD (Create, Read, Update, Delete) region.
Let's make the insert:
#region CRUD
public static T Insert<T>(T document)
{
var collection = GetCollection<T>(typeof(T).Name + "_COLLECTION");
collection.InsertOne(document);
return document;
}
public static List<T> Insert<T>(List<T> documents)
{
GetCollection<T>(typeof(T).Name + "_COLLECTION").InsertManyAsync(documents);
return documents;
}
public static List<T> Insert<T>(string collectioName, List<T> documents)
{
GetCollection<T>(collectioName).InsertManyAsync(documents);
return documents;
}
public static T Insert<T>(string collectioName, T document)
{
GetCollection<T>(collectioName).InsertOne(document);
return document;
}
public static List<BsonDocument> Insert(string collectioName, List<BsonDocument> documents)
{
GetCollection<BsonDocument>(collectioName).InsertManyAsync(documents);
return documents;
}
public static BsonDocument Insert(string collectioName, BsonDocument document)
{
GetCollection<BsonDocument>(collectioName).InsertOneAsync(document);
return document;
}
In mongodriver we have always to choose between insert many, or insert one, insert one async, insert bson, insert object, I don't want to worry about that anymore, let the framework choose each one to use.
For all the inserts methods (and all the operations we will make of the CRUD sequence) we will use the database already declared and the getcollection method.
We are choosing each things not to worry, and making generic methods to abstract them.
Going on...
Let's make a the R (Read) of the "CRUD":
public static List<T> GetDocuments<T>(FilterDefinition<T> filter)
{
return GetCollection<T>().FindAsync(filter).Result.ToList<T>();
}
public static List<T> GetDocuments<T>(string collectionName, FilterDefinition<T> filter)
{
return GetCollection<T>(collectionName).FindAsync(filter).Result.ToList<T>();
}
public static T GetDocument<T>(FilterDefinition<T> filter)
{
return GetCollection<T>().FindAsync(filter).Result.SingleOrDefault<T>();
}
public static T GetDocument<T>(string collectionName, FilterDefinition<T> filter)
{
return GetCollection<T>(collectionName).FindAsync(filter).Result.SingleOrDefault<T>();
}
A good thing to do is get everything returning in the type of object I want to use, I don't want to convert c# collections into lists for example, I want every return of each method right how I want to use them, in this example, it would be a List or a simple T.
Let`s make the D (Delete) and the U (Update) :
public static void Update<T>(MongoDB.Driver.FilterDefinition<T> filter, KeyValuePair<string, object> property, bool updateOne, bool IsUpsert)
{
var update = Builders<T>.Update.Set(property.Key, property.Value);
if (updateOne)
GetCollection<T>()
.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = IsUpsert });
else
GetCollection<T>()
.UpdateMany(filter, update, new UpdateOptions { IsUpsert = IsUpsert });
}
public static void Update<T>(MongoDB.Driver.FilterDefinition<T> filter, Dictionary<string, object> properties, bool updateOne, bool IsUpsert)
{
foreach(var property in properties)
Update<T>(filter, property, false, true);
}
public static void Update<T>(MongoDB.Driver.FilterDefinition<T> filter, Dictionary<string, object> properties)
{
foreach(var property in properties)
Update<T>(filter, property, false, true);
}
public static void Update<T>(MongoDB.Driver.FilterDefinition<T> filter, Dictionary<string, object> properties, bool updateOne)
{
foreach(var property in properties)
Update<T>(filter, property, false, true);
}
public static void Update<T>(MongoDB.Driver.FilterDefinition<T> filter, KeyValuePair<string, object> property)
{
Update<T>(filter, property, false, true);
}
public static void Update<T>(MongoDB.Driver.FilterDefinition<T> filter, KeyValuePair<string, object> property, bool updateOne)
{
Update<T>(filter, property, false, true);
}
public static void Delete<T>(FilterDefinition<T> filter, bool DeleteOne)
{
if (DeleteOne)
GetCollection<T>().DeleteOne(filter);
else
GetCollection<T>().DeleteMany(filter);
}
public static void Delete<T>(FilterDefinition<T> filter)
{
Delete<T>(filter, false);
}
So we can go on to the beautiful part of the post.
Let's make more new methods, to get everything even easier.
Everything by now was changing the Context class, or making a basic Configuration class. Now let's create a Base class.
I will create a new class named "Base".
public class Base<T>
{
public T Insert(T T)=>Context.Insert<T>(T);
public void Upsert(FilterDefinition<T> filter, T entity)
{
if(List(filter).Count > 0)
{
Dictionary<string, object> props = new Dictionary<string, object>();
foreach (var responsibleProp in entity.GetType().GetProperties()) props.Add(responsibleProp.Name, responsibleProp.GetValue(entity));
Context.Update<T>(filter, props);
}
else Context.Insert<T>(entity);
}
public T Find(FilterDefinition<T> filter)=>Context.GetDocument<T>(filter);
public List<T> List(FilterDefinition<T> filter)=>Context.GetDocuments<T>(filter);
public void Delete(FilterDefinition<T> filter)=>Context.Delete<T>(filter);
}
One of the things I don't want to worry is if we are making a Insert or a Update, so we can create a Upsert method. If the filter get's any result in the query, it will update these results with the entity, else it will insert a new entity in the database.
"Find" with a simple filter, and "List" returning a "List"(List of generic objects), and then the update.
Now we can configure the framework into a new software: let's create a ConfigDB class :
public class ConfigDB<T> : Base<T>
{
public ConfigDB()
{
Configuration configuration = new Configuration();
Connection conn = new Connection();
configuration.dbName = conn.GetDatabaseName();
configuration.mongoUrl = new MongoUrl(conn.GetUrl());
Context.Initializer(configuration);
}
~ConfigDB()
{
Context.Dispose();
}
}
And let's make a simple agent class, just to use the base operations :
public class Agent<T> : ConfigDB<T>
{
}
Now we can create our business class, I will name it UserBusiness :
public class User
{
public string login {get;set;}
public string password {get;set;}
public string curriculum {get;set;}
public string aboutMe {get;set;}
[BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
public string ID { get; set; }
public bool deleted { get; set; } = false;
public bool active { get; set; } = true;
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime creationDate { get; set; } = DateTime.Now;
}
public class UserBusiness
{
public User InsertUser(User user) => new Agent<User>().Insert(user);
public User findUserByID(string ID)=> new Agent<User>().Find(Builders<User>.Filter.Where(u => u.ID == ID));
public User findUserByLogin(string login)=> new Agent<User>().Find(Builders<User>.Filter.Where(u => u.active == true && u.login == login));
}
This was just a small example of how to think when building a framework.
We need to worry, now, about what we don't want to worry about in the future.
This was my message, hope you like it.
Top comments (0)