Skip to content

Commit

Permalink
Merge PR #18.
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony Sneed committed Oct 23, 2017
2 parents 5a5f074 + f7632f7 commit f5709f0
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 23 deletions.
288 changes: 266 additions & 22 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,279 @@
## URF - Unit of Work & (extensible/generic) Repositories Framework ##
### The Official URF Team & Repository ###
# URF #
**_<sup>(Unit-of-Work & Repository Framework)</sup>_**
### Official [URF](https://github.com/lelong37/URF) Team | Docs: [goo.gl/6zh9zp](https://goo.gl/6zh9zp) | Subscribe URF Updates: [@lelong37](http://twitter.com/lelong37) ###

This framework (over 45K+ downloads) minimizes the surface area of your ORM technology from disseminating in your application. This framework was deliberately designed to be lightweight, small in footprint size, and non-intimidating to extend and maintain. **When we say lightweight we really mean lightweight, when using this framework with the Entity Framework provider there are only 10 classes.** This lightweight framework will allow you to elegantly, unobtrusively, and easily patternize your applications and systems with Repository, Unit of Work, and Domain Driven Design. To use Generic Repositories or not? The framework allows the freedom of both, generic repositories and the ability to add in your own domain specific repository methods.
This framework ([over 100K+ total downloads](https://genericunitofworkandrepositories.codeplex.com)) minimizes the surface area of your ORM technology from disseminating in your application. This framework was deliberately designed to be lightweight, small in footprint size, and non-intimidating to extend and maintain. **When we say lightweight we really mean lightweight, when using this framework with the Entity Framework provider there are only 10 classes.** This lightweight framework will allow you to elegantly, unobtrusively, and easily patternize your applications and systems with Repository, Unit of Work, and Domain Driven Design. To use Generic Repositories or not? The framework allows the freedom of both, generic repositories and the ability to add in your own domain specific repository methods, in short **Unit of Work with extensible and generic Repositories**.

Live demo: [longle.azurewebsites.net](http://longle.azurewebsites.net)

### Architecture Overview (Sample Northwind Application with URF Framework) ###
![Architecture Overview (Sample Northwind Application & Framework)](https://lelong37.files.wordpress.com/2015/01/2015-01-03_19-15-001.png)

1. UI (Presentation) Layer
#### URF sample and usage in ASP.NET Web API ####

>>ASP.NET MVC - (Sample app: Northwind.Web)
>>Kendo UI - (Sample app: Northwind.Web)
>>AngularJS - (Sample app: Northwind.Web)

2. Service and Data Layer
```csharp
public class CustomerController : ODataController
{
private readonly ICustomerService _customerService;
private readonly IUnitOfWorkAsync _unitOfWorkAsync;

>>Repository Pattern - Framework (Repository.Pattern, Repository.Pattern.Ef6, Northwind.Repository)
>>Unit of Work Pattern - Framework (Repository.Pattern, Repository.Pattern.EF6, Northwind.Repository)
>>Entity Framework
>>Service Pattern - Framework (Service.Pattern, Northwind.Service)

3. Domain Driven Design (*slated for release v4.0.0)
public CustomerController(
IUnitOfWorkAsync unitOfWorkAsync,
ICustomerService customerService)
{
_unitOfWorkAsync = unitOfWorkAsync;
_customerService = customerService;
}

>>Domain Events
>>*more to come
#### Technology Stack ####
// GET: odata/Customers
[HttpGet]
[Queryable]
public IQueryable<Customer> GetCustomer()
{
return _customerService.Queryable();
}

Visual Studio 2013, Entity Framework 6, Sql Server 2014 / Sql Azure, Azure WebSite, ASP.NET MVC 5, [AngularJS](http://angularjs.org/), [Kendo UI](http://http//www.telerik.com/kendo-ui), [Angular Kendo](http://kendo-labs.github.io/angular-kendo/#/), Web Api 2, OData, [Entlib Unity](http://unity.codeplex.com/)
// GET: odata/Customers(5)
[Queryable]
public SingleResult<Customer> GetCustomer([FromODataUri] string key)
{
return SingleResult.Create(_customerService.Queryable().Where(t => t.CustomerID == key));
}

Subscribe to updates: [@lelong37](http://twitter.com/lelong37)
// PUT: odata/Customers(5)
public async Task<IHttpActionResult> Put(string key, Customer customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (key != customer.CustomerID)
{
return BadRequest();
}

customer.TrackingState = TrackingState.Modified;
_customerService.Update(customer);

try
{
await _unitOfWorkAsync.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(key))
{
return NotFound();
}
throw;
}

return Updated(customer);
}

// POST: odata/Customers
public async Task<IHttpActionResult> Post(Customer customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

customer.TrackingState = TrackingState.Added;
_customerService.Insert(customer);

try
{
await _unitOfWorkAsync.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (CustomerExists(customer.CustomerID))
{
return Conflict();
}
throw;
}

return Created(customer);
}

//// PATCH: odata/Customers(5)
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] string key, Delta<Customer> patch)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

Customer customer = await _customerService.FindAsync(key);

if (customer == null)
{
return NotFound();
}

patch.Patch(customer);
customer.TrackingState = TrackingState.Modified;

try
{
await _unitOfWorkAsync.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(key))
{
return NotFound();
}
throw;
}

return Updated(customer);
}

// DELETE: odata/Customers(5)
public async Task<IHttpActionResult> Delete(string key)
{
Customer customer = await _customerService.FindAsync(key);

if (customer == null)
{
return NotFound();
}

customer.TrackingState = TrackingState.Deleted;

_customerService.Delete(customer);
await _unitOfWorkAsync.SaveChangesAsync();

return StatusCode(HttpStatusCode.NoContent);
}

// GET: odata/Customers(5)/CustomerDemographics
[Queryable]
public IQueryable<CustomerDemographic> GetCustomerDemographics([FromODataUri] string key)
{
return
_customerService.Queryable()
.Where(m => m.CustomerID == key)
.SelectMany(m => m.CustomerDemographics);
}

// GET: odata/Customers(5)/Orders
[Queryable]
public IQueryable<Order> GetOrders([FromODataUri] string key)
{
return _customerService.Queryable().Where(m => m.CustomerID == key).SelectMany(m => m.Orders);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
_unitOfWorkAsync.Dispose();
}
base.Dispose(disposing);
}

private bool CustomerExists(string key)
{
return _customerService.Query(e => e.CustomerID == key).Select().Any();
}
}
```

#### Implementing Domain Logic with URF Service Pattern ###
All methods that are exposed from `Repository<TEntity>` in `Service<TEntity>` are overridable to add any pre or post domain/business logic. Domain business logic should be in the Service layer and not in Controllers or Repositories for separation of concerns.

1. Create an Interface e.g. `ICustomerService`, which should always inherit `IService<TEnttiy>` e.g. `IService<Customer>`
2. Implement the concrete implementation for your Interface e.g. `CustomerService` which implements `ICustomerService`
3. If using DI & IoC, don't forget to wire up the binding of your Interface and Implementation e.g. `container.RegisterType<ICustomerService, CustomerService>()`, see next example for more details on wiring up DI & IoC.

```csharp
public interface ICustomerService : IService<Customer>
{
decimal CustomerOrderTotalByYear(string customerId, int year);
IEnumerable<Customer> CustomersByCompany(string companyName);
IEnumerable<CustomerOrder> GetCustomerOrder(string country);
}


public class CustomerService : Service<Customer>, ICustomerService
{
private readonly IRepositoryAsync<Customer> _repository;

public CustomerService(IRepositoryAsync<Customer> repository) : base(repository)
{
_repository = repository;
}

public decimal CustomerOrderTotalByYear(string customerId, int year)
{
// add any domain logic here
return _repository.GetCustomerOrderTotalByYear(customerId, year);
}

public IEnumerable<Customer> CustomersByCompany(string companyName)
{
// add any domain logic here
return _repository.CustomersByCompany(companyName);
}

public IEnumerable<CustomerOrder> GetCustomerOrder(string country)
{
// add any domain logic here
return _repository.GetCustomerOrder(country);
}

public override void Insert(Customer entity)
{
// e.g. add any business logic here before inserting
base.Insert(entity);
}

public override void Delete(object id)
{
// e.g. add business logic here before deleting
base.Delete(id);
}
}

```

#### URF Sample DI & IoC Configuration with Framework of your choice, exampled here using Microsoft Unity DI & IoC ####

```csharp
public class UnityConfig
{
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});

public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}

public static void RegisterTypes(IUnityContainer container)
{
container
.RegisterType<IDataContextAsync, NorthwindContext>(new PerRequestLifetimeManager())
.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager())
.RegisterType<IRepositoryAsync<Customer>, Repository<Customer>>()
.RegisterType<IRepositoryAsync<Product>, Repository<Product>>()
.RegisterType<IProductService, ProductService>()
.RegisterType<ICustomerService, CustomerService>()
.RegisterType<INorthwindStoredProcedures, NorthwindContext>(new PerRequestLifetimeManager())
.RegisterType<IStoredProcedureService, StoredProcedureService>();
}
}
```

### Roadmap ###

Expand All @@ -44,4 +288,4 @@ https://github.com/TrackableEntities/observable-entities-js
- URF v5 Beta ETA: 10/2017 - https://github.com/lelong37/URF/releases/tag/v5.0-alpha
- URF v5 RC1 ETA: 10/2017 - https://github.com/lelong37/URF/releases/tag/v5.0-alpha

URF v5 major feature will include (self) Trackable Entities across physical boundaries without DbConext/DataConext, coming soon...! [Tony Sneed](https://twitter.com/tonysneed) from the [Trackable Entities Team](https://github.com/TrackableEntities/trackable-entities) will be leading this effort and collaboration..! Please tweet us [@tonysneed](https://twitter.com/tonysneed), [@lelong37](https://twitter.com/lelong37) for any questions or comments.
URF v5 major feature will include (self) Trackable Entities across physical boundaries without DbConext/DataConext, coming soon...! [Tony Sneed](https://twitter.com/tonysneed) from the [Trackable Entities Team](https://github.com/TrackableEntities/trackable-entities) will be leading this effort and collaboration..! Please tweet us [@tonysneed](https://twitter.com/tonysneed), [@lelong37](https://twitter.com/lelong37) for any questions or comments. Special thanks [@reddy6ue](https://github.com/reddy6ue) for helping out with migrating our docs from CodePlex.
2 changes: 1 addition & 1 deletion main/Source/Repository.Pattern/Repositories/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface IRepository<TEntity> where TEntity : class, ITrackable
[Obsolete("InsertGraphRange has been deprecated. Instead call Insert to set TrackingState on enttites in a graph.")]
void InsertGraphRange(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Delete(object id);
void Delete(params object[] keyValues);
void Delete(TEntity entity);
IQueryFluent<TEntity> Query(IQueryObject<TEntity> queryObject);
IQueryFluent<TEntity> Query(Expression<Func<TEntity, bool>> query);
Expand Down

0 comments on commit f5709f0

Please sign in to comment.