Blog Archives
Entity Framework 4 POCO, Repository and Specification Pattern [Upgraded to EF 4.1]
I am excited to announce that the framework has been upgraded to the new version to follow up with the final release of Microsoft ADO.NET Entity Framework 4.1 yesterday. The upgrade includes some changes from Entity Framework API itself and also incorporates bug fixed as well many great suggestion/comments for improvement from the readers. You can download the latest version of the framework (1.4 at this writing) at http://code.google.com/p/ef4prs/downloads/list and play around with it. Note that to be able to compile the code, you first need to download and install EF 4.1 using standalone installer or by adding nuget package to your project.
If you are new to this framework and the design idea behind it, take some time to read these posts first:
- Entity Framework 4 POCO, Repository and Specification Pattern: this post explained an implementation of a data access layer on top of Entity Framework using code first development with POCO, Repositiory and Specification Pattern
- Specification Pattern in Entity Framework 4 Revisited: this post addressed a problem of the Specification patten being implemented in the earlier post and also proposed a solution to fix that problem
- Entity Framework 4 POCO, Repository and Specification Pattern [Upgraded to CTP5]: detailed the upgrade to EF CTP5
Happy coding!
Entity Framework 4 POCO, Repository and Specification Pattern [Upgraded to CTP5]
Before reading this, you might want to read these two posts first:
- Entity Framework 4 POCO, Repository and Specification Pattern: this post explained an implementation of a data access layer on top of Entity Framework using code first development with POCO, Repositiory and Specification Pattern
- Specification Pattern in Entity Framework 4 Revisited: this post addressed a problem of the Specification patten being implemented in the earlier post and also proposed a solution to fix that problem
You might have been aware that MS just released Entity Framework 4 CTP5 some days ago. There have been some comments asking me when the data access layer would be upgraded to that version. Since I had been quite busy these days, the answers for these questions was just a promise that I would do the upgrade when I had time after I finished investigating the new release carefully. However, some readers seem to be anxious waiting for the code updated so they keep asking me for the progress (and I feel good because I know there are some ones who are interested in the framework I built :)).
So, today I decided to fulfill my promise: the source code has been updated in google code. You can download the latest version (1.3 at this writing) here http://code.google.com/p/ef4prs/downloads/list
Note: to be able to compile the code, you must first download and install EF4 CTP5.
Some implementation notes:
– The assembly is now changed to EntityFramework.dll and located at C:\Program Files (x86)\Microsoft ADO.NET Entity Framework Feature CTP5\Binaries\EntityFramework.dll
– Base mapping class has changed from EntityConfiguration<T> to EntityTypeConfiguration<T>
– Entity Framework team seems to concentrate on implementing DbContext much more than ObjectContext. But if you want to access to ObjectContext from DbContext, here is the way:
ObjectContext ctx = ((IObjectContextAdapter)_dbContext).ObjectContext;
– We do not need to register entity any more, just add mapping class(es) to the ModelBuilder then we are all set. Well, this is for your information only, since this framework will do this automatically in the XContextBuilder class.
– The most and important thing, I think, is EF now will automatically map entity relationships without explicitly writing the mapping code. For example:
I have an entity Customer that has many Orders. These two classes will be defined as below:
public class Customer : Entity { public Customer() { Orders = new List<Order>(); } public virtual string Firstname { get; set; } public virtual string Lastname { get; set; } public virtual IList<Order> Orders { get; set; } public virtual DateTime Inserted { get; set; } } public class Order : Entity { public Order() { OrderLines = new List<OrderLine>(); } public virtual IList<OrderLine> OrderLines { get; set; } public virtual DateTime OrderDate { get; set; } public virtual Customer Customer { get; set; } }
Here is the mapping classes:
public class CustomerMapping : EntityTypeConfiguration<Customer> { public CustomerMapping() { HasKey(x => x.Id); Property(x => x.Firstname).IsRequired().HasMaxLength(25); Property(x => x.Lastname).IsRequired().HasMaxLength(25); Property(x => x.Inserted); //we dont need to write this code to specify the relationship, this will autolatically done by EF //HasMany(x => x.Orders).WithRequired(y => y.Customer).HasForeignKey(c => c.CustomerId); ToTable("Customer"); } } public class OrderMapping : EntityTypeConfiguration<Order> { public OrderMapping() { HasKey(x => x.Id); Property(x => x.OrderDate); // No need to write mapping code to Customer //HasRequired(c => c.Customer); ToTable("Order"); } }
With these mappings, EF will generate the Order table with a foreign key to Customer table as below:
You can download and check out the test code to see other mapping scenarios like many-to-many or how to change the table name in the mapping code.
Happy coding!
Entity Framework 4 POCO, Repository and Specification Pattern
Just like those who are going to work with a new technology, I started digging into EF by reading related books, hunting the web to find good articles and blog posts mentioning about building data access on top of EF. Since I have worked with NHibernate and also use Repository Pattern to build the data access layer, the keywords entered Google should include: “Entity Framework”, “Entity Framework Repository”, “Entity Framework vs. NHibernate”, “Entity Framework POCO and Repository Pattern” blah blah blah. Luckily I found quite a few of good posts (these will be listed as source of references at the end of this post).
With some sources of reference in hand, I started to build the data access layer using EF. But before talking about how I build it, here is a set of design patterns I want to apply: Repository, Unit of Work, and Specification. Since I also want to use POCOs instead of objects auto-generated by Visual Studio so this, once again, leads me to looking for the technique to write mapping stuff, and I found that it is also very easy.
Repository
Repository is defined as “an abstraction that provides us with persistence ignorance and a separation of concerns where the responsibility of persisting domain objects is encapsulated by the Repository that leaving the domain objects to deal entirely with the domain model and domain logic.”
The contract for a repository contains operations to perform CRUD and also for querying entity objects. The general technique is using .NET generic to build the Repository, the code below depicts the contract of repository:
public interface IRepository<TEntity> : IDisposable where TEntity : class { IQueryable<TEntity> GetQuery(); IEnumerable<TEntity> GetAll(); IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate); TEntity Single(Expression<Func<TEntity, bool>> predicate); TEntity First(Expression<Func<TEntity, bool>> predicate); void Add(TEntity entity); void Delete(TEntity entity); void Attach(TEntity entity); void SaveChanges(); void SaveChanges(SaveOptions options); }
And the common implementation:
/// <summary> /// A generic repository for working with data in the database /// </summary> /// <typeparam name="T">A POCO that represents an Entity Framework entity</typeparam> public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class { /// <summary> /// The context object for the database /// </summary> private ObjectContext _context; /// <summary> /// The IObjectSet that represents the current entity. /// </summary> private IObjectSet<TEntity> _objectSet; /// <summary> /// Initializes a new instance of the GenericRepository class /// </summary> /// <param name="context">The Entity Framework ObjectContext</param> public GenericRepository(ObjectContext context) { _context = context; _objectSet = _context.CreateObjectSet<TEntity>(); } /// <summary> /// Gets all records as an IQueryable /// </summary> /// <returns>An IQueryable object containing the results of the query</returns> public IQueryable<TEntity> GetQuery() { return _objectSet; } /// <summary> /// Gets all records as an IEnumberable /// </summary> /// <returns>An IEnumberable object containing the results of the query</returns> public IEnumerable<TEntity> GetAll() { return GetQuery().AsEnumerable(); } /// <summary> /// Finds a record with the specified criteria /// </summary> /// <param name="predicate">Criteria to match on</param> /// <returns>A collection containing the results of the query</returns> public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate) { return _objectSet.Where<TEntity>(predicate); } /// <summary> /// Gets a single record by the specified criteria (usually the unique identifier) /// </summary> /// <param name="predicate">Criteria to match on</param> /// <returns>A single record that matches the specified criteria</returns> public TEntity Single(Expression<Func<TEntity, bool>> predicate) { return _objectSet.Single<TEntity>(predicate); } /// <summary> /// The first record matching the specified criteria /// </summary> /// <param name="predicate">Criteria to match on</param> /// <returns>A single record containing the first record matching the specified criteria</returns> public TEntity First(Expression<Func<TEntity, bool>> predicate) { return _objectSet.First<TEntity>(predicate); } /// <summary> /// Deletes the specified entitiy /// </summary> /// <param name="entity">Entity to delete</param> /// <exception cref="ArgumentNullException"> if <paramref name="entity"/> is null</exception> public void Delete(TEntity entity) { if (entity == null) { throw new ArgumentNullException("entity"); } _objectSet.DeleteObject(entity); } /// <summary> /// Adds the specified entity /// </summary> /// <param name="entity">Entity to add</param> /// <exception cref="ArgumentNullException"> if <paramref name="entity"/> is null</exception> public void Add(TEntity entity) { if (entity == null) { throw new ArgumentNullException("entity"); } _objectSet.AddObject(entity); } /// <summary> /// Attaches the specified entity /// </summary> /// <param name="entity">Entity to attach</param> public void Attach(TEntity entity) { _objectSet.Attach(entity); } /// <summary> /// Saves all context changes /// </summary> public void SaveChanges() { _context.SaveChanges(); } /// <summary> /// Saves all context changes with the specified SaveOptions /// </summary> /// <param name="options">Options for saving the context</param> public void SaveChanges(SaveOptions options) { _context.SaveChanges(options); } /// <summary> /// Releases all resources used by the WarrantManagement.DataExtract.Dal.ReportDataBase /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Releases all resources used by the WarrantManagement.DataExtract.Dal.ReportDataBase /// </summary> /// <param name="disposing">A boolean value indicating whether or not to dispose managed resources</param> protected virtual void Dispose(bool disposing) { if (disposing) { if (_context != null) { _context.Dispose(); _context = null; } } } }
As you can see, the repository use a generic type of TEntity to represent an entity that we want to manipulate. Suppose we have an entity named Customer, when we need to perform operations on Customer entity we will do something like this:
IRepository<Customer> customerRepository = new GenericRepository<Customer>(); // Gets all customers IEnumerable<Customer> customers = customerRepository.GetAll(); // Insert a new customer Customer customer = new Customer { Firstname = "Huy", Lastname = "Nguyen" }; customerRepository.Add(customer); customerRepository.SaveChanges();
For some specific entity selection, instead of widening the contract of generic repository, the common technique is to subclass the generic repository to implement a specific repository:
public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository { public CustomerRepository(ObjectContext context) : base(context) { } public IList<Customer> NewlySubscribed() { var lastMonth = DateTime.Now.Date.AddMonths(-1); return GetQuery().Where(c => c.Inserted >= lastMonth) .ToList(); } public Customer FindByName(string firstname, string lastname) { return GetQuery().Where(c => c.Firstname == firstname && c.Lastname == lastname).FirstOrDefault(); } }
But I find it a bit inconvenient since we have to create multiple repository instances in case we want to work with multiple entities at a time. In other words, if we want to perform operations on n entities, we have to create nearly n repository instances (I am saying ‘nearly’ because if an entity is not an aggregate root, we do not implement a repository for it), like the code below:
private void AddOrders() { var context = new NorthwindEntities("connectionString"); var customerRepository = new CustomerRepository(context); IRepository<Order> orderRepository = new GenericRepository<Order>(context); IRepository<Product> productRepository = new GenericRepository<Product>(context); var c = customerRepository.FindByName("John", "Doe"); var winXP = productRepository.Single(x => x.Name == "Windows XP Professional"); var winSeven = productRepository.Single(x => x.Name == "Windows Seven Professional"); var o = new Order { OrderDate = DateTime.Now, Purchaser = c, OrderLines = new List<OrderLine> { new OrderLine { Price = 200, Product = winXP, Quantity = 1}, new OrderLine { Price = 699.99, Product = winSeven, Quantity = 5 } } }; orderRepository.Add(o); orderRepository.SaveChanges(); }
To get rid of that inconvenient, I decide to re-implement the more generic repository by making the repository’s methods generic:
public interface IRepository : IDisposable { IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class; IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class; IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class; TEntity Single<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class; TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class; void Add<TEntity>(TEntity entity) where TEntity : class; void Delete<TEntity>(TEntity entity) where TEntity : class; void Attach<TEntity>(TEntity entity) where TEntity : class; void SaveChanges(); void SaveChanges(SaveOptions options); }
/// <summary> /// A generic repository for working with data in the database /// </summary> /// <typeparam name="T">A POCO that represents an Entity Framework entity</typeparam> public class GenericRepository : IRepository { /// <summary> /// The context object for the database /// </summary> private ObjectContext _context; private readonly PluralizationService _pluralizer; /// <summary> /// Initializes a new instance of the GenericRepository class /// </summary> /// <param name="context">The Entity Framework ObjectContext</param> public GenericRepository(ObjectContext context) { _context = context; _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en")); } public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class { var entityName = GetEntityName<TEntity>(); return _context.CreateQuery<TEntity>(entityName); } public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class { return GetQuery<TEntity>().AsEnumerable(); } public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class { return GetQuery<TEntity>().Where(predicate).AsEnumerable(); } public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class { return GetQuery>TEntity>().Single<TEntity>(predicate); } public TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class { return GetQuery<TEntity>().Where(predicate).FirstOrDefault(); } public void Add<TEntity>(TEntity entity) where TEntity : class { _context.AddObject(GetEntityName<TEntity>(), entity); } public void Delete<TEntity>(TEntity entity) where TEntity : class { _context.DeleteObject(entity); } private string GetEntityName<TEntity>() where TEntity : class { return string.Format("ObjectContext.{0}", _pluralizer.Pluralize(typeof(TEntity).Name)); } // ... }
private void AddOrders() { var context = new NorthwindEntities("connectionString"); var customerRepository = new CustomerRepository(context); var repository = new GenericRepository(context); var c = customerRepository.FindByName("John", "Doe"); var winXP = repository.Single<Product>(x => x.Name == "Windows XP Professional"); var winSeven = repository.Single<Product>(x => x.Name == "Windows Seven Professional"); var o = new Order { OrderDate = DateTime.Now, Purchaser = c, OrderLines = new List<OrderLine> { new OrderLine { Price = 200, Product = winXP, Quantity = 1}, new OrderLine { Price = 699.99, Product = winSeven, Quantity = 5 } } }; repository.Add<Order>(o); repository.SaveChanges(); }
public static class ObjectContextFactory { /// <summary> /// Gets the default ObjectContext for the project /// </summary> /// <returns>The default ObjectContext for the project</returns> public static NorthwindEntities GetContext() { string connectionString = ConfigurationManager.ConnectionStrings["YourConnection"].ConnectionString; return GetContext(connectionString); } /// <summary> /// Gets the default ObjectContext for the project /// </summary> /// <param name="connectionString">Connection string to use for database queries</param> /// <returns>The default ObjectContext for the project</returns> public static NorthwindEntities GetContext(string connectionString) { return new NorthwindEntities(connectionString); } }
private void AddOrders() { var context = ObjectContextFactory.GetContext("connectionString"); var customerRepository = new CustomerRepository(context); var repository = new GenericRepository(context); var c = customerRepository.FindByName("John", "Doe"); var winXP = repository.Single<Product>(x => x.Name == "Windows XP Professional"); var winSeven = repository.Single<Product>(x => x.Name == "Windows Seven Professional"); var o = new Order { OrderDate = DateTime.Now, Purchaser = c, OrderLines = new List<OrderLine> { new OrderLine { Price = 200, Product = winXP, Quantity = 1}, new OrderLine { Price = 699.99, Product = winSeven, Quantity = 5 } } }; repository.Add<Order>(o); repository.SaveChanges(); }
public static class ObjectContextManager { public static void Init(string[] mappingAssemblies, bool recreateDatabaseIfExist = false) { Init(DefaultConnectionStringName, mappingAssemblies, recreateDatabaseIfExist); } public static void Init(string connectionStringName, string[] mappingAssemblies, bool recreateDatabaseIfExist = false) { AddConfiguration(connectionStringName, mappingAssemblies, recreateDatabaseIfExist); } public static void InitStorage(IObjectContextStorage storage) { if (storage == null) { throw new ArgumentNullException("storage"); } if ((Storage != null) && (Storage != storage)) { throw new ApplicationException("A storage mechanism has already been configured for this application"); } Storage = storage; } /// <summary> /// The default connection string name used if only one database is being communicated with. /// </summary> public static readonly string DefaultConnectionStringName = "DefaultDb"; /// <summary> /// Used to get the current object context session if you're communicating with a single database. /// When communicating with multiple databases, invoke <see cref="CurrentFor()" /> instead. /// </summary> public static ObjectContext Current { get { return CurrentFor(DefaultConnectionStringName); } } /// <summary> /// Used to get the current ObjectContext associated with a key; i.e., the key /// associated with an object context for a specific database. /// /// If you're only communicating with one database, you should call <see cref="Current" /> instead, /// although you're certainly welcome to call this if you have the key available. /// </summary> public static ObjectContext CurrentFor(string key) { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } if (Storage == null) { throw new ApplicationException("An IObjectContextStorage has not been initialized"); } if (!objectContextBuilders.ContainsKey(key)) { throw new ApplicationException("An ObjectContextBuilder does not exist with a key of " + key); } ObjectContext context = Storage.GetObjectContextForKey(key); if (context == null) { context = objectContextBuilders[key].BuildObjectContext(); Storage.SetObjectContextForKey(key, context); } return context; } /// <summary> /// This method is used by application-specific object context storage implementations /// and unit tests. Its job is to walk thru existing cached object context(s) and Close() each one. /// </summary> public static void CloseAllObjectContexts() { foreach (ObjectContext ctx in Storage.GetAllObjectContexts()) { if (ctx.Connection.State == System.Data.ConnectionState.Open) ctx.Connection.Close(); } } private static void AddConfiguration(string connectionStringName, string[] mappingAssemblies, bool recreateDatabaseIfExists = false) { if (string.IsNullOrEmpty(connectionStringName)) { throw new ArgumentNullException("connectionStringName"); } if (mappingAssemblies == null) { throw new ArgumentNullException("mappingAssemblies"); } objectContextBuilders.Add(connectionStringName, new ObjectContextBuilder<ObjectContext>(connectionStringName, mappingAssemblies, recreateDatabaseIfExists)); } /// <summary> /// An application-specific implementation of IObjectContextStorage must be setup either thru /// <see cref="InitStorage" /> or one of the <see cref="Init" /> overloads. /// </summary> private static IObjectContextStorage Storage { get; set; } /// <summary> /// Maintains a dictionary of object context builders, one per database. The key is a /// connection string name used to look up the associated database, and used to decorate respective /// repositories. If only one database is being used, this dictionary contains a single /// factory with a key of <see cref="DefaultConnectionStringName" />. /// </summary> private static Dictionary<string, IObjectContextBuilder<ObjectContext>> objectContextBuilders = new Dictionary<string, IObjectContextBuilder<ObjectContext>>(); }
public interface IObjectContextBuilder<T> where T : ObjectContext { T BuildObjectContext(bool lazyLoadingEnabled = true); } public class ObjectContextBuilder<T> : ContextBuilder<T>, IObjectContextBuilder<T> where T : ObjectContext { private readonly DbProviderFactory _factory; private readonly ConnectionStringSettings _cnStringSettings; private readonly PluralizationService _pluralizer; private readonly bool _recreateDatabaseIfExists; public ObjectContextBuilder(string connectionStringName, string[] mappingAssemblies, bool recreateDatabaseIfExists) { _cnStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; _factory = DbProviderFactories.GetFactory(_cnStringSettings.ProviderName); _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en")); _recreateDatabaseIfExists = recreateDatabaseIfExists; AddConfigurations(mappingAssemblies); } /// <summary> /// Creates a new <see cref="ObjectContext"/>. /// </summary> /// <param name="lazyLoadingEnabled">if set to <c>true</c> [lazy loading enabled].</param> /// <param name="recreateDatabaseIfExist">if set to <c>true</c> [recreate database if exist].</param> /// <returns></returns> public T BuildObjectContext(bool lazyLoadingEnabled = true) { var cn = _factory.CreateConnection(); cn.ConnectionString = _cnStringSettings.ConnectionString; var ctx = Create(cn); ctx.ContextOptions.LazyLoadingEnabled = lazyLoadingEnabled; if (!ctx.DatabaseExists()) { ctx.CreateDatabase(); } else if (_recreateDatabaseIfExists) { ctx.DeleteDatabase(); ctx.CreateDatabase(); } return ctx; } /// <summary> /// Adds mapping classes contained in provided assemblies and register entities as well /// </summary> /// <param name="mappingAssemblies"></param> private void AddConfigurations(string[] mappingAssemblies) { if (mappingAssemblies == null || mappingAssemblies.Length == 0) { throw new ArgumentNullException("mappingAssemblies", "You must specify at least one mapping assembly"); } foreach (string mappingAssembly in mappingAssemblies) { Assembly asm = Assembly.LoadFrom(MakeLoadReadyAssemblyName(mappingAssembly)); foreach (Type type in asm.GetTypes()) { if (!type.IsAbstract) { if (type.IsSubclassOf(typeof(StructuralTypeConfiguration))) { StructuralTypeConfiguration instance = Activator.CreateInstance(type) as StructuralTypeConfiguration; this.Configurations.Add(instance); Type entityType = GetEntityType(type); if (entityType != null) { RegisterEntity(entityType); } } } } } if (this.Configurations.Count == 0) { throw new ArgumentException("No mapping class found!"); } } // other code }
public interface IObjectContextBuilder<T> where T : ObjectContext { T BuildObjectContext(bool lazyLoadingEnabled = true); } public class ObjectContextBuilder<T> : ModelBuilder, IObjectContextBuilder<T> where T : ObjectContext { private readonly DbProviderFactory _factory; private readonly ConnectionStringSettings _cnStringSettings; private readonly PluralizationService _pluralizer; private readonly bool _recreateDatabaseIfExists; public ObjectContextBuilder(string connectionStringName, string[] mappingAssemblies, bool recreateDatabaseIfExists) { _cnStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; _factory = DbProviderFactories.GetFactory(_cnStringSettings.ProviderName); _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en")); _recreateDatabaseIfExists = recreateDatabaseIfExists; // we do not want the EdmMedadata table to be generated IncludeMetadataInDatabase = false; AddConfigurations(mappingAssemblies); } /// <summary> /// Creates a new <see cref="ObjectContext"/>. /// </summary> /// <param name="lazyLoadingEnabled">if set to <c>true</c> [lazy loading enabled].</param> /// <param name="recreateDatabaseIfExist">if set to <c>true</c> [recreate database if exist].</param> /// <returns></returns> public T BuildObjectContext(bool lazyLoadingEnabled = true) { var cn = _factory.CreateConnection(); cn.ConnectionString = _cnStringSettings.ConnectionString; // calls ModelBuilder.CreateModel() to create DbModel, then uses it to create ObjectContext var ctx = CreateModel().CreateObjectContext<T>(cn); ctx.ContextOptions.LazyLoadingEnabled = lazyLoadingEnabled; if (!ctx.DatabaseExists()) { ctx.CreateDatabase(); } else if (_recreateDatabaseIfExists) { ctx.DeleteDatabase(); ctx.CreateDatabase(); } return ctx; } // other code are kept the same }
/// <summary> /// Generic repository /// </summary> public class GenericRepository : IRepository { private readonly ObjectContext _context; private readonly PluralizationService _pluralizer; /// <summary> /// Initializes a new instance of the <see cref="Repository<TEntity>"/> class. /// </summary> public GenericRepository() : this(string.Empty) { } /// <summary> /// Initializes a new instance of the <see cref="Repository<TEntity>"/> class. /// </summary> /// <param name="connectionStringName">Name of the connection string.</param> public GenericRepository(string connectionStringName) { _context = GetObjectContext(connectionStringName); _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en")); } private ObjectContext GetObjectContext(string connectionStringName) { if (connectionStringName == string.Empty) return ObjectContextManager.Current; return ObjectContextManager.CurrentFor(connectionStringName); } // other code }
So far so good, here is the revisited test code:
[TestFixture] public class RepositoryTest { private ICustomerRepository customerRepository; private IRepository orderRepository; private IRepository productRepository; [TestFixtureSetUp] public void SetUp() { ObjectContextManager.InitStorage(new SimpleObjectContextStorage()); ObjectContextManager.Init("DefaultDb", new[] { "Infrastructure.Tests" }, true); customerRepository = new CustomerRepository(); orderRepository = new GenericRepository(); productRepository = new GenericRepository(); } private void AddOrders() { var c = customerRepository.FindByName("John", "Doe"); var winXP = productRepository.FindOne<Product>(x => x.Name == "Windows XP Professional"); var winSeven = productRepository.FindOne<Product>(x => x.Name == "Windows Seven Professional"); var o = new Order { OrderDate = DateTime.Now, Purchaser = c, OrderLines = new List<OrderLine> { new OrderLine { Price = 200, Product = winXP, Quantity = 1}, new OrderLine { Price = 699.99, Product = winSeven, Quantity = 5 } } }; orderRepository.Add(o); orderRepository.SaveChanges(); } }
public interface ISpecification<TEntity> { bool IsSatisfiedBy(TEntity entity); }
public interface IRepository { // other methods.. TEntity Single<TEntity>(ISpecification<TEntity> criteria) where TEntity : class IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> criteria) where TEntity : class }
public class GenericRepository : IRepository { // other code... public TEntity Single<TEntity>(ISpecification<TEntity> criteria) where TEntity : class { return GetQuery<TEntity>().Single<TEntity>(criteria.IsSatisfiedBy); } public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> criteria) where TEntity : class { return GetQuery<TEntity>().Where(criteria.IsSatisfiedBy).AsEnumerable(); } // other code... }
public class Specification<TEntity> : ISpecification<TEntity> { public Specification(Expression<Func<TEntity, bool>> predicate) { _predicate = predicate; } public bool IsSatisfiedBy(TEntity entity) { return _predicate.Compile().Invoke(entity); } private Expression<Func<TEntity, bool>> _predicate; } public class ProductByNameSpecification : Specification<Product> { public ProductByNameSpecification(string nameToMatch) : base(p => p.Name == nameToMatch) { } } public class ProductOnSaleSpecification : Specification<Product> { public ProductOnSaleSpecification() : base(p => p.Price < 100) { } }
public class Specification<TEntity> : ISpecification<TEntity> { public Specification(Expression<Func<TEntity, bool>> predicate) { _predicate = predicate; } public AndSpecification<TEntity> And(Specification<TEntity> specification) { return new AndSpecification<TEntity>(this, specification); } public OrSpecification<TEntity> Or(Specification<TEntity> specification) { return new OrSpecification<TEntity>(this, specification); } public NotSpecification<TEntity> Not(Specification<TEntity> specification) { return new NotSpecification<TEntity>(this, specification); } public bool IsSatisfiedBy(TEntity entity) { return _predicate.Compile().Invoke(entity); } private Expression<Func<TEntity, bool>> _predicate; } /// <summary> /// http://devlicio.us/blogs/jeff_perrin/archive/2006/12/13/the-specification-pattern.aspx /// </summary> /// <typeparam name="TEntity"></typeparam> public abstract class CompositeSpecification<TEntity> : ISpecification<TEntity> { protected readonly Specification<TEntity> _leftSide; protected readonly Specification<TEntity> _rightSide; public CompositeSpecification(Specification<TEntity> leftSide, Specification<TEntity> rightSide) { _leftSide = leftSide; _rightSide = rightSide; } public abstract bool IsSatisfiedBy(TEntity entity); } public class AndSpecification<TEntity> : CompositeSpecification<TEntity> { public AndSpecification(Specification<TEntity> leftSide, Specification<TEntity> rightSide) : base(leftSide, rightSide) { } public override bool IsSatisfiedBy(TEntity obj) { return _leftSide.IsSatisfiedBy(obj) && _rightSide.IsSatisfiedBy(obj); } } public class OrSpecification<TEntity> : CompositeSpecification<TEntity> { public OrSpecification(Specification<TEntity> leftSide, Specification<TEntity> rightSide) : base(leftSide, rightSide) { } public override bool IsSatisfiedBy(TEntity entity) { return _leftSide.IsSatisfiedBy(entity) || _rightSide.IsSatisfiedBy(entity); } } public class NotSpecification<TEntity> : CompositeSpecification<TEntity> { public NotSpecification(Specification<TEntity> leftSide, Specification<TEntity> rightSide) : base(leftSide, rightSide) { } public override bool IsSatisfiedBy(TEntity entity) { return _leftSide.IsSatisfiedBy(entity) && !_rightSide.IsSatisfiedBy(entity); } }
[TestFixture] public class RepositoryTest { private ICustomerRepository customerRepository; private IRepository orderRepository; private IRepository productRepository; [TestFixtureSetUp] public void SetUp() { ObjectContextManager.InitStorage(new SimpleObjectContextStorage()); ObjectContextManager.Init("DefaultDb", new[] { "Infrastructure.Tests" }, true); customerRepository = new CustomerRepository(); orderRepository = new GenericRepository(); productRepository = new GenericRepository(); } [Test] public void Test() { // setup data code.. FindBySpecification(); FindByCompositeSpecification(); FindByConcretSpecification(); FindByConcretCompositeSpecification(); } private void FindBySpecification() { Specification<Product> specification = new Specification<Product>(p => p.Price < 100); IEnumerable<Product> productsOnSale = productRepository.Find<Product>(specification); Assert.AreEqual(2, productsOnSale.Count()); } private void FindByCompositeSpecification() { IEnumerable<Product> products = productRepository.Find<Product>( new Specification<Product>(p => p.Price < 100).And(new Specification<Product>(p => p.Name == "Windows XP Professional"))); Assert.AreEqual(1, products.Count()); } private void FindByConcretSpecification() { ProductOnSaleSpecification specification = new ProductOnSaleSpecification(); IEnumerable<Product> productsOnSale = productRepository.Find<Product>(specification); Assert.AreEqual(2, productsOnSale.Count()); } private void FindByConcretCompositeSpecification() { IEnumerable<Product> products = productRepository.Find<Product>( new AndSpecification<Product>( new ProductOnSaleSpecification(), new ProductByNameSpecification("Windows XP Professional"))); Assert.AreEqual(1, products.Count()); } }
- Entity Framework POCO (EF4): Generic Repository and Unit of Work Prototype
- Generic Repository for Entity Framework
- EntityFramework 4 – Part One : Persistence Ignorance and EntityFramework 4 – Part Deux : Audit Info and Entity Framework 4 – Part Three : Repository Pattern
- Entity Framework 4 Series : Generic Repository
- Creating a Generic Entity Framework 4.0 Repository
- Composite Specification Pattern
- Addressing the pain of Entity Frameworks ObjectQuery<T>.Include()
The code for this post can be found here.