In this article, I will share some more information about working with version 2 of MongoDB driver. We will cover reading, sorting, inserting, replacing, updating, upserting and deleting. We will also take a look at Projections.
Introduction
There are plenty of manuals over the internet of how to work with MongoDB driver version 1, but not so much with version 2. I want to add a little bit more information about working with version 2.
So, Let's Start With Reading
In MongoDB, reading can be considered as following options:
- Finding all documents
- Filtering
- Sorting
- 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:
Replace
Update
Upsert
I'll also mention deleting, or letter D.
And also, I want to describe how to use projections as a way to limit what you take from MongoDB.
So, let's start with synchronous finding of some information in MongoDB.
Suppose I have the following classes declarations:
public abstract class MongoEntityBase
{
// ReSharper disable once InconsistentNaming
public ObjectId _id { get; set; }
}
public class SampleClarification : MongoEntityBase
{
public ObjectId SomeId { get; set; }
public int RectificationNumber { get; set; }
public string RectificationReason { get; set; }
public string RectificationText { get; set; }
}
This class will give me the 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 listed in memory of all documents.
So, that was a synchronous call.
Now I want to demonstrate an asynchronous 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 a 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 SampleClarification
instances where RectificationNumber
is greater or equal to 7.
Other possibilities 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 nullable value there.
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 )
{
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 I saw &
instead of &&
.
But that's not all. Let me introduce you to another shortcut: &=
. Like this:
if (true )
{
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 an example of 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 a new one, with modified values.
First of all, you will need to find element, then update
its value, and then save it. Here is an example:
SampleDataContext sc = new SampleDataContext();
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 parameter 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 { get; set; }
public int RectificationNumber { get; set; }
}
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 fields.
Points of Interest
In this article, I wrote my observation of CRUD operations for MongoDB along with filtering. While writing this article, I was most surprised with Projects of MongoDB which take only some fields, and not all of them.
History
- 3rd March, 2021: Initial post