Chapter 2. Mongo DB CRUD and Mongo Shell – Part One

2. CRUD and Mongo Shell

MongoDBCRUD which stands for an operation of creating, read, update, and delete documents.

In Mongo DB, we called these operations in different names:

Create – Insert
Read – Find
Update – Update
Delete – Remove

All of these operations are function-based methods, which means that Mongo DB’s CRUD operations exist as methods/functions in programming language APIs, not as a separate language like T-SQL.

2.1 Create Operations

Create or insert operations add new documents to a collection. The MongoDB provides the following methods:

  • db.collection.insert()
  • db.collection.insertOne() new in version 3.2
  • db.collection.insertMany() new in version 3.2

In MongoDB, insert operations target a single collection. All write operations in MongoDB are atomic on the level of a single document

For more information on atomicity, transaction and concurrency control will be discussed in later chapters.

insert

In the new version 3.2, there are two functions are added – insertOne() and insertMany(). What’s the differences between three of these functions?

Behavior

If the collection does not exist, insert operations will create the collection.

Returns

Insert(): returns a WriteResult object, like WriteResult({“nInserted”: 1 }). The nInserted field specifies the number of documents added. If the operation encounters an error, the WriteResult object will contain the error information.

InsertOne(): returns a document with the status of the operation:

{
    "acknowledged": true,
    "insertedId": ObjectId("5742045ecacf0ba0c3fa82b0")
}

InsertMany(): returns a document with the status of the operation:

{
    "acknowledged" : true,
    "insertedIds" : [
        ObjectId("57420d48cacf0ba0c3fa82b1"),
        ObjectId("57420d48cacf0ba0c3fa82b2"),
        ObjectId("57420d48cacf0ba0c3fa82b3")
    ]
}

2.2 Query Documents

Query Method

  • db.collection.findOne()
  • db.collection.find() this method returns a cursor to the matching document.

db.collection.find(<query filter>, <projection>)

Return only one document

If we just want to get one document from your collection. db.collection.findOne() is the best choice.

findOne

Select All Document in a Collection

db.collection.find({}) or db.collection.find()

Specify Query Filter Conditions

Specify Equality Condition

<field> : <value> expressions are used to select all documents that contain the <field> with the specified <value>:

{<field1> : <value1>, <field2> : <value2>, …}

Specify Conditions Using Query Operators

A query filter document can use the query operators to specify conditions in the following form:

{ <field1>: { <operator1>: <value1> }, … }

$in : Either … Or … operator (comparison operators)

collection.find

OR Conditions

$or : a logical OR conjunction so that the query selects the documents in the collection that match at least one condition.

orCondition

AND Conditions

Implicitly, a logical AND combination connects the clauses of a compound query so that the query selects the documents in the collection that match all the conditions.

andCondition

Specify AND as well as OR Conditions

and_orCondition

Using regex and $exists

$exists

exists

$regex

regex

Query on Embedded Documents

When the field holds an embedded document, a query can either specify an exact match on the embedded document or specify a match by individual fields in the embedded document using the dot notation.

db.users.find( { favorites: { artist: &amp;quot;Picasso&amp;quot;, food: &amp;quot;pizza&amp;quot; } } )
db.users.find( { &amp;quot;favorites.artist&amp;quot;: &amp;quot;Picasso&amp;quot; } )

Query on Arrays

When the field holds an array, you can query for an exact array match or for specific values in the array. If the array holds embedded documents, you can query for specific fields in the embedded documents using dot notation.

If you specify multiple conditions using the $elemMatch operator, the array must contain at least one element that satisfies all the conditions.

Use the Task Parallel Library

Before we begin to discuss the details of Task and Task<T> class, we should understand an important concept about Parallel Programming and Asynchronous Programming.

Parallel Programming

Parallel processing (or parallel programming) uses multi-threading to maximize the use of multiple processors. This processing will split up the work among multiple threads, which can each run independently on a different core. Parallel processing is one type of multi-threading, and multi-threading is one type of concurrency. There’s another type of concurrency that is important in modern applications but is not (currently) familiar to many developers: asynchronous programming.

Asynchronous Programming

A form of concurrency that uses futures or callbacks to avoid unnecessary threads. A future (or promise) is a type that represents some operation that will complete in the future. The modern future types in .NET are Task and Task<TResult>. Older asynchronous APIs use callbacks or events instead of futures.

Task

Queuing a work item to a thread pool can be useful, but it has its shortcomings. There is no built-in way to know when the operation has finished and what the return value is. That’s why we need Task.  The Task can tell you if the work is completed and if the operation returns a result, the Task gives you the result. Meanwhile, it makes your application more responsive. A task scheduler is responsible for starting the Task and managing it. By default, the Task scheduler uses threads from the thread pool to execute the Task.

Example: Starting a new Task

using System;
using System.Threading.Tasks;
 
namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            var t = Task.Run(() =>
            {
                for (int i = 0; i > 100; i++)
                {
                    Console.WriteLine("*");
                }
            });
 
            t.Wait();
        }
    }
}

This example creates a new Task and immediately starts it. Calling Wait method is equivalent to calling Join method on a thread. We also can use the Task<T> class to return a value.

Example: Using a Task that returns a value

using System;
using System.Threading.Tasks;
 
namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            Task<int> t = Task.Run(() => 42);
 
            Console.WriteLine(t.Result);
        }
    }
}

Because of the object-oriented nature of the Task object, one thing you can do is add a continuation task. This means that you want another operation to execute as soon as the Task finishes.

Example: Adding a continuation

using System;
using System.Threading.Tasks;
 
namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            Task t = Task.Run(() => 42)
                .ContinueWith(i => i.Result*2);
 
            Console.WriteLine(t.Result); //Display 84
        }
    }
}

The ContinueWith method has a couple of overloads that we can use to configure when the continuation will run. This way we can add different continuation methods that will run when an exception happens, the Task is canceled, or the Task completes successfully.

Example: Scheduling different continuation tasks

using System;
using System.Threading.Tasks;

namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            Task t = Task.Run(() => 42);
            t.ContinueWith(i => { Console.WriteLine("Canceled"); }, TaskContinuationOptions.OnlyOnCanceled);

            t.ContinueWith(i => { Console.WriteLine("Failured"); }, TaskContinuationOptions.OnlyOnFaulted);

            var completeTask = t.ContinueWith((i) => { Console.WriteLine("Complete"); },
                TaskContinuationOptions.OnlyOnRanToCompletion);

            completeTask.Wait();
        }
    }
}

Next to continuation Tasks, a Task can also have several child Tasks.

Example: Attaching child tasks to a parent task

using System;
using System.Threading.Tasks;

namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            var parent = Task.Run(() =>
            {
                var results = new Int32[3];
                new Task(() =>
                    results[0] = 0,
                    TaskCreationOptions.AttachedToParent).
                    Start();
                new Task(() =>
                    results[1] = 1,
                    TaskCreationOptions.AttachedToParent).
                    Start();
                new Task(() =>
                    results[2] = 2,
                    TaskCreationOptions.AttachedToParent).
                    Start();
                return results;
            });
            var finalTask =
                parent.ContinueWith(parentTask => { foreach (var i in parentTask.Result) Console.WriteLine(i); });

            finalTask.Wait();
        }
    }
}

The finalTask runs only after the parent Task is finished, and the parent Task finishes when all three children are finished. We can use this to create quite complex Task hierarchies that will go through all the steps we specified. To make the process easier, we can use a TaskFactory. A TaskFactory is created with a certain configuration and can be used to create Tasks with that configuration.

Example: Using a TaskFactory

using System;
using System.Threading.Tasks;
 
namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            Task<Int32[]> parentTask = Task.Run(() =>
            {
                var result = new Int32[3];
 
                var taskFactory = new TaskFactory(TaskCreationOptions.AttachedToParent,
                    TaskContinuationOptions.ExecuteSynchronously);
                taskFactory.StartNew(() => result[0] = 0);
                taskFactory.StartNew(() => result[1] = 1);
                taskFactory.StartNew(() => result[2] = 2);
                return result;
            });
 
            var finalTask = parentTask.ContinueWith(parent =>
            {
                foreach (var i in parent.Result)
                {
                    Console.WriteLine(i);
                }
            });
 
            finalTask.Wait();
        }
    }
}

Next to calling Wait on a single Task, we can also use the method WaitAll to wait for multiple Tasks to finish before continuing execution. The next example will use an array to store all Tasks.

Example: Using Task.WaitAll

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            var tasks = new Task[3];

            tasks[0] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("1");
                return 1;
            });

            tasks[1] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("2");
                return 2;
            });

            tasks[2] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("3");
                return 3;
            });

            Task.WaitAll(tasks);
        }
    }
}

In this case, all three Tasks are executed simultaneously, and the whole run takes approximately 1000ms instead of 3000. Next to WaitAll, you also have a WhenAll method that you can use to schedule a continuation method after all Tasks have finished. Instead of waiting until all tasks are finished, you can also wait until one of the tasks is finished. You use the WaitAny method for this.

Example: Using Task.WaitAny

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
 
namespace Example
{
    public class Program
    {
        private static void Main(string[] args)
        {
            var tasks = new Task[3];
 
            tasks[0] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                return 1;
            });
 
            tasks[1] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                return 2;
            });
            
            tasks[2] = Task.Run(() =>
            {
                Thread.Sleep(1000);
                return 3;
            });
 
            while (tasks.Length &gt; 0)
            {
                var i = Task.WaitAny(tasks);
                Task&lt;int&gt; completeTask = (Task&lt;int&gt;) tasks[i];
 
                Console.WriteLine(completeTask.Result);
 
                var temp = tasks.ToList();
                temp.RemoveAt(i);
                tasks = temp.ToArray();
 
                Console.ReadKey();
            }
        }
    }
}