Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid the need to reference Entity Framework on my service layer?

While trying to create a data access layer for a new project, I ran into what I can only imagine as an OOP/Design/Generics problem (using EF 4.3 to access the database).

Mainly I wanted to achieve two things with this data layer:

  • Different Context objects I have on my project should share the same connection string.
  • Abstract repository class with common implementation.

For some reason, I cannot compile my solution without referencing EntityFramework on the service layer. What I'm looking for is a way to fix this. Here's what I have:

//Project/Namespace BusinessLogicLayer.DomainClasses
//POCO classes mapped on Entity Framework.

//Project/Namespace DataAccessLayer.Base
//Base classes and interfaces for all data access layer, such as:

public abstract class BaseContext<TContext> : DbContext where TContext : DbContext
{
  //To allow multiple contexts sharing the same connection string
  protected BaseContext(): base("name=MyConnectionString") {}
}

//Generic interface for a read-only repository
public interface IReadOnlyRepository<T> : IDisposable where T : class

//Generic interface for a read/write repository
public interface IRepository<T> : IReadOnlyRepository<T> where T : class

//Basic implementation for a read-only repository
public abstract class BaseReadOnlyRepository<C, T> : IReadOnlyRepository<T>
    where T : class
    where C : BaseContext<C>, new()
{
}

//Basic implementation for a read/write repository
public abstract class BaseRepository<C, T> : IRepository<T>
    where T : class
    where C : BaseContext<C>, new()
{
}

//Project DataAccessLayer.AccountContext/ Namespace DataAccessLayer
//Context class:

public class AccountContext : BaseContext<AccountContext> {}

//With this, I can have simple repositories:

public class UserRepository : BaseRepository<AccountContext, User>
{ //All implementation comes from the base abstract class, unless I need to change it (override methods)
}

I have a service layer between data access and the application (Windows Forms). Because I have a generic repository, it seemed a good and logic idea to have generic services. In the end, very similar to the repository structure:

//Project/Namespace BusinessLogicLayer.Services
//Service layer supposed to reference only the repository project and not Entity Framework.

//Generic interface for a read-only service working with a read-only repository
public interface IReadOnlyService<T> where T : class {}

//Generic interface for a read/write service working with a read/write repository
public interface IService<T> : IReadOnlyService<T> where T : class

//Base implementation for a read-only service
public abstract class BaseReadOnlyService<T, R> : IReadOnlyService<T>
    where T : class
    where R : IReadOnlyRepository<T>, new()
{
}

//Base implementation for a read/write service
public abstract class BaseService<T, R> : IService<T>
    where T : class
    where R : IRepository<T>, new()
{
}

//Concrete sample service
public class UserService : BaseService<User, UserRepository>
{ //As with the repository I can change the default behavior of implementation overriding methods
}

With this setup, the only way to compile is to reference Entity Framework on the service layer project. How can I avoid the need to reference Entity Framework there?

At this point, I'm willing to even throw it all out and rebuild everything but this is the only way I found to make it work given my needs (DbContext sharing connection strings, generic repository to avoid replication of code).

Appreciate any help. Thanks.

--Edit--Including here some extra steps I did 3 hours after I posted the question--

In order to figure this out, I started to create a sample project with the same code above plus some implementation to mimic the results on the original project as much as possible.

I created the domain classes project, the whole base data layer project and then the context project. I noticed I need to reference Entity Framework on the context project even though the context class does not derive directly from DbContext. Instead, it derives from an abstract class that derives from DbContext. This is ok though since my context will have DbSets and any other implementation related to DbContext.

Next is the repository project. Needs to reference all other three (domain, base data layer and context). My repository has no code. All funcionality lies on the ancestor. I try to compile the repository project and VS requires me to referente Entity Framework. I wonder if it's really just a matter of embedding libraries. If this is confirmed, it's going to be a surprise. Entity Framework library is present on the output of the other projects. Why would I need to reference it here too? What's making VS require this?

Anyway, for testing purposes, I added the reference. After all, I'm inside the data layer. I can live with that. Moving on to the service layer. For the sake of simplicity, I put all of the service classes into the same project.

One possible flaw is that one of the constraints for the service abstract classes is the repository interface. That requires me to add a reference to the base data layer on my service layer. Perhaps already here there is something I can do that allows me to use only the repository reference. I have no option but to reference base data layer.

Lastly, my concrete service is created and VS gives me the following error message: The type 'System.Data.Entity.DbContext' is defined in an assembly that is not referenced. You must add a reference to assembly 'EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

So, in the end, the only way to keep going is to reference Entity Framework on the service layer. And at some point, when building the Windows Forms App, I will also have to reference Entity Framework.

What should I do to avoid having these references? What improvements can I have on this structure?

What I know is that my app certainly doesn't have to know that Entity Framework is involved anywhere on the other layers. Neither does the service layer. Services will only consume repositories. Repositories can even be providing fake data for tests.

In case anyone is interested, I uploaded the project I created while writing this. It's a 1,17Mb zip file with no binaries whatsoever (except the Entity Framework 4.3.1 dll that I got through Nuget). Link: http://www.mediafire.com/?b45zkedy2j7eocc.

Again, thanks for helping.

like image 860
Rodrigo Lira Avatar asked Nov 07 '12 22:11

Rodrigo Lira


1 Answers

Instead of having abstract BaseContext in your BusinessLogicLayer, declare an interface. Then implement it in your Data Access Layer.

public interface IDataContext : IDisposable
{
   int SaveChanges();
}

//Generic interface for a read-only repository
public interface IReadOnlyRepository<T> : IDisposable where T : class

//Generic interface for a read/write repository
public interface IRepository<T> : IReadOnlyRepository<T> where T : class

//Basic implementation for a read-only repository
public abstract class BaseReadOnlyRepository<C, T> : IReadOnlyRepository<T>
    where T : class
    where C : IDataContext
{
}

//Basic implementation for a read/write repository
public abstract class BaseRepository<C, T> : IRepository<T>
    where T : class
    where C : IDataContext
{
}


public interfaces IAccountContext : IDataContext
{
   //other methods
}

Then in the data access layer

public abstract class BaseContext : DbContext, IDataContext
{
  //To allow multiple contexts sharing the same connection string
  protected BaseContext(): base("name=MyConnectionString") {}
}

public class AccountContext : BaseContext, IAccountContext {}

//With this, I can have simple repositories:

public class UserRepository : BaseRepository<AccountContext, User>
{ //All implementation comes from the base abstract class, unless I need to change it (override methods)
}

You can use DI/Ioc to inject the Context and the Repository to the services instead of instantiating the context inside the repository.

This decoupling will remove the need for referencing EF assembly in your business logic layer but remember that your domain entities are not completely independent of EF. For example navigational properties, relationship fix-ups will not work outside EF context. So in a way you are actually hiding a dependency!!

like image 126
Eranga Avatar answered Nov 14 '22 14:11

Eranga