yet another repository for .net
Yarn is a generic repository pattern implementation for .Net. Its goal is to enforce consistent approach to data persistence regardless of the underlying technology.
Here is what it currently supports:
- EF
- EF Core
- NHibernate
- SQL Server
- MySql
- SQLite
- Oracle
- Postgres
- RavenDB
- MongoDB
- In-memory object storage
- Nemo (micro-ORM library)
- EventStore
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository>(() => new Yarn.Data.MongoDbProvider.Repository(
new RepositoryOptions
{
ConnectionString = ConfigurationManager.AppSettings["MongoConnection"]
}));
// Resolve IRepository (this may happen anywhere within application)
// Let's assume we have defined entity Category
var repo = ObjectContainer.Current.Resolve<IRepository>();
var category = repo.GetById<Category, int>(1000);
var categories = repo.GetByIdList<Category, int>(new[] { 1000, 1100 });
// Yarn provides a simple IoC container implementation
// One can easily override it with any DI framework
// of choice by implementing IContainer
// The following code should be called on application startup
// in order to override IoC container
ObjectContainer.Initialize(() => new Any_IoC_Implementation_Based_On_IContainer());
// IRepository is instantiated using default constructor of Yarn.Data.EntityFrameworkProvider.Repository
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
// IRepository is instantiated using parametrized constructor of Yarn.Data.EntityFrameworkProvider.Repository
ObjectContainer.Current.Register<IRepository>(() => new Yarn.Data.EntityFrameworkProvider.Repository(
ObjectContainer.Current.Resolve<IDataContext<DbContext>>()));
// IRepository is instantiated unders a specific instance name
ObjectContainer.Current.Register<IRepository>(() => new Yarn.Data.EntityFrameworkProvider.Repository(
ObjectContainer.Current.Resolve<IDataContext<DbContext>>()), "Lazy");
// IRepository is instantiated as a singleton
ObjectContainer.Current.Register<IRepository>(new Yarn.Data.EntityFrameworkProvider.Repository(
ObjectContainer.Current.Resolve<IDataContext<DbContext>>()));
// Resolved IRepository implementation
var repo = ObjectContainer.Current.Resolve<IRepository>();
// IRepository is resolved by instance name
var repo = ObjectContainer.Current.Resolve<IRepository>("Lazy");
// Bind IRepository to specific implementation (this should happen during application startup)
// For this example one must provide "EF.Default.Model" application setting and "EF.Default.Connection" connection string setting
// ("EF.Default.Model" points to an assembly which contains model class definition)
ObjectContainer.Current.Register<IRepository>(() => new Yarn.Data.MongoDbProvider.Repository(
new RepositoryOptions
{
ConnectionString = ConfigurationManager.AppSettings["MongoConnection"]
}), "mongo");
ObjectContainer.Current.Register<IRepository>(() => new Yarn.Data.EntityFrameworkProvider.Repository(
ObjectContainer.Current.Resolve<IDataContext<DbContext>>()), "ef");
// Resolve IRepository (this may happen anywhere within application)
// "mongo" will resolve to MongoDB implementation, while "ef" will resolve to EF implementation
var repo = ObjectContainer.Current.Resolve<IRepository>("ef");
//var repo = ObjectContainer.Current.Resolve<IRepository>("mongo");
var category = repo.GetById<Category, int>(1000);
// With NHibernate one must specify implementation of the data context to be used with repository
// For this example one must provide "NHibernate.MySqlClient.Model" application setting
// and "NHibernate.MySqlClient.Connection" connection string setting
ObjectContainer.Current.Register<IRepository>(() => new Yarn.Data.NHibernateProvider.Repository(
ObjectContainer.Current.Resolve<IDataContext<ISession>>("nh_uow_mysql")), "nh");
ObjectContainer.Current.Register<IDataContext<ISession>>(
() => new Yarn.Data.NHibernateProvider.MySqlClient.MySqlDataContext(), "nh_uow_mysql");
// In order to use NHibernate with SQL Server one has to bind IDataContext to the SQL Server implementation
// Similarly to the MySQL example "NHibernate.SqlClient.Model" application setting
// and "NHibernate.SqlClient.Connection" connection string setting should be defined
// ObjectContainer.Current.Register<IDataContext<ISession>>(() =>
new Yarn.Data.NHibernateProvider.SqlClient.Sql2012DataContext(), "nh_uow_sql");
var repo = ObjectContainer.Current.Resolve<IRepository>("nh");
var categories = repo.FindAll<Category>(c => c.Name.Contains("cat"), offset: 50, limit: 10);
-
EF
// Currently works only with SQL Server provider for EF ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.FullTextRepository>(); ObjectContainer.Current.Register<IFullTextProvider, Yarn.Data.EntityFrameworkProvider.SqlClient.SqlFullTextProvider>(); var repo = ObjectContainer.Current.Resolve<IRepository>(); var categories = ((IFullTextRepository)repo).FullText.Seach<Category>("hello world");
-
NHibernate
ObjectContainer.Current.Register<IRepository, Yarn.Data.NHibernateProvider.FullTextRepository>(); ObjectContainer.Current.Register<IFullTextProvider, Yarn.Data.NHibernateProvider.LuceneClient.LuceneFullTextProvider>(); // One can resort to use of SQL Server full text as well // ObjectContainer.Current.Register<IFullTextProvider, Yarn.Data.NHibernateProvider.SqlClient.SqlFullTextProvider>(); var repo = ObjectContainer.Current.Resolve<IRepository>(); var categories = ((IFullTextRepository)repo).FullText.Seach<Category>("hello world");
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
// Resolve IRepository (this may happen anywhere within application)
var repo = ObjectContainer.Current.Resolve<IRepository>();
// Create a specification to abstract search criteria
var spec = new Specification<Category>(c => c.Name.Contains("hello")).Or(c => c.Name.Contains("world"));
var categories = repo.FindAll<Category>(spec);
// Implement a cache provider to support caching of your choice
public class SimpleCache : ICacheProvider
{
// Implementation goes here
}
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
// Resolve IRepository (this may happen anywhere within application)
var repo = ObjectContainer.Current.Resolve<IRepository>();
// Initialize cache repository adapter
// Note: Cache adapter implements write-through cache for entity-related
// operations and generational caching for queries
var cachedRepo = repo.WithCache<SimpleCache>();
// Create a specification to abstract search criteria
var spec = new Specification<Category>(c => c.Name.Contains("hello")).Or(c => c.Name.Contains("world"));
// This call produces a cache miss, hence the database is hit
var categories1 = cachedRepo.FindAll<Category>(spec);
// This call produces a cache hit, hence there will be no trip to the database
var categories2 = cachedRepo.FindAll<Category>(spec);
In order for this to work IRepository
implementation must also implement ILoadServiceProvider
.
NoSQL Yarn providers such as Yarn.MongoDB and Yarn.RavenDB do not implement this interface.
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
// Resolve IRepository (this may happen anywhere within application)
var repo = ObjectContainer.Current.Resolve<IRepository>();
// Load customer with orders and order details
var customer = repo.As<ILoadServiceProvider>()
.Load<Customer>()
.Include(c => c.Orders)
.Include(c => c.Orders.Select(o => o.Order_Details))
.Find(c => c.CustomerID == "ALFKI");
Object graph merging only works for repositories that implement ILoadServiceProvider
interface.
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
// Resolve IRepository (this may happen anywhere within application)
var repo = ObjectContainer.Current.Resolve<IRepository>();
// Merge customer changes with customer data from the database
// Yarn will attempt to merge only the changes specified by the navigation paths
// Note: currently only EF, NHibernate and Nemo providers implement this functionality
var mergedCustomer = repo.As<ILoadServiceProvider>()
.Load<Customer>()
.Include(c => c.Orders)
.Include(c => c.Orders.Select(o => o.Order_Details))
.Update(customer);
// Auditable adapter will automatically populate audit information
// when calling Add/Update for all entities which implement IAuditable interface
var repo = ObjectContainer.Current.Resolve<IRepository>().WithAudit(Thread.CurrentPrincipal);
// Soft-delete adapter will automatically re-write Remove as Update
// and will filter all deleted records out on retrieve for all entities
// which implement ISoftDelete interface
var repo = ObjectContainer.Current.Resolve<IRepository>().WithSoftDelete(Thread.CurrentPrincipal);
// Multi-tenancy adapter will automatically filter tenant related data as well
// as check tenant ownership when calling Add, Update and Remove for all entities
// which implement ITenant interface
// Note: the following example assumes Thread.CurrentPrincipal implements ITenant interface
var repo = ObjectContainer.Current.Resolve<IRepository>().WithMultiTenancy((ITenant)Thread.CurrentPrincipal);
// Fail-over adapter can use an alternative repository if one becomes unavailable
// Once the fail-over happens, the repo adapter will stick to that one until it becomes unavailable
// Updates however are sent to both repositories
// Note: one can use it with more than two repository implementations by chaining
var repo = ObjectContainer.Current.Resolve<IRepository>().WithFailover(ObjectContainer.Current.Resolve<IRepository>("no-sql"));
// It is also possible to chain the adapters
// Note: IPrincipal parameter is optional for soft-delete and auditable adapters
var repo = ObjectContainer.Current.Resolve<IRepository>().WithSoftDelete().WihAudit();
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
// Resolve IRepository (this may happen anywhere within application)
var repo = ObjectContainer.Current.Resolve<IRepository>();
// As of now bulk operations are implemented by EF and Mongo providers only
var bulk = repo.As<IBulkOperationsProvider>();
// Bulk retrieve
var customers = bulk.GetById<Customer, string>(new[] { "ALFKI", "ANTON" });
// Bulk delete by id
bulk.Delete<Customer, string>(new[] { "ALFKI", "ANTON" });
// Bulk delete
bulk.Delete<Customer>(c => c.City == "London");
// Bulk update
bulk.Update<Customer>(c => c.City == "New York", c => new Customer { City = c.City + " City" });
public class LogInterceptor : IDisposable
{
private readonly ILog logger = LogManager.GetLogger("my-log");
private readonly Stopwatch sw;
public LogIntercetor(InterceptorContext ctx)
{
sw = Stopwatch.StartNew();
logger.Info("Begin " + ctx.Method.Name);
ctx.Execute();
}
public void Dispose()
{
sw.Stop();
logger.Info("End " + ctx.Method.Name + ", " + sw.Elapsed.TotalMilliseconds + " ms");
}
}
// Interceptor will automatically intercept all methods
var repo = ObjectContainer.Current.Resolve<IRepository>().WithInterceptor(ctx => new LogInterceptor(ctx));
// Bind IRepository to specific implementation (this should happen during application startup)
ObjectContainer.Current.Register<IRepository, Yarn.Data.EntityFrameworkProvider.Repository>();
ObjectContainer.Current.Register<ICustomerRepository, CustomerRepository>();
public interface ICustomerRepository : IEntityRepository<Customer, string>
{
IQueryResult<Order> GetOrders(string id);
}
public class CustomerRepository : ICustomerRepository
{
private readonly IRepository _repo;
public CustomerRepository(IRepository repo)
{
_repo = repo;
}
public IQueryResult<Customer> Find(ISpecification<Customer> criteria)
{
var customer = _repo.Find(criteria);
var items = customer != null ? new[] { _repo.Find(criteria) } : new Customer[] { };
return new QueryResult<Customer>(items, customer != null ? 1 : 0 );
}
public IQueryResult<Customer> GetAll()
{
return new QueryResult<Customer>(_repo.FindAll(new Specification<Customer>(c => true)),
_repo.Count<Customer>());
}
public Customer GetById(string id)
{
return _repo.GetById<Customer, string>(id);
}
public IQueryResult<Order> GetOrders(string id)
{
return new QueryResult<Order>(_repo.FindAll<Order>(o => o.CustomerID == id),
_repo.Count<Order>(o => o.CustomerID == id));
}
public void Remove(Customer entity)
{
_repo.Remove(entity);
}
public Customer Remove(string id)
{
return _repo.Remove<Customer, string>(id);
}
public bool Save(Customer entity)
{
return _repo.As<ILoadServiceProvider>().Load<Customer>().Update(entity) != null;
}
}