I did the pluralsight tutorials on the unit of work and repository patterns. I’m trying to put the two together in an MVC and Ef4 implementation. The tutorial has a small EF model, only 2 entities. So objects matching the 2 entities were defined with ICollections for navigation properties etc just like in code first but there was no tie between those objects and the entity framework. These are the objects that were aggregated as repositories in the unit of work. But this is where I start to have the following questions.
- Why create basically duplicate objects of the objects already in the entity framework when you can use the objects in the entity framework? Can’t you?
- If I do create these objects … do I have to map out the entire ef model? Having the ICollections for the navigation properties I get errors if the objects don’t match exactly and it basically continues to cascade throughout most of the model. The model is quite large involving over a dozen entities and I don’t want to have to create entities that aren’t going to be used in the repository.
- If these are the classes that you send to your views … aren’t your view model classes supposed to be stripped down versions of your actual model classes? This would mean that in my controller I would using the unit of work and repository classes to retrieve data and then also stripping them down and only passing certain pieces of data to the view model. Which now means that all that cleanliness which we just gained by using the unit of work and repository patterns is now starting disappear again.
I have the following code …
namespace Data
{
public interface IRepository<T>
where T : class//, IEntity
{
IQueryable<T> FindAll();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
//T FindById(int id);
void Add(T newEntity);
void Remove(T entity);
}
public class SqlRepository<T> : IRepository<T>
where T : class//, IEntity
{
public SqlRepository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
{
return _objectSet.Where(predicate);
}
public void Add(T newEntity)
{
_objectSet.AddObject(newEntity);
}
public void Remove(T entity)
{
_objectSet.DeleteObject(entity);
}
public IQueryable<T> FindAll()
{
return _objectSet;
}
protected ObjectSet<T> _objectSet;
}
}
public class SqlUnitOfWork : IUnitOfWork
{
public SqlUnitOfWork()
{
var connectionString =
ConfigurationManager
.ConnectionStrings[ConnectionStringName]
.ConnectionString;
_context = new ObjectContext(connectionString);
_context.ContextOptions.LazyLoadingEnabled = true;
}
public IRepository<Domain.Project> Projects
{
get
{
if (_projects == null)
{
_projects = new SqlRepository<Domain.Project>(_context);
}
return _projects;
}
}
public void Commit()
{
_context.SaveChanges();
}
SqlRepository<Domain.Project> _projects = null;
//SqlRepository<TimeCard> _timeCards = null;
readonly ObjectContext _context;
const string ConnectionStringName = "Entities";
}
}
namespace Domain
{
public interface IRepository<T>
where T : class//, IEntity
{
IQueryable<T> FindAll();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
//T FindById(int id);
void Add(T newEntity);
void Remove(T entity);
}
public interface IUnitOfWork
{
IRepository<Project> Projects { get; }
//IRepository<TimeCard> TimeCards { get; }
void Commit();
}
}
and then I’ve declared my domain classes in the following fashion … also in the domain namespace.
public class Project //: IEntity
{
public virtual int ProjectId { get; set; }
public virtual int UserId { get; set; }
public virtual int CategoryId { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
// nav prop - do I have to have these?
public virtual ICollection<Image> Images { get; set; }
public virtual ICollection<WatchedProject> WatchedProjects { get; set; }
public virtual ICollection<RequestForProposal> RequestForProposals { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual Category Category { get; set; }
}
Right now I’m just testing this and I have the following console application code …
static void Main(string[] args)
{
IUnitOfWork _unitOfWork = new SqlUnitOfWork();
IRepository<Domain.Project> _repository = _unitOfWork.Projects;
var prjs = _unitOfWork.Projects.FindAll( );
foreach (Domain.Project prj in prjs)
{
Console.WriteLine(prj.Description);
}
Console.Read();
}
but when I run this line
IRepository<Domain.Project> _repository = _unitOfWork.Projects;
its to …
_objectSet = context.CreateObjectSet<T>();
and I get the following exception …
System.Data.MetadataException was unhandled
Message=Schema specified is not valid. Errors:
The relationship 'Model.fk_projects_catetegoryid_categories' was not loaded because the type 'Model.Category' is not available.
The following information may be useful in resolving the previous error:
The required property 'ServiceProviderCategories' does not exist on the type 'Domain.Category'.
The relationship 'Model.FK_Messages_Projects_ProjectId' was not loaded because the type 'Model.Message' is not available.
The following information may be useful in resolving the previous error:
The required property 'MessageId' does not exist on the type 'Domain.Message'.
The relationship 'Model.fk_requestforproposals_projectid_projects' was not loaded because the type 'Model.RequestForProposal' is not available.
The following information may be useful in resolving the previous error:
The required property 'Project' does not exist on the type 'Domain.RequestForProposal'.
The relationship 'Model.FK_WatchedProject_ProjectId_Projects' was not loaded because the type 'Model.WatchedProject' is not available.
The following information may be useful in resolving the previous error:
The required property 'WatchedProjectId' does not exist on the type 'Domain.WatchedProject'.
The relationship 'Model.ProjectImage' was not loaded because the type 'Model.Image' is not available.
The following information may be useful in resolving the previous error:
The required property 'ImageId' does not exist on the type 'Domain.Image'.
Source=System.Data.Entity
StackTrace:
at System.Data.Metadata.Edm.ObjectItemCollection.LoadAssemblyFromCache(ObjectItemCollection objectItemCollection, Assembly assembly, Boolean loadReferencedAssemblies, EdmItemCollection edmItemCollection, Action`1 logLoadMessage)
at System.Data.Metadata.Edm.ObjectItemCollection.ImplicitLoadAssemblyForType(Type type, EdmItemCollection edmItemCollection)
at System.Data.Metadata.Edm.MetadataWorkspace.ImplicitLoadAssemblyForType(Type type, Assembly callingAssembly)
at System.Data.Objects.ObjectContext.GetTypeUsage(Type entityCLRType)
at System.Data.Objects.ObjectContext.GetEntitySetFromContainer(EntityContainer container, Type entityCLRType, String exceptionParameterName)
at System.Data.Objects.ObjectContext.GetEntitySetForType(Type entityCLRType, String exceptionParameterName)
at System.Data.Objects.ObjectContext.CreateObjectSet[TEntity]()
at Data.SqlRepository`1..ctor(ObjectContext context) in C:\Projects\TestProjectClasses\Data\SqlRepository.cs:line 18
at Data.SqlUnitOfWork.get_Projects() in C:\Projects\TestProjectClasses\Data\SqlUnitOfWork.cs:line 31
at TestProjectClasses.Program.Main(String[] args) in C:\Projects\TestProjectClasses\TestProjectClasses\Program.cs:line 26
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
I get this exception because those objects haven’t been created in my domain namespace … but I don’t want to create copies of every object. I have done some research but all the research. But it seems that I need to convert these objects at some point … is there a better way to do it than putting a bunch of conversion logic in your controller code?
The entities defined in EF are Persistence Models, that is they model what data is saved with what structure. The application usually has a model that models the real business problems and solutions, which are behaviours. The repository receives this app objects and then extract from them any info required for storage needs. Most of the time the 2 models (app and persistence) will resemble a lot , for simple things are identical but in many cases there are differences because of the intent (app model intent is to model behavior, persistence intent is to model storage).
When loading saved data, the repository recreates the app objects from their saved form, so it maps a persistence model to an app model. If the model is very simple then you could use directly the EF entities, but if you know that in the future the models will be different, it’s safer to do the ‘conversion’ from the beginning, which will look like you just duplicate the entities.
Also, the idea is to separate the app from db access implementation details and EF entity are an implementation detail. If tomorrow you decide that EF sux and Nhibernate is beter or you need a much lighter and performant way, you change only the repository implementation without needing to touch the rest of the app. The code example you provided does a poor job though, because the repository interface exposes implementation details such as IQueryable (that’s called a leaky abstraction)
You don’t have to map the entire persistence model. You map only for the app current needs. If the app want a Foo object back, you get all the required Foo data with Ef (it can span multiple tables and maybe complex queries) and map that to Foo. If the Bar object is very simple and almost 1 to 1 to an EF entity, you just copy the entity into the object (so that later would be easy to switch from EF to other orm)
They aren’t. These are persistence classes not View model classes. Most of EF tutorials doo a very poor job making you think that you’re using directly the EF entities as your everywhere model. The View Model is a separate model catering to the View needs. It is constructed though from the persistence model. The repository (a different, specializzed repo for queries) will map the EF entities resulted from a query directly to bits of the View Model. Once again, if you switch to a different Orm, you don’t have to change anything for the view model.
As you see the repository does a lot and has an important responsability to maintain a properly layered application. But EF is an implementation detail of the repository and when designing the model for the app or the views, you should forget about EF or db structure and know only about the repository interface (which is designed according to the app needs).