NRepository

4/9/2015 -Currently in the process of moving this to GitHub. This will include far better quality sample code and the ContosoUniversity application will contain full production quality back end code. demonstrating CQRS, DDD and NRepository in action. Peek: https://github.com/j-kelly

Please download the latest sample code for Asp.Net, Web.Api & OData examples using both Entity Framework and MongoDb.


NRepository is one of the simplest and yet the most powerful generic, and data store agnostic, repository implementations available today. Rather than promoting reuse through service layers (which create tightly coupled, inflexible and bloated code), it's extensive use of reusable strategies allows a clean, understandable and fluent approach for creating use case specific queries or commands.

NRepository removes completely the need to implement multiple IRepository<T> or ICustomerServices types of patterns in your code. To access or manipulate your data simply inject one of 2 interfaces IQueryRepository (for queries only) or IRepository (for queries and commands) into your constructor and away you go.

Very Quick Start

  1. Install the NRepository.Core, NRepository.EntityFramework, NRepository.MongoDb nuget package.
  2. To retrieve data run the following code:
// Entity Framework
IQueryRepository queryRepository = new EntityFrameworkQueryRepository(new DbContext());
var persons = queryRepository.GetEntities<Person>();
var person = await queryRepository.GetEntityAsync<Person>(p => p.Id == 1);

// MongoDb
IQueryRepository queryRepository = new MongoDbQueryRepository(new MongoDatabase());
var persons = await queryRepository.GetEntitiesAsync<Person>();
var person = queryRepository.GetEntity<Person>(p => p.Id == 1);

// IEnumerable<object> ('In Memory' list - ideal for unit tests)
IQueryRepository queryRepository = new InMemoryQueryRepository(myListOfObjects);
var persons = await queryRepository.GetEntitiesAsync<Person>();
var person = queryRepository.GetEntity<Person>(p => p.Id == 1);

3. And to update your repository use the following code:

// Entity Framework
IRepository repository = new EntityFrameworkRepository(new DbContext());
var newPerson = new Person { ... };
repository.Add(newPerson);
repository.Save();

// MongoDb
IRepository repository = new MongoDbUnitOfWorkRepository(new MongoDatabase());
var newPerson = new Person { ... } };
repository.Add(newPerson);
await repository.SaveAsync();

It really is that simple.

NRepository Features and Benefits

Some of the benefits of NRepository include:
  1. Designed for CQRS and DDD but works equally well in traditionally designed applications.
  2. Perfect for micro services
  3. Built to enable clean, testable, easily understandable and maintainable service code.
  4. Works with your existing entities as is. No need to add base classes or identity interfaces to your entities.
  5. Provides full interception capabilities of any call to your repository. This enables projection, auditing, logging, caching, etc. to be easily added to you application.
  6. A full event aware architecture allows any action performed in NRepository to be fully recorded through the raising of events.
  7. Includes a full suite of unit test helper classes.
  8. Multiple implementations exist.

Below are examples of some of the more complex queries and command that can be created, although see documentation for more advanced features such as interception, reusable query creations and how to fully test your NRepository code.

General Query Strategies

// Get list with paging
persons = queryRepository.GetEntities<Person>(
    new OrderByDescendingQueryStrategy<Person>(p => p.Name),
    new PagingQueryStrategy(10, 10, getRowCount: false));

//  All persons that contain Peter in the name but not those that also contain Pan
persons = queryRepository.GetEntities<Person>(
        new TextSearchSpecificationStrategy<Person>(p => p.Name, "Peter", false) & !
        new TextSearchSpecificationStrategy<Person>(p => p.Name, "Pan", false),
        new OrderByQueryStrategy<Person>(p => p.Name));

// Filtering by type
persons = queryRepository.GetEntities<Person>(
    new OfTypeQueryStrategy<Manager>(p => p.Name));

// dynamic field ordering with conditional strategies
var filterByAge = true;
persons = queryRepository.GetEntities<Person>(
    new OrderByQueryStrategy("Age").OnCondition(filterByAge),
    new OrderByQueryStrategy("Name").OnCondition(!filterByAge));

// using conditional strategies
var take = 10;
var skip = 0 ;
string orderBy = PropertyInfo<AggregateCustomerSales>.GetMemberName(p => p.Sales);
var customers = await _QueryRepository.GetEntitiesAsync<AggregateCustomerSales>(
    new ConditionalQueryStrategy(ascending, () => new OrderByQueryStrategy(orderBy)),
    new ConditionalQueryStrategy(!ascending, () => new OrderByDescendingQueryStrategy(orderBy)),
    new ConditionalQueryStrategy(take > 0, () => new PagingQueryStrategy(take, skip)));

// Adding new implementation specific strategies
var interestingNames = new[] {"John", "Jim", "Bob"} ;
persons = queryRepository.GetEntities<Person>(
    new FilterByNameQueryStrategy<Person>(interestingNames),
    new OrderByQueryStrategy<Person>(
        p => p.Name,
        p => p.Age));

Entity Framework Specific Query Strategies

// All persons where Peter is not found in the name with eager loading (Include)
persons = queryRepository.GetEntities<Person>(
        !new TextSearchSpecificationStrategy<Person>(p => p.Name, "Peter", false),
        new AsNoTrackingQueryStrategy(),
        new EagerLoadingQueryStrategy<Person>(
            p => p.Partner,
            p => p.Children));

// Entity framework strategies (EagerLoading and no tracking)
persons = queryRepository.GetEntities<Person>(
    new AsNoTrackingQueryStrategy(),
    new EagerLoadingQueryStrategy<Person>(
        p => p.Children,
        p => p.Partner));

// Explicit Loading with eager loading
person = queryRepository.GetEntity<Person>(p => p.Name == "Peter Parker");
queryRepository.Load(
    person,
    p => p.Partner,
    new AsNoTrackingQueryStrategy(),
    new EagerLoadingQueryStrategy<Person>(
        p => p.Children,
        p => p.Age));

// Explicit loading with filtering using strategies
person = queryRepository.GetEntity<Person>(p => p.Name == "Peter Parker");
queryRepository.Load(
    person,
    p => p.Children,
    new ExpressionQueryStrategy<Person>(p => p.Children.Count() > 2));

// Explicit loading with filtering using multiple strategies
person = queryRepository.GetEntity<Person>(p => p.Name == "Peter Parker");
queryRepository.Load(
    person,
    p => p.Children,
    new ExpressionSpecificationQueryStrategy<Person>(p => p.Children.Count() > 2) &
    new TextSearchSpecificationStrategy<Person>(p => p.Name, "Peter", false),
    new AsNoTrackingQueryStrategy(),
    new OrderByQueryStrategy<Person>(p => p.Name),
    new EagerLoadingQueryStrategy<Person>(
        p => p.Children,
        p => p.Name));

// Entity framework method extensions

// Update the entity state of an object
repository.UpdateEntityState(person, EntityState.Detached);
var affectedCount = repository.ExecuteStoredProcudure("UpdateNextSalesDate @Date", DateTime.Today);
var items = queryRepository.ExecuteSqlQuery<Person>("GetPersonDetails {0}", 12) ;

More advanced specification strategies

// Complex rules based search
var isValidDate = new FilterByValidDateSpecificationStrategy(DateTime.Now);
var isValidUser = new FilterByUserSpecificationStrategy(userId);
var isValidSection = new FilterBySectionSpecificationStrategy(section);
var isValidEditionStatus = new FilterbyEditionStatusSpecificationStrategy(owningProduct);
var isValidProductStatus = new FilterbyProductStatusSpecificationStrategy(owningProduct);
var isFuturePresentation = new FilterFuturePresentationaSpecificationStrategy(isPastPresenation);
var isSpecialist = new FilterByRoleSpecificationStrategy(PermissionRoleTypes.Specialist, role);
var isInProductsFaculty = new FilterByFacultySpecificationStrategy(faculty, owningFaculty);
var isModuleManagerForProduct = new FilterByModuleSpecificationStrategy(product.ProductType) & new FilterByRoleSpecificationStrategy(PermissionRoleTypes.ModuleManager, role) & new FilterByProductIdSpecificationStrategy(productId);
var isProgramManagerForProduct = new FilterByRoleSpecificationStrategy(PermissionRoleTypes.ProgramManager, role) & new FilterByProgrammeSpecificationStrategy(programmeOrgUnit);
var isQualificationManagerForProduct = new FilterByRoleSpecificationStrategy(PermissionRoleTypes.QualificationManager, role) & new FilterByProductIdSpecificationStrategy(productId);

// Aggregate strategies into a single statement
var permissions = queryRepository.GetEntities<Permission>(
    isValidDate &
    isValidUser &
    isValidSection &
    isValidEditionStatus &
    isValidProductStatus &
    (isSpecialist | isFuturePresentation) &
    (isSpecialist
        | isModuleManagerForProduct
        | isInProductsFaculty & (isProgramManagerForProduct | isQualificationManagerForProduct)));

var canEit = permissions.Any();

// Quick search pattern for searching for text from multiple columns using strategies
private static SpecificationQueryStrategy<UserClaimViewModel> CreateSearchStrategy(string searchString)
{
    searchString = searchString ?? string.Empty;

    var spec = (SpecificationQueryStrategy<UserClaimViewModel>)new ExpressionSpecificationQueryStrategy<UserClaimViewModel>(p => true);
    foreach (var item in searchString.Split(' '))
    {
        var validDate = default(DateTime);
        if (DateTime.TryParse(item, out validDate))
        {
            spec = spec & new ExpressionSpecificationQueryStrategy<UserClaimViewModel>(
                p => p.ValidFrom <= validDate.Date && (!p.ValidTo.HasValue || p.ValidTo > validDate));

            continue;
        }

        spec = spec &
            (new TextSearchSpecificationStrategy<UserClaimViewModel>(p => p.AllowableProducts, item) |
            new TextSearchSpecificationStrategy<UserClaimViewModel>(p => p.AllowableProgrammes, item) |
            new TextSearchSpecificationStrategy<UserClaimViewModel>(p => p.Faculty, item) |
            new TextSearchSpecificationStrategy<UserClaimViewModel>(p => p.Role, item) |
            new TextSearchSpecificationStrategy<UserClaimViewModel>(p => p.UserName, item));
    }

    return spec;
}

Last edited Sep 4, 2015 at 6:33 PM by KelbobK5, version 55