asp.net-mvc-3 - 存储库模式 + 工作单元模式 + MVC3 + EF4 - 无法定义模型数据以处理所有内容

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

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

  1. 当您可以使用 Entity Framework 中的对象时,为什么要创建与 Entity Framework 中已有对象基本相同的对象?你不能吗?
  2. 如果我确实创建了这些对象……我是否必须绘制出整个 ef 模型?拥有用于导航属性的 ICollections,如果对象不完全匹配,我会收到错误,并且它基本上会继续级联整个模型的大部分。该模型非常大,涉及十几个实体,我不想创建不会在存储库中使用的实体。
  3. 如果这些是您发送给 View 的类……难道您的 View 模型类不应该是实际模型类的精简版本吗?这意味着在我的 Controller 中,我将使用工作单元和存储库类来检索数据,然后将它们剥离并仅将某些数据片段传递给 View 模型。现在这意味着我们刚刚通过使用工作单元和存储库模式获得的所有清洁度现在又开始消失了。

我有以下代码...

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: 

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

最佳答案

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

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

    此外,这个想法是将应用程序与数据库访问实现细节分开,而 EF 实体是一个实现细节。如果明天您决定 EF sux 和 Nhibernate 更好,或者您需要一种更轻便和更高效的方式,您只需更改存储库实现,而无需触及应用程序的其余部分。不过,您提供的代码示例效果不佳,因为存储库接口(interface)公开了实现细节,例如 IQueryable(称为泄漏抽象)

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

  3. 他们不是。这些是持久性类而不是 View 模型类。大多数 EF 教程做得很差,让您认为您直接使用 EF 实体作为您的无处不在的模型。 View Model 是一个单独的模型,可以满足 View 的需求。它是从持久性模型构建的。存储库(一个不同的、专门用于查询的存储库)会将查询结果的 EF 实体直接映射到 View 模型的位。再次重申,如果您切换到不同的 Orm,则无需更改 View 模型的任何内容。

如您所见,存储库做很多事情,并且负有维护适当分层应用程序的重要责任。但是 EF 是存储库的实现细节,在为应用程序或 View 设计模型时,您应该忘记 EF 或数据库结构,而只知道存储库接口(interface)(根据应用程序的需要设计)。

关于asp.net-mvc-3 - 存储库模式 + 工作单元模式 + MVC3 + EF4 - 无法定义模型数据以处理所有内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9985929/

相关文章:

asp.net - MVC 3 System.Web.Optimization bundle 单个文件

asp.net - MVC 3 授权自定义角色

.net - NHibernate SessionFactory 和 EF 4.0 ObjectContext 之间的区别

entity-framework-4 - EF 4 代码优先 : Model compatibility cannot be checked because the EdmMetadata type was not included in the model

c# - 使用 Entity Framework 在数据库之间复制实体

repository - 存储库模式的替代方案?

c# - ASP.NET MVC 3 中注销时清理浏览器缓存和 cookie 的问题

jquery - Facebook 评论插件

Spring 数据 jpa : Not an managed type: class xxx

opencv - 在Ubuntu服务器11.10上安装OpenCV预构建库