存储库模式+工作单元模式+ MVC3 + EF4 - 无法定义模型数据以处理所有事情

时间:2012-04-03 01:16:37

标签: asp.net-mvc-3 entity-framework-4 repository repository-pattern unit-of-work

我做了关于工作单元和存储库模式的复数教程。我试图将两者放在MVC和Ef4实现中。本教程有一个小型EF模型,只有2个实体。因此,匹配2个实体的对象使用ICollections定义导航属性等,就像在代码中一样,但这些对象和实体框架之间没有关系。这些是在工作单元中作为存储库聚合的对象。但这是我开始有以下问题的地方。

  1. 当您可以在实体框架中使用对象时,为什么要创建实体框架中已有对象的基本重复对象?不能吗?
  2. 如果我确实创建了这些对象......我是否必须绘制整个ef模型?拥有导航属性的ICollections如果对象不完全匹配,我会得到错误,并且它基本上继续在大多数模型中级联。该模型非常庞大,涉及十几个实体,我不想创建不会在存储库中使用的实体。
  3. 如果这些是您发送到视图的类...那么您的视图模型类是不是应该剥离实际模型类的版本?这意味着在我的控制器中,我将使用工作单元和存储库类来检索数据,然后将其剥离并仅将某些数据传递给视图模型。现在这意味着我们刚刚通过使用工作单元和存储库模式获得的所有清洁度现在开始消失了。
  4. 我有以下代码......

    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();
        }
    }
    

    然后我以下列方式声明了我的域类...也在域命名空间中。

    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; }
    }
    

    现在我只是测试这个,我有以下控制台应用程序代码......

        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();
        }
    

    但是当我运行此行时

    IRepository<Domain.Project> _repository = _unitOfWork.Projects;
    

    它到......

    _objectSet = context.CreateObjectSet<T>();
    

    我得到以下异常......

    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: 
    

    我得到了这个异常,因为那些对象尚未在我的域命名空间中创建...但我不想创建每个对象的副本。我做了一些研究,但所有的研究。但似乎我需要在某些时候转换这些对象...有没有比在控制器代码中放置一堆转换逻辑更好的方法呢?

1 个答案:

答案 0 :(得分:4)

  1. EF中定义的实体是持久性模型,即它们模拟使用什么结构保存的数据。应用程序通常有一个模型来模拟真实的业务问题和解决方案,这些都是行为。存储库接收此应用程序对象,然后从中提取存储需求所需的任何信息。大多数情况下,2个模型(应用程序和持久性)将会很多,因为简单的事情是相同的,但在很多情况下由于意图存在差异(应用程序模型意图是模型行为,持久性意图是模型存储)。

    加载保存的数据时,存储库会从其保存的表单中重新创建应用程序对象,因此它会将持久性模型映射到应用程序模型。如果模型非常简单,那么您可以直接使用EF实体,但是如果您知道将来模型会有所不同,那么从一开始就进行“转换”会更安全,这看起来就像您只是复制了实体

    此外,我们的想法是将应用程序与db访问实现细节分开,EF实体是一个实现细节。如果明天您决定EF sux和Nhibernate是更好的,或者您需要更轻松和高效的方式,那么您只需更改存储库实现而无需触及应用程序的其余部分。您提供的代码示例虽然很糟糕,因为存储库接口公开了实现细节,例如IQueryable(称为漏洞抽象)

  2. 您不必映射整个持久性模型。您只映射应用程序当前需求。如果应用程序想要返回Foo对象,则可以使用Ef获取所有必需的Foo数据(它可以跨越多个表,也可以复杂查询)并将其映射到Foo。如果Bar对象非常简单并且几乎是1到1的EF实体,那么您只需将实体复制到对象中(以便以后可以很容易地从EF切换到其他orm)

  3. 他们不是。这些是持久性类而不是View模型类。大多数EF教程的工作非常糟糕,让您认为您直接使用EF实体作为您的模型。 View Model是一个迎合View需求的独立模型。它是通过持久性模型构建的。存储库(用于查询的不同的,特殊的repo)将查询产生的EF实体直接映射到视图模型的位。再一次,如果你切换到另一个Orm,你不必为视图模型改变任何东西。

  4. 如您所见,存储库做了很多工作,并且对维护正确分层的应用程序负有重要责任。但EF是存储库的实现细节,在为应用程序或视图设计模型时,您应该忘记EF或db结构,并且只知道存储库接口(根据应用程序需求设计)。