1. Jarle Moe
  2. Cuddlefluff.Data

Overview

HTTPS SSH

Cuddlefluff.Data

Repository pattern with Create, Update, Delete operations and support for inheritance.

Dapper is a dependency.

Fairly simple to use and doesn't have any surprising behavior as far as I know.

Key points

  • Inheritance support
  • Compound key support
  • Relatively database agnostic (It uses SELECT @Identity, Lda. which is MSSQL specific.)
  • Caching is easy to implement (ICache, DataContext.CreateCache(), DataContext.GetCacheStore())
  • Relatively fast. (Needs optimization and a code cleanup)
  • Connection will be automatically freed back to the connection pool on Dispose
  • No need to have DTO's other than the objects representing the entity (which means that the entities themselves are serializable! No proxies!)
  • Logging happens in one place (DataContext)
  • Easy to use
  • Fairly small
  • No build operations or code generation necessary
  • No special understanding or specific know-how necessary
  • Transparent

The idea is to provide a complete set of methods for solving common problems and actions that you perform often. The repository pattern makes communication with the database simple, and the entity->object approach seamlessly integrates into your regular work flow.

I decided to make this after dealing with some frustration resulting from what I feel is over-engineering in EntityFramework.
I did like the idea of inheritance in databases, but all the encumbering "paperwork" on EF made me not want to write code, so it effectively became a repellant for progress.

How to use

You extend BaseDataContext where you create a connection and optionally a cache. This DataContext will become the place you work with all your objects. The DataContext is IDisposable so instanciate it in a using or try-finally clause. You then extend IdentityEntityRepository (Or BaseEntityRepository depending on your needs) for each table (or use IdentityEntityRepository directly). Your tables must be defined by you, and the property names should reflect the name of the column in the database. The table classes needs a Table attribute set on them, so that the repository knows that the type is a database entity. The identity for the table needs to be marked with a Key-attribute.

In order to get it working you need to add Dapper to the project. I chose this project-layout so that you can clone the project directly into your solution.

And that's it.

Example

using Cuddlefluff.Data;
using Cuddlefluff.Data.Repositories;
using Cuddlefluff.Data.DataAnnotations;

public class AwesomeDataContext : BaseDataContext
{
    protected override System.Data.IDbConnection CreateConnection()
    {
        var conn = new System.Data.SqlClient.SqlConnection(
            @"Server=.\SQLEXPRESS;Integrated Security=true;Database=CuddlefluffDataTest");

        conn.Open();

        return conn;
    }

    private readonly Lazy<PersonRepository> _personRepo;

    public PersonRepository People { get { return _personRepo.Value; } }

    public AwesomeDataContext() : base()
    {
        _personRepo = CreateRepository<PersonRepository>();
    }
}

public class PersonRepository : IdentityEntityRepository<Person, int>
{
    // this is more than what we need.
    // You can also just use IdentityEntityRepository<Person, int> directly
    // but I'd advise you to make your own classes, because what you need
    // will change and you will kick yourself for not doing it
    // in the first place. (Plan ahead)
}

[Table]
public class BaseObject : IIdentifier<int>, ICreated, IUpdated
{
    [Key]
    public int Id { get; set;}
    public DateTime Created { get; set; }
    public DateTime Updated { get; set; }
}

[Table]
public class Person : BaseObject
{
    public string Name {get;set;}

    public override string ToString()
    {
        return Name;
    }
}

public class Program
{
    public static void Main()
    {

        using(var context = new AwesomeDataContext())
        {
            // Instanciate some dude
            var bob = new Person() { Name = "Bob" };



            // Create in database
            bob = context.People.Create(bob);

            Console.WriteLine(
                string.Format("Create person with Id {0} named {1}", 
                    bob.Id, bob.Name)
            );

            // Rename him

            var mutator = context.People.CreateMutator(bob);

            mutator.Apply(new { Name = "Dobbs" });

            context.People.Update(mutator);

            // name is now Dobbs

            Console.WriteLine(
                string.Format("Updated person with Id {0} and named him {1}", 
                    bob.Id, bob.Name)
            );

        }

    }
}

SQL Code for example objects

CREATE TABLE BaseObject (
    Id int not null identity(1,1),
    Created datetime NOT NULL,
    Updated datetime NOT NULL
) ON [PRIMARY]
ALTER TABLE BaseObject 
ADD CONSTRAINT PK_BaseObject PRIMARY KEY CLUSTERED ( Id )

CREATE TABLE Person (
    Id int NOT NULL,
    Name nvarchar(50) NULL
) ON [PRIMARY]
ALTER TABLE Person 
ADD CONSTRAINT PK_Person PRIMARY KEY CLUSTERED (Id)

A few notes

  • As you can see, I don't explicitly set Created and Updated, because they will be automatically populated by the repository (via ICreated and IUpdated).
  • Modifying entites goes through a mutator; basically just a class that keeps track of which properties are modified, so that we don't need to send the entire object to the database on every update.

This project is just a base. If you meet problems you need to solve, I can't guarantee that this will do your specific task out-of-the-box. A few things I've encountered which needs implementation are collections; a list of preset values stored in the database you need to access. Hirearchial systems (probably should do that on a collection-basis). But it will provide you with the functionality you'll need for about 80% of your database-related task.

Known issues

  • CodeOnlyAttribute is not implemented (Its intention is to tell the repository that a field is not reflected in the database)
  • ColumnAttribute is not implemented (which means that your properties' names must be identical to the ones in the database)
  • Not enough comments in the code

At the moment, not intended for production use. Screwing around only.

Important classes :

BaseDataContext

The primary object you initialize to work with repositories.
Tip : In your code you shouldn't invoke the constructor directly, instead make a type of factory pattern which creates datacontexts for you. Something like this:

YourDataContext.Create()

because it will allow you to easily work on several databases simultaneously, for instance;

YourDataContext.CreateForAccounts()
YourDataContext.CreateForGeneral()

On that note I'm thinking about making it possible for one DataContext to use several connections simultaneously so you'll only need one datacontext, because working with several contexts can be if not limiting, slightly annoying.

The connection and repositories are all lazy-load, so this means that if you use a datacontext to retrieve an object, the datacontext will not necessarily open a database connection (if it can just retrieve it directly from cache)

CreateRepository<T> instanciates a Lazy-loader which will create the repository and add it to an internal list of repositories. Repositories created with CreateRepository will be automatically disposed when the DataContext does. So if you have anything that needs some specific type of cleanup via IDisposable, it will automatically be handled.

The Cache also implements IDisposable, this is basically to make sure that if you use Caching mechanisms that needs cleanup (TCP connections etc), it will automatically be freed when the datacontext goes out of context.

Lazy-loaded repositories should be created in the constructor.

There is a method called GetConnection(object key) which you can use if you need more than one data connection. The reason the parameter is object is so that you can use enum (you can override the method with "protected virtual new GetConnection(MyDbEnum t) { base.GetConnection(t); }") to retrieve the connection. Basically, the method just does a .ToString on the parameter and calls CreateConnection(string keyName). I have provided a simple example of how it can be done, using DbProviderFactories to automatically generate the connection based on the connectionstring-settings in your application.config or web.config (in comments).

BaseEntityRepository

The base CRUD object. It has methods of Creating (Create), Reading (GetEntityById), Updating (Update), Deleting (Delete). It also handles caching on single entities (not on entire queries as of right now). When you need to list objects, you need to write your own implementation for that. I may add a method to read several entire objects, but in general when you're listing a large amount of entries, you should stick to the necessities.

IdentityEntityRepository

Since most tables have one single Id, i've made a simple repository for dealing with those objects. In most cases, this is the one you can rely on.

IIdentifier<T>

Interface that is required for IdentityEntityRepository, basically just says that the table has an Id, and its type is T (int in many cases).

TableAttribute

Required for entities. This is to prevent it from searching for a table called Object for instance.

KeyAttribute

Required for primary keys. You can use several for compound keys.

EntityMutator

For updating entities. Keeps track of what changes have been made. You should create this on the repository instead of using new EntityMutator. You're not forced to, but if you create it from there, it will be easier to inject specific entity-related functionality if needed.

DISCLAIMER

You are free to use this source code in its entirety, or parts of it however you see fit. If you chose to rewrite and/or rehost it however, I do require that you point to the original source (here).