Wiki

Clone wiki

DataAccess.Core / Home

Quick Start

Overview

DataAccess.Core is a small framework which scope is to standardize contracts (interfaces) in order to access to the underlying data resources in a common way for many kinds of Providers.

Actually, there's two implementations for this framework:

Framework features

This architecture model reflects to one famous pattern called CQRS, because this framework segregates responsibilities about how data is retrieved, and how is persisted.

Another important thing is its compatibility with old and the most recent frameworks (net45, net46, and NetStandard), so you can develop on the top using this layer, with the same semantic for all framework versions indicated.

Currently, those responsibilities are segregated into these contracts :

  • ITransactionDataFacade

    This contracts defines methods used to create unit of works.

    For example BeginTransaction().

  • IContextObserver

    Defines a set of methods used to understand the status of underlying provider (items cached, dirty items ecc).

  • IQueryableDataFacade

    Defines a set of methods responsible to retrieve data, using mainly Expressions, and some custom logic like

    paging, retrieving by id, data transformation ecc.

  • IQueryDataFacade

    defines a set of methods used to access data by query language semantics.

    In the case of RDBMS storages it could be SQL queries, in a particular case could be HQL syntax by NHibernate.

  • IPersisterDataFacade

    defines a set of methods responsible to persist data in an atomic way.

  • IQueryableDataFacadeService

    this contract aggregates the logic of persistence (IPersisterDataFacade) and retrieving data (IQueryableDataFacade).

There are another important contracts to know:

  • IContextProvider<TContext>

    Encapsulates the current context on which facades must use in order to interact with underlying data source.

    Example: It could be DbContext of EntityFramework, ISession / IStatelessSession from NHibernate.

  • ITransactionProvider<TContext>

    As well indicated by its name, this contract provides a mechanism to interact with transactions (used for persisting data).

  • INamedQueryProvider

    A custom query provider which manages a set of custom queries definitions in order to execute them into underlying storage.

Framework Diagram

Facades

Framework_Contracts.PNG

Facades dependencies

Facade_Dependencies.PNG

Providers

Providers.PNG

Exceptions

Exceptions.PNG

Getting Started

The easiest way to get started is by installing the available NuGet packages.

Quick examples

Let's see how can be used the principals interfaces.

IQueryableDataFacade

// using IQueryableDataFacade (this assignment is trivial, here It must be used the custom implementation)
IQueryableDataFacade facade = new MyQueryableDataFacade(); 

// Get entity by Id (id is an object, the underlying implementation must be know how come use this parameter as entity identifier)
User user = facade.Get<User>(user.Id);

// Exists logic applying an expression which returns a boolean
bool match = facade.Exists<User>(us => us.Name == "name parameter");

// Tries to find an unique instance from source, if there are many matches results throws an exception
User user = facade.UniqueResult<User>(us => us.CF == "CF parameter");

// Tries to find instances from source with the given criteria
IEnumerable<User> users = facade.ApplyWhere<User>(us => us.Name == "name parameter");

// Tries to find instances using IQueryable<User> function, you can apply some custom / complex logic on filtering
IEnumerable<User> users = facade.ExecuteExpression<User>(usr => usr.Where(us => us.Name == "name parameter"));

// Tries to find instances using like previous IQueryable<User> function, but using a custom output transformation
// in output I will have an List<dynamic>
var users = facade.ExecuteExpression<User, List<dynamic>>(usr => usr
                      .Where(us => us.Name == "name parameter")
                      .Select(ss => new { ss.Name, ss.Surname })
                      .ToList());

// Tries to find instances using like previous IQueryable<User> function, but in this case using anonymous result, 
// Note how the function is built inline on method.
var users = facade.ExecuteExpression((IQueryable<User> usr) => usr
                      .Where(us => us.Name == "name parameter")
                      .Select(ss => new { ss.Name, ss.Surname, ss.Birthdate })
                      .ToList());

With this approach, It's possible to read anonymous data, and take advantage of intellisense.

IPersisterDataFacade

MakePersistent() method is used to persist instance status, so It could create or update instances (It depends upon how identifier was set and mapped into underlying layer.)

MakeTransient() instead, removes instances if there are present into underlying storage (retrieved by instance identifier)

IQueryableDataFacade facade = ...;

var isolation = IsolationLevel.ReadCommitted;

// create an instance
using (var tran = facade.BeginTransaction(new TransactionDescriptor { Name = "create-instance", Isolation = isolation }))
{
    facade.MakePersistent<User>(new User());

    // Commit must be called in order to persist isntance, otherwise the given transaction rollbacks implicitly.
    tran.Commit();
}

// create many instances
IEnumerable<User> users = GetUsersToPersist();

using (var transaction = facade.BeginTransaction(new TransactionDescriptor { Isolation = IsolationLevel.ReadCommitted }))
{
    facade.MakePersistent<User>(users);
    transaction.Commit();
}

// delete instance
using (var tran = facade.BeginTransaction(new TransactionDescriptor { Name = "delete-instance", Isolation = isolation }))
{
    facade.MakeTransient<User>(user.Id);
    tran.Commit();
}

// you can use transaction Scopes in order to create a transaction root 
using (
    var rootTran = new TransactionScope(TransactionScopeOption.Required,
        new TransactionOptions {IsolationLevel = isolation, Timeout = TimeSpan.FromSeconds(240)}))
{
    using (var tran1 = facade.BeginTransaction(new TransactionDescriptor { Name = "delete-instance", Isolation = isolation }))
    {
        facade.MakePersistent(instance);
        tran1.Commit();
    }

    using (var tran2 = new TransactionScope())
    {
        // all another kinds of unit of works
        // if this transaction is not Completed correctly, root transaction cannot be commitable.
    }

    // needed in order to render atomic all inner transactions
    rootTran.Complete();
}

ITransactionalDataFacade

As seen in the previous example, transaction serves to persist definitively instances status.

// a default usage to create a transaction from facade.
using (var tran2 = facade.BeginTransaction())
{
    // all another kinds of unit of works
    // if this transaction is not Completed correctly, root transaction cannot be commitable.
}

// you can specify a transaction descriptor
User instance = GetUserToPersist();
var isolation = IsolationLevel.ReadCommitted;

using (var tran1 = facade.BeginTransaction(new TransactionDescriptor { Name = "create-or-update-instance", Isolation = isolation }))
{
    facade.MakePersistent(instance);
    tran1.Commit();
}

IQueryDataFacade

Represents a generic facade used as engine query executor.

This component can provide two types of queries:

  • IQueryCommand: Represents a query executable command.

  • IQueryMaterializer: Represents an executable query which materialize result.

Updated