Channels ▼
RSS

Open Source

MongoDB with C#: Deep Dive


Now, add a new class, Player (that inherits from MongoEntity), within the Entities folder:

namespace RetrogamesWeb.Data.Entities
{
    using System.Collections.Generic;

    using MongoDB.Bson;
    using MongoDB.Bson.Serialization.Attributes;

    [BsonIgnoreExtraElements]
    public class Player : MongoEntity
    {
        public Player()
        {
            Scores = new List<Score>();
        }

        public string Name { get; set; }

        [BsonRepresentation(BsonType.String)]
        public Gender Gender { get; set; }

        public List<Score> Scores { get; set; }
    }
}

Notice that the only attribute specified for the properties is [BsonRepresentation(BsonType.String)]. This way, the Gender property of an enum type will be saved as a string (Male or Female).

Add a new class, MongoConnectionHandler:

namespace RetrogamesWeb.Data
{
    using Entities;
    using MongoDB.Driver;

    public class MongoConnectionHandler<T> where T : IMongoEntity
    {
        public MongoCollection<T> MongoCollection { get; private set; }

        public MongoConnectionHandler()
        {
            const string connectionString = "mongodb://localhost";

            //// Get a thread-safe client object by using a connection string
            var mongoClient = new MongoClient(connectionString);

            //// Get a reference to a server object from the Mongo client object
            var mongoServer = mongoClient.GetServer();

            //// Get a reference to the "retrogamesweb" database object 
            //// from the Mongo server object
            const string databaseName = "retrogamesweb";
            var db = mongoServer.GetDatabase(databaseName);

            //// Get a reference to the collection object from the Mongo database object
            //// The collection name is the type converted to lowercase + "s"
            MongoCollection = db.GetCollection<T>(typeof(T).Name.ToLower() + "s");
        }
    }
}

The MongoConnectionHandler<T> class defines constants for both the connection string and the MongoDB database. You can obviously save these strings in a configuration file and retrieve them from there. The constructor converts the type of T to lowercase and adds an "s" prefix to determine the collection name in a generic way and get the reference to the collection object.

Create the Services folder and add a new interface, IEntityService, within the new folder:

namespace RetrogamesWeb.Data.Services
{
    using Entities;

    public interface IEntityService<T> where T: IMongoEntity
    {
        void Create(T entity);

        void Delete(string id);

        T GetById(string id);

        void Update(T entity);
    }
}

Now, add the EntityService abstract class (that implements the previously created IEntityService interface), within the Services folder:

namespace RetrogamesWeb.Data.Services
{
    using MongoDB.Bson;
    using MongoDB.Driver;
    using MongoDB.Driver.Builders;
    using Entities;

    public abstract class EntityService<T> : IEntityService<T> where T : IMongoEntity
    {
        protected readonly MongoConnectionHandler<T> MongoConnectionHandler;

        public virtual void Create(T entity)
        {
            //// Save the entity with safe mode (WriteConcern.Acknowledged)
            var result = this.MongoConnectionHandler.MongoCollection.Save(
                entity, 
                new MongoInsertOptions
                {
                    WriteConcern = WriteConcern.Acknowledged
                });

            if (!result.Ok)
            {
                //// Something went wrong
            }
        }

        public virtual void Delete(string id)
        {
            var result = this.MongoConnectionHandler.MongoCollection.Remove(
                Query<T>.EQ(e => e.Id, 
                new ObjectId(id)), 
                RemoveFlags.None, 
                WriteConcern.Acknowledged);            

            if (!result.Ok)
            {
                //// Something went wrong
            }
        }

        protected EntityService()
        {
            MongoConnectionHandler = new MongoConnectionHandler<T>();
        }

        public virtual T GetById(string id)
        {
            var entityQuery = Query<T>.EQ(e => e.Id, new ObjectId(id));
            return this.MongoConnectionHandler.MongoCollection.FindOne(entityQuery);
        }

        public abstract void Update(T entity);
    }
}

The EntityService<T> class provides the following virtual methods to generalize the common operations with entities that all the services will provide:

  • Create: Saves the entity with a safe mode by specifying the WriteConcern.Acknowledged value for the WriteConcern property of the MongoInsertOptions instance, then passing the instance as one of the parameters for the Save method. When you specify this option, MongoDB will confirm the receipt of the save operation and it will be possible to catch exceptions related to data issues, such as a duplicate keys for unique indexes. If you don't specify the option and work with the WriteConcern.Unacknowledged value, the code will be able to catch only network-related errors because MongoDB won't confirm the receipt of the save operation. The same explanation applies to other write operations. In this case, the code includes an if block that checks the results of the operation, but doesn't take any action based on the result (to keep the code easier to read).
  • Delete: Removes the document from the MongoDB collection. The code also uses the with a WriteConcern.Acknowledged option for this operation.
  • GetById: Uses the FindOne method to retrieve a document from the MongoDB collection that matches the specified Id. The method receives the Id as a string because the idea is to have code that is prepared for a clear separation between the data layer and the ViewModels. ObjectId shouldn't be a known type for the ViewModels.( In this example, it will be a known type just to avoid adding all the layers and keep it simple). The GetById method generates some problems that are very common when working with embedded documents. I'll explain these problems later.
  • Update: The method must be coded in each implementation of the EntityService<T> class because a generic update would cause problems for entities with embedded documents. In this case, I want to show the solution to the problem through the use of specific commands that generate an efficient update operation.

Add the GameService class( that implements EntityService<Game>), within the Services folder:

namespace RetrogamesWeb.Data.Services
{
    using System.Collections.Generic;

    using Entities;
    using MongoDB.Driver.Builders;

    public class GameService: EntityService<Game>
    {
        public IEnumerable<Game> GetGamesDetails(int limit, int skip)
        {
            var gamesCursor = this.MongoConnectionHandler.MongoCollection.FindAllAs<Game>()
                .SetSortOrder(SortBy<Game>.Descending(g => g.ReleaseDate))
                .SetLimit(limit)
                .SetSkip(skip)
                .SetFields(Fields<Game>.Include(g => g.Id, g => g.Name, g => g.ReleaseDate));
            return gamesCursor;
        }


        public override void Update(Game entity)
        {
            //// Not necessary for the example
        }
    }
}

The GameService class doesn't require the Update method; therefore, it is empty. The class adds a GetGamesDetails method that calls the FindAllAs<Game> method to retrieve a limited number of documents from the MongoDB collection. The SetLimit and SetSkip methods limit the number of documents and specify the quantity to be skipped before returning the rest. Thus, both methods are very useful to paginate results. In this example, the Web application will just use this method to display the first 100 results, but you can easily implement a pager.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video