Abstract repository for direct EF, WCF and WCF Data Services connection with database.
By Mirek on (tags: Code First, Entity Framework, WCF Data Services, categories: architecture, code)In this article I would like to show you my implementation and approach for using abstract repository no matter if the client is connecting directly to the database using Entity Framework, over WCF service or WCF Data Services (WCFDS) service.
1. General explicit repository
The goal here is to have common interface of the repository so the client application does not care about where data comes from. It is configurable and automatically set how data is transmitted from database to the client. It can be done by direct usage of Entity Framework context, either using some explicit WCF service reference or using an OData service exposed over EF database context.
Let’s see our SimpleModel POCO class
1: [DataContract]
2: [DataServiceKey("Id")]
3: public class SimpleModel
4: {
5: [DataMember]
6: public int Id { get; set; }
7:
8: [DataMember]
9: public DateTime CreatedOn { get; set; }
10:
11: [DataMember]
12: public double FloatNumber { get; set; }
13:
14: [DataMember]
15: public string Name { get; set; }
16:
17: [DataMember]
18: public IList<SubModel> SubElements { get; set; }
19: }
20:
21: [DataContract]
22: [DataServiceKey("Id")]
23: public class SubModel
24: {
25: [DataMember]
26: public int Id { get; set; }
27:
28: [DataMember]
29: public DateTime CreatedOn { get; set; }
30:
31: [DataMember]
32: public string Name { get; set; }
33: }
As you can see this simple classes implements are marked as data contracts to be used in WCF service. Attribute DataServiceKey is responsible of informing WCFDS engine that this class is an entity and has an unique entity key.
We will have an generic repository which will be base for all repositories
1: public interface IRepository<TEntity, TKey> where TEntity : class
2: {
3: TEntity GetById(TKey Id);
4:
5: IList<TEntity> GetAll();
6:
7: IList<TEntity> GetAllPaged(int pageNumber, int pageSize);
8:
9: void AddNew(params TEntity[] newEntities);
10:
11: void Update(params TEntity[] Entities);
12:
13: void Delete(params TEntity[] Entities);
14: }
Then the repository for SimpleModel is definied by interface
1: public interface ISimpleModelRepository : IRepository<SimpleModel,int>
2: {
3: IList<SimpleModel> GetAllByName(string Name);
4: }
Which adds one extra method for our repository methods.
Ok we have repository interfaces, so we now how our repository looks like. We need an implementation of the repository. Let’s start with the simplest implementation using direct Entity Framework database context
1: public class SimpleModelDbContext : DbContext
2: {
3: static SimpleModelDbContext()
4: {
5: Database.DefaultConnectionFactory = new SqlCeConnectionFactory(“System.Data.SqlServerCe.4.0”);
6: Database.SetInitializer(new SimpleModelDbInitializer());
7: }
8:
9: public SimpleModelDbContext()
10: :base(“SimpleModelDb”)
11: {
12:
13: }
14:
15: public DbSet<SimpleModel> SimpleModels { get; set; }
16:
17: public DbSet<SubModel> SubModels { get; set; }
18: }
SimpleModelInitializer seeds the database with some random data. Then the repository for SimpleModel using this EF context looks following:
1: public class SimpleModelRepoEF : ISimpleModelRepository
2: {
3: //Entity Framework context; default initialized in SimpleModelDbContext class
4: private SimpleModelDbContext dbctx = new SimpleModelDbContext();
5:
6: public IList<SimpleModel> GetAllByName(string Name)
7: {
8: return dbctx.SimpleModels.Where(sm => String.Equals(sm.Name, Name, StringComparison.Ordinal)).ToList();
9: }
10:
11: public SimpleModel GetById(int Id)
12: {
13: return dbctx.SimpleModels.Where(sm => sm.Id == Id).FirstOrDefault();
14: }
15:
16: public IList<SimpleModel> GetAll()
17: {
18: return dbctx.SimpleModels.ToList();
19: }
20:
21: public void AddNew(params SimpleModel[] newEntities)
22: {
23: foreach (var ne in newEntities)
24: {
25: dbctx.SimpleModels.Add(ne);
26: }
27: dbctx.SaveChanges();
28: }
29:
30: public void Update(params SimpleModel[] Entities)
31: {
32: foreach (var ne in Entities)
33: {
34: dbctx.SimpleModels.Attach(ne);
35: dbctx.Entry<SimpleModel>(ne).State = System.Data.EntityState.Modified;
36: }
37: dbctx.SaveChanges();
38: }
39:
40: public void Delete(params SimpleModel[] Entities)
41: {
42: foreach (var ent in Entities)
43: {
44: dbctx.SimpleModels.Remove(ent);
45: }
46: dbctx.SaveChanges();
47: }
48:
49:
50: public IList<SimpleModel> GetAllPaged(int pageNumber, int pageSize)
51: {
52: return dbctx.SimpleModels.Skip((pageNumber – 1) * pageSize).Take(pageSize).ToList();
53: }
54: }
Ok, one case is ready, our client application may use this repository so it will be using direct access to the database. The Entity Framework can be configured to use different database types and custom connection strings.
To use our repository over internet we need to expose it as a service.
Standard WCF service:
1: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
2: public class WcfService : IWcfService
3: {
4: private SimpleModelRepoEF repo = new SimpleModelRepoEF();
5:
6: public SimpleModel GetById(int Id)
7: {
8: return repo.GetById(Id);
9: }
10:
11: public IList<SimpleModel> GetAll()
12: {
13: return repo.GetAll();
14: }
15:
16: public IList<SimpleModel> GetAllByName(string Name)
17: {
18: return repo.GetAllByName(Name);
19: }
20:
21: public void AddNew(SimpleModel[] newEntities)
22: {
23: repo.AddNew(newEntities);
24: }
25:
26: public void Update(SimpleModel[] Entities)
27: {
28: repo.Update(Entities);
29: }
30:
31: public void Delete(SimpleModel[] Entities)
32: {
33: repo.Delete(Entities);
34: }
35:
36:
37: public IList<SimpleModel> GetAllPaged(int pageNumber, int pageSize)
38: {
39: return repo.GetAllPaged(pageNumber, pageSize);
40: }
41: }
As you can see it uses SimpleModelRepoEF as soon as it connects to the database directly. The shape of methods exposed by this service is adopted to be convenient for future use.
On the client side we have to add a reference to the service and implement appropriate repository for our SimpleModel
1: public class SimpleModelRepoWCF : ISimpleModelRepository
2: {
3: private WcfServiceClient client = new WcfServiceClient();
4:
5: public IList<SimpleModel> GetAllByName(string Name)
6: {
7: return client.GetAllByName(Name);
8: }
9:
10: public SimpleModel GetById(int Id)
11: {
12: return client.GetById(Id);
13: }
14:
15: public IList<SimpleModel> GetAll()
16: {
17: return client.GetAll();
18: }
19:
20: public void AddNew(params SimpleModel[] newEntities)
21: {
22: client.AddNew(newEntities);
23: }
24:
25: public void Update(params SimpleModel[] Entities)
26: {
27: client.Update(Entities);
28: }
29:
30: public void Delete(params SimpleModel[] Entities)
31: {
32: client.Delete(Entities);
33: }
34:
35: public IList<SimpleModel> GetAllPaged(int pageNumber, int pageSize)
36: {
37: return client.GetAllPaged(pageNumber, pageSize);
38: }
39: }
As you can see thanks to the specific shape of service methods this repository is simple one to one wrapper over the service proxy client.
Let’s now expose WCF Data Services (OData) service
1: [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
2: public class WcfDataService : DataService<SimpleModelDbContext>
3: {
4: public static void InitializeService(DataServiceConfiguration config)
5: {
6: config.SetEntitySetAccessRule(“*”, EntitySetRights.All);
7: config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
8: }
9: }
Ok that is easy, create data service over DbContext and allow all entities to be queried, updated and deleted. Now we normally add a service reference in our client application as we did in case of standard WCF service. Unfortunately current version of WCF DS has limitation which does not allow to reuse shared types in service reference. Why is it important?
Well let’s imagine the case when we use direct EF connection and we want to switch to using WCFDS. In case of direct EF we are using our SimpleModel directly no matter if it is kept locally or in some shared assembly. After switch to WCFDS we want to use the same POCO instead of refactoring all instances of SimpleModel to those which are generated in WCFDS reference proxy.
In standard WCF service we can reuse our models from shared assembly, so the same type is used in our service, where it is running, and in our client no matter if it connects directly or uses WCF. In case of WCFDS we have to use DataServiceContext instead of auto generated proxy.
1: public class SimpleModelRepoWCFDS : ISimpleModelRepository
2: {
3: //Create data service context providing its URL
4: private DataServiceContext client = new DataServiceContext(new Uri(ConfigurationManager.AppSettings["WCFDSServiceUri"]));
5:
6: public IList<SimpleModel> GetAllByName(string Name)
7: {
8: return client.CreateQuery<SimpleModel>(“SimpleModels”).Where(sm => sm.Name.Equals(Name)).ToList();
9: }
10:
11: public SimpleModel GetById(int Id)
12: {
13: return client.CreateQuery<SimpleModel>(“SimpleModels”).Where(sm => sm.Id.Equals(Id)).FirstOrDefault();
14: }
15:
16: public IList<SimpleModel> GetAll()
17: {
18: return client.CreateQuery<SimpleModel>(“SimpleModels”).ToList();
19: }
20:
21: public void AddNew(params SimpleModel[] newEntities)
22: {
23: foreach (var ne in newEntities)
24: {
25: client.AddObject(“SimpleModels”,ne);
26: }
27: client.SaveChanges();
28: }
29:
30: public void Update(params SimpleModel[] Entities)
31: {
32: foreach (var ce in Entities)
33: {
34: client.UpdateObject(ce);
35: }
36: client.SaveChanges();
37: }
38:
39: public void Delete(params SimpleModel[] Entities)
40: {
41: foreach (var delent in Entities)
42: {
43: client.DeleteObject(delent);
44: }
45: client.SaveChanges();
46: }
47:
48: public IList<SimpleModel> GetAllPaged(int pageNumber, int pageSize)
49: {
50: return client.CreateQuery<SimpleModel>(“SimpleModels”).Skip((pageNumber – 1) * pageSize).Take(pageSize).ToList();
51: }
52: }
So here SimpleModel is referenced from the shared library and represents the same type as this used in direct EF connection. In line 4 we just get the stored WCFDS service url from application config file.
Ok we now have three different implementation of our SimpleModel repository which implements the same interface.
On the client side we can simply use
1: ModelRepository repo
and assign to it an instance of any of our implementations
1: ISimpleModelRepository repo = new SimpleModelRepoEF();
2: ISimpleModelRepository repo = new SimpleModelRepoWCF();
3: ISimpleModelRepository repo = new SimpleModelRepoWCFDS();
To make the things more comfortable we would like to change the way our program connects to the database without changing the code and recompiling the application. Let’s see how Autofac IoC container may help here.
Following code initializes Autofac and registers all neccesary types
1: var builder = new ContainerBuilder();
2: builder.RegisterModule(new ConfigurationSettingsReader(“autofac”));
3: var container = builder.Build();
The kind of repository is stored in app.config file where can be easily modified
1: <configSections>
2: <section name=“autofac“ type=“Autofac.Configuration.SectionHandler, Autofac.Configuration“/>
3: </configSections>
4:
5: <autofac defaultAssembly=“eidias.playground.dal.core“>
6: <components>
7:
8: <!–Repository for direct database access–>
9: <!–<component type=”eidias.playground.dal.core.Repositories.SimpleModelRepoEF, eidias.playground.dal.core”
10: service=”eidias.playground.dal.core.Interfaces.ISimpleModelRepository” />–>
11:
12: <!–Repository for access via wcf–>
13: <!–<component type=”eidias.playground.dal.console.Repositories.SimpleModelRepoWCF, eidias.playground.dal.console”
14: service=”eidias.playground.dal.core.Interfaces.ISimpleModelRepository”/>–>
15:
16: <!–Repository for access via wcf data services–>
17: <component type=“eidias.playground.dal.console.Repositories.SimpleModelRepoWCFDS, eidias.playground.dal.console”
18: service=“eidias.playground.dal.core.Interfaces.ISimpleModelRepository“ />
19:
20: </components>
21: </autofac>
To change the repository to different just uncomment proper line and comment another. In the code the creation of the repository is performed by following Autofac method
1: ISimpleModelRepository repo = container.Resolve<ISimpleModelRepository>();
Thats' it. Now we can easily change between direct database connection, connection using WCF Data Services and connection using standard WCF.
2. Generic query able repository
We could have the repository interface more generic if we resign of standard WCF way. Since direct EF context and OData context supports IQueryable calls including all benefits of using Linq (Where(), Skip(), Single,…) we can not have such functionality in explicit WCF interface. There is a couple of projects on the web which brings some kind of similar functionality, for ex. expression trees serialization, so it can be send as a parameter of the WCF method, but are still in development and usually adds a measurable performance overhead.
The way we can go is to resign of the standard WCF services and use only direct EF and OData service. Then we can have the generic repository which looks similar to this
1: public interface IQueryableRepository<T> : IQueryable<T> where T : class
2: {
3: void AddNew(T entity);
4:
5: void Update(T entity);
6:
7: void Delete(T entity);
8:
9: void SaveChanges();
10: }
so we can then use
1: repo.Where(sm => sm.Name.Equals(“someModel”)).SingleOrDefault();
The implementation for WCF DS connection can now look like this
1: public class SimpleModelQueryableRepoWCFDS : IQueryableRepository<SimpleModel>
2: {
3: private DataServiceContext client = new DataServiceContext(new Uri(ConfigurationManager.AppSettings["WCFDSServiceUri"]));
4:
5: private DataServiceQuery<SimpleModel> simpleModels;
6:
7: public DataServiceQuery<SimpleModel> SimpleModels
8: {
9: get
10: {
11: if (simpleModels == null)
12: {
13: simpleModels = client.CreateQuery<SimpleModel>(“SimpleModels”);
14: }
15: return simpleModels;
16: }
17: }
18:
19: public void AddNew(SimpleModel entity)
20: {
21: client.AddObject(“SimpleModels”, entity);
22: }
23:
24: public void Update(SimpleModel entity)
25: {
26: client.UpdateObject(entity);
27: }
28:
29: public void Delete(SimpleModel entity)
30: {
31: client.DeleteObject(entity);
32: }
33:
34: public void SaveChanges()
35: {
36: client.SaveChanges();
37: }
38:
39: public IEnumerator<SimpleModel> GetEnumerator()
40: {
41: return SimpleModels.GetEnumerator();
42: }
43:
44: System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
45: {
46: return SimpleModels.GetEnumerator();
47: }
48:
49: public Type ElementType
50: {
51: get { return typeof(SimpleModel); }
52: }
53:
54: public System.Linq.Expressions.Expression Expression
55: {
56: get { return SimpleModels.Expression; }
57: }
58:
59: public IQueryProvider Provider
60: {
61: get { return SimpleModels.Provider; }
62: }
63: }
In line 7 here is defined and DataServiceQuery which is then used as a source of an IQuerable interface. Some could say why we can not have this repository still generic for all types of entity. Well the answer is simple, because we have to provide the name of the entityCollection property which indicates the name of the IDbSet on backend data source (here “SimpleModels”). One solution for that could be making the assumption which says that each entity is represented by property named by entity’s name followed by ‘s’. Then we can create a generic repository which will extract the entity name, adds ‘s’ and uses that to query WCF DS.
In case of direct entity framework situation is simpler
1:
2:
3: public class GenericQueryableRepoEF<T> : IQueryableRepository<T> where T : class
4: {
5: private DbContext dbctx;
6:
7: public GenericQueryableRepoEF(DbContext dbContext)
8: {
9: dbctx = dbContext;
10: }
11:
12: public void AddNew(T entity)
13: {
14: dbctx.Set<T>().Add(entity);
15: }
16:
17: public void Update(T entity)
18: {
19: dbctx.Set<T>().Attach(entity);
20: dbctx.Entry<T>(entity).State = System.Data.EntityState.Modified;
21: }
22:
23: public void Delete(T entity)
24: {
25: dbctx.Set<T>().Remove(entity);
26: }
27:
28: public void SaveChanges()
29: {
30: dbctx.SaveChanges();
31: }
32:
33: public IEnumerator<T> GetEnumerator()
34: {
35: return ((IDbSet<T>)dbctx.Set<T>()).GetEnumerator();
36: }
37:
38: System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
39: {
40: return GetEnumerator();
41: }
42:
43: public Type ElementType
44: {
45: get { return typeof(T); }
46: }
47:
48: public System.Linq.Expressions.Expression Expression
49: {
50: get { return ((IDbSet<T>)dbctx.Set<T>()).Expression; }
51: }
52:
53: public IQueryProvider Provider
54: {
55: get { return ((IDbSet<T>)dbctx.Set<T>()).Provider; }
56: }
57: }
Repository is still generic (not only interface) so we do not need to create separate implementation for each entity type.
augustapop
2/3/2015 1:07 PM
you have a link to download this codeken
9/23/2015 9:59 PM
Do you have the code in this example for a download - it is easier to view the separation of the classes and interactivity in the IDE, so it would be a nice to have - even if it was not a "project" file - simply the classes would be a nice thing to see ..ken
9/23/2015 10:33 PM
IWcfService is missing from the example..Ken
1/14/2016 10:16 PM
I am not sure if you are keeping up with this or not; Do I need to create a WCF service for each entity in the Database in order to use this for all of my entities ? So instead of SimpleModel, I have for example Customers, Products, Orders , States So for each of these Entities I would need a separate WCF service (or endpoint maybe?) ? If so is there a way around that? Example ...Ken
1/26/2016 3:44 PM
The WCF service here looks like it is PER ENTITY.. is this the case ?kenar
2/23/2017 11:17 AM
Hi. I have followed your example and also added autofac, but I have an error The service 'WebCode.Service.WCF.TablasMaestrasService, WebCode.Service.WCF' configured for WCF is not registered with the Autofac container.Drdos
9/21/2018 7:39 AM
thanks a lot Can you send me download link