CRUD with MongoDB version 2

Hello everybody,

today I want to describe CRUD options in MongoDB version 2.

So, let's start with Reading. 

In MongoDB reading can be considered as following options:

  1. Finding all documnets
  2. Filtering
  3. Sorting
  4. Definitions and builders.

Creating documents in MongoDB is simple. You can do it just with Insert ( letter C ). 

And here you have plenty of options:

  1. Replace
  2. Update
  3. Upsert

I'll also mention deleting, or letter D.

And also I want to describe how to use projections as way to limit what you take from MongoDB

So, let's start with synchronous finding of some information in MongoDB. 

Suppose I have following classes declarations:

public abstract class MongoEntityBase
    {
        // ReSharper disable once InconsistentNaming
        public ObjectId _id { getset; }
    }
 
    public class SampleClarification : MongoEntityBase
    {
        public ObjectId SomeId { getset; }
        public int RectificationNumber { getset; }
        public string RectificationReason { getset; }
        public string RectificationText { getset; }
    }

This class will give me option of working with  SampleClarification collection in my MongoDB database.

Also please look at Default construction of database context for MongoDB:

public class SampleDataContext
    {
        public IMongoDatabase Database;
 
        public SampleDataContext()
        {
            var client = new MongoClient("http://localhost:27017/sample");
            Database = client.GetDatabase("sample");
        }
    }

In order to get this collection synchronously, I will need to add interface collection to datacontext. Data context will be transformed to this: 

public class SampleDataContext
    {
        public IMongoDatabase Database;
        public SampleDataContext()
        {
            var client = new MongoClient("http://localhost:27017/sample");
            Database = client.GetDatabase("sample");
        }
        public IMongoCollection<SampleClarification> SampleClarifications => Database.GetCollection<SampleClarification>("sample");
    }

The most important part here is SampleClarifications

And then you can search for all SampleClarifications in the following way:

SampleDataContext sc = new SampleDataContext();
var allDocs = sc.SampleClarifications.Find(new BsonDocument()).ToList();

then   allDocs will be list in memory of all documents.

So, that was syncrhonous call. 

Now I want to demonstrate asyncrhonous call ( that will be as easy as cake ):

SampleDataContext sc = new SampleDataContext();
var allDocs = await sc.SampleClarifications.Find(new BsonDocument()).ToListAsync();

But let's say you want to iterate through returned documents, and do something with them. Then very handy can be function ForEachAsync. And I recommend to use it with cursor. 

The next is going filtering. One of the ways of filtering can be the following:

SampleDataContext sc = new SampleDataContext();
            var allDocs = await sc.SampleClarifications.Find(new BsonDocument()
                    {
                        { "RectificationNumber" , 4}
                }
                ).ToListAsync();

Do you have an impression, that pointing string is not great way to make filtering? If yes, you are not alone. And MongoDB driver has some new features for filtering:

SampleDataContext sc = new SampleDataContext();
           
var allDocs = await sc.SampleClarifications.
             Find(Builders<SampleClarification>.Filter.Gte(a => a.RectificationNumber, 7)).ToListAsync();

One of the ways to read filters creation can be the following: build me the filter on a SampleClarification type  which returns SampleClarificaiton instances where RectificationNumber is  Greater or equal 7.

Other posibilities can be Lte - less equal, Eq - equal to, etc. 

Another useful feature is using Where. For example like this:

SampleDataContext sc = new SampleDataContext();
            
var allDocs = await sc.SampleClarifications.
        Find(Builders<SampleClarification>.Filter.Where(a => a.RectificationNumber == 7)).ToListAsync();

IMHO where is intended for cases if you don't want to remember Lte, Eq, Gte, Ne, etc. But Where has one important feature. You can pass there nullable value.

Another interesting feature of MongoDB is combining filtering features. See the following code:

SampleDataContext sc = new SampleDataContext();
 
var filter = Builders<SampleClarification>.Filter.Empty;
 
if (true /*some condition*/)
{
     filter = filter & Builders<SampleClarification>.Filter.Where(a => a.RectificationText.Contains("aaa"));
}
 
var allDocs = await sc.SampleClarifications.
        Find(filter).ToListAsync();

Pretty interesting, huh??? I was surprised when seeen & instead of &&.

But that's not all. Let me introduce you another shortcut: &=. Like this:

if (true /* another condition */)
{
      filter &= Builders<SampleClarification>.Filter.Gte(a => a.RectificationNumber, 20);
}

Sorting

For sorting you can work again with Builder. It can be achieved like this:

SampleDataContext sc = new SampleDataContext();
 
var filter = Builders<SampleClarification>.Filter.Empty;
var sorting = Builders<SampleClarification>.Sort.Ascending(t => t.RectificationNumber);
var allDocs = await sc.SampleClarifications.Find(filter).Sort(sorting).ToListAsync();

If you feel bored to do a lot of typing in order to Sort something, you can see to SortBy function:

SampleDataContext sc = new SampleDataContext();
var filter = Builders<SampleClarification>.Filter.Empty;
var allDocs = await sc.SampleClarifications.Find(filter).SortBy(s => s.RectificationNumber).ToListAsync();

Or you can use even more complicated schema:

SampleDataContext sc = new SampleDataContext();
var filter = Builders<SampleClarification>.Filter.Empty;
var allDocs = await sc.SampleClarifications.Find(filter).
    SortBy(s => s.RectificationNumber)
    .ThenByDescending(a => a.TenderId)
    .ThenBy(a => a.RectificationReason).ToListAsync();

Inserting or C from CRUD

You can insert either one element, or more. Here is example how to create one:

SampleDataContext sc = new SampleDataContext();
var filter = Builders<SampleClarification>.Filter.Empty;
var clar = new SampleClarification();
clar.RectificationNumber = 10;
clar.RectificationReason = "some reason";
clar.RectificationText = "fasdfdas fdsfas";
await sc.SampleClarifications.InsertOneAsync(clar);

Replacing or almost U from CRUD

First of all what is replacing. Replace is when you find document, delete it, and instead of it put new one, with modified values.

First of all you will need to find element, then update it's value, and then save it. Here is example:

SampleDataContext sc = new SampleDataContext();
//var filter = Builders<SampleClarification>.Filter.Where(r => r.TenderId == ObjectId.Parse("56d55f1d4ee6bc45a0d7b539"));
//var clar = sc.SampleClarifications.Find(filter).FirstOrDefault();
 
//or another way:
var clar =  sc.SampleClarifications.Find(a => a.TenderId == ObjectId.Parse("56d55f1d4ee6bc45a0d7b539")).FirstOrDefault();
 
if (clar != null)
{
    sc.SampleClarifications.ReplaceOne(r => r._id == clar._id, clar);
}

Replace one, will take as paramether search criteria, and will replace passed element.

U from CRUD or Update

Well, for making update you'll need Builder with update policy. See the example of code:

SampleDataContext sc = new SampleDataContext();
 
var text = "fasfds afasfas";
var clar =  sc.SampleClarifications.Find(a => a.TenderId == ObjectId.Parse("56d55f1d4ee6bc45a0d7b539")).FirstOrDefault();
var modificationsUpdate = Builders<SampleClarification>.Update
    .Set(a => a.RectificationReason, text)
    .Set(v => v.RectificationNumber, 35);
 
if (clar != null)
{
    sc.SampleClarifications.UpdateOne(c => c._id == clar._id, modificationsUpdate);
}

Also you can make it async if you wish.

Another U from CRUD or Upsert

Upsert means the following. If upsert option is set to true, then if not matching document exists, then it will be inserted. If document exists, then it will be replaced. 

Code which can be helpful:

SampleDataContext sc = new SampleDataContext();
 
var text = "fasfds afasfas";
var clar =  sc.SampleClarifications.Find(a => a.TenderId == ObjectId.Parse("56d55f1d4ee6bc45a0d7b539")).FirstOrDefault();
UpdateOptions options = new UpdateOptions
{
    IsUpsert = true
};
 
if (clar != null)
{
    sc.SampleClarifications.ReplaceOne(c => c._id == clar._id, clar, options);
}

Finally Delete or D

Finally you can delete any document. It's relatively easy:

SampleDataContext sc = new SampleDataContext();
sc.SampleClarifications.DeleteOne(a => a.RectificationNumber == 10);

Projections

Let's say that you want to return not full list of SampleClarification fields, but subset of them. For this purpose you can use projections. 

Imagine, that you don't want to extract all fields from SampleClarification, and for this purpose you created the following view class:

public class SampleClarificationView : MongoEntityBase
    {
        public ObjectId TenderId { getset; }
        public int RectificationNumber { getset; }
    }

Then you can use projections:

SampleDataContext sc = new SampleDataContext();
var filter = Builders<SampleClarification>.Filter.Empty;
var middle = sc.SampleClarifications.Find(filter).Project(r => new SampleClarificationView()
{
    _id =  r._id,
    TenderId = r.TenderId,
    RectificationNumber = r.RectificationNumber
});
var result = await middle.ToListAsync();

Result will contain not all fiedls.

No Comments

Add a Comment