Click here to Skip to main content
Click here to Skip to main content

A generic .NET NoSQL abstraction layer using an Entity-Framework like Repository pattern

, 29 Jul 2013
Rate this:
Please Sign up or sign in to vote.
This article introduces you to an open source solution for decoupling your data management and query logic from the NoSQL vendor-specific integration logic.

Introduction

The article presents an open source .NET abstraction layer above NoSQL databases, providing and Entity-Framework-like Repository Pattern above the various database.

Background

While NoSQL is becoming more and more common, an abstraction above the specific database's API, allow separation between the vendor-specific logic and the database access and management logic is amiss.

Using the code

You can download the entire solution including source code and tests from the CodePlex project site or consume the binaries using NuGet (see links in the CodePlex project site).

The API

Basic usage

For those of you familiar with ORMs like Entity Framework, code like this will look familiar:

using (var context = new MyContext(ParametersForTests.ConnectionInfo))
{
    var aaaa = new MyEntity
    {
        Id = Guid.NewGuid(),
        Name = "aaaa",
    };

    var bbbb = new MyEntity
    {
        Id = Guid.NewGuid(),
        Name = "bbbb",
    };

    context.MyEntities.Add(aaaa);
    context.MyEntities.Add(bbbb);
}

And queries such of this won't know you off your feet either:

using (var context = new MyContext(ParametersForTests.ConnectionInfo))
{
    var aaaa = context.MyEntities.AsQueryable().Where(ent => ent.Name == "aaaa")
        .FirstOrDefault();
    var bbbb = context.MyEntities.AsQueryable().Where(ent => ent.Name == "bbbb")
        .FirstOrDefault();
}

Defining the context

Defining an interface for a context such as this may not be what you are used to, but should seem reasonable to you:

public interface IMyContext : IDomainContext
{
    IEntitySet<Guid, MyEntity> MyEntities { get; }
    IEntitySet<String, UserLogin> UserLogins { get; }
    new IFileSet<Guid> Files { get; }
}

The definition of the actual context may seem a bit lighter than you are used to:

public class MyContext: MongoDbContext, IMyContext
{
    public MyContext(MongoDbConnectionInfo connectionInfo)
        : base(connectionInfo)
    {
    }

    public IEntitySet<Guid, MyEntity>MyEntities
    {
        get;
        private set;
    }

    public IEntitySet<String, UserLogin> UserLogins
    {
        get;
        private set;
    }

    public new IFileSet<Guid> Files
    {
        get;
        private set;
    }
}

And the fact that you do not have to write code or XML or even select tables and columns in a diagram in order to explain your ORM how these entities are mapped to tables might relieve you. Since NoSQL serializes your objects, as is, into the database, requiring no schema, the model is all you really need to modify.

Since collections can be stored under their parent entities, as either a list of objects or a list of IDs, there is no need for an equivalent for many to many tables, therefore, for common usage, a single field is enough for an ID property. ID types that have been tested for basic support include Guid, String and int.

What about indexes?

You can always define indexes within your context class, as so:

private static IndexDefinition MyEntityByName
{
    get
    {
        return new IndexDefinition(
            typeof(MyEntity),
            new[] { new KeyProperty("Name", Direction.Ascending) },
            asUnique: false,
            asSparse: true);
    }
}

The reason this are defined on the context and not on the entities, is because the context can hold any inheriting entity types and the indexes may be on properties that do not exist on the base type and because I did not want to define indexes on the base type, that refer to property that may only exist on inheriting types.

Swapping between NoSQL vendors

The only change you need to make to move from MongoDB to Redis and back is to change the base class and connection info types of your context like so:

public class MyContext: MongoDbContext, IMyContext
{
    public MyContext(MongoDbConnectionInfo connectionInfo)
        : base(connectionInfo)
    {
    }  

vs.

public class MyContext: RedisDbContext, IMyContext
{
    public MyContext(RedisDbConnectionInfo connectionInfo)
        : base(connectionInfo)
    {
    }

The base class handles all of the database-specific integration code.

NoSQL data modeling

When you design a NoSQL model, you have two types of entities:

  • First class entities - entities that have their own collection (equivalent of table)
  • Second class entities - entities that are composed under under entities and do not have their own collection.

The first class entities are similar to the entities you are used to mapping to relational databases, however, their properties can be either simple fields, or complex properties, e.g., another object, a collection or a collection of objects. Entities can therefore be stored with either IDs of other entities or the actual objects. You can use these rule of thumb for determining which modeling to use.

  • Can the instances of sub-entities stand own (first class) or do they only have meaning under the parent (second class)?
  • Do the sub-entities have the same access rules as the parent class? (Security considerations.) If not they should be first class.
  • When you load the parent, do you always want to get the sub-entities (second class), or do you want to be able to load them without loading the children (first class)?

When you model sub-entities as first class entities, you end up hold IDs of one entity within another (like foreign keys do in relational database), which will probably lead you to looking for a solution for navigation properties.

Navigation properties

You can define a navigation property like so:

public class EntityWithNavigation : IEntity<Guid>
{
    public Guid Id { get; set; }
    public String Name { get; set; }
        
    public Guid InfoId { get; set; }
        
    [Navigation("InfoId", typeof(InfoBase))]
    public Info Info { get; set; }

    public IEnumerable<Guid> TagIds { get; set; }

    [Navigation("TagIds", typeof(Tag))]
    public IEnumerable<Tag> Tags { get; set; }
}

where InfoBase is the base class of Info and the type used to define the collection in which Info is stored.

And use it like this:

uow.EntitiesWithNavigation.SaveNavigation(new[] { item }, new[] { "Info", "Tags" });

and

uow.EntitiesWithNavigation.LoadNavigation(new[] { item }, new[] { "Info", "Tags" });

Where the properties you choose to save or load can be defined on all of the entities or only on some of them (e.g. only on entities of a specific inheriting type).

Other interesting features

Additional attributes in use include DbIgnoreAttribute, which enables you to prevent storing and retrieving specific properties.

The MongoDb version as supports GridFS - MongoDB's file storage.

Points of Interest

While this abstraction has been implemented above both MongoDB (using MongoDB's official C# driver) and Redis (using ServiceStack.Redis), the abilities of these two database and their .NET clients differ and therefore the abstraction above each one has different limitations. For more details see the readme file.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

DannyVarod
Architect
Israel Israel
A Software Developer with over 12 years of experience in Software Analysis, Design and Development, Domain Modelling, Requirement Analysis, Product Management, Leading Development, Mentoring, System Analysis and Design and in Innovating.
 
Experienced in a variety of domains and in Interdisciplinary Development too.
 
Specializes in analysing existing environments, identifying points for improvement and new opportunities and coming up with innovative solutions.
 
Graduated with awards in Computer & Software Engineering. (Technion - Israel Institute of Technology)

Comments and Discussions

 
QuestionGreat Initiative PinmemberMember 101935906-Aug-13 9:13 
AnswerRe: Great Initiative PinmemberDannyVarod23-Oct-13 8:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 29 Jul 2013
Article Copyright 2013 by DannyVarod
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid