Шаблон репозитория + шаблон единицы работы + MVC3 + EF4 - проблема определения данных модели для работы со всем - PullRequest
3 голосов
/ 03 апреля 2012

Я сделал учебники по множеству вопросов о единицах работы и шаблонах репозитория. Я пытаюсь соединить два в реализации MVC и Ef4. Учебник имеет небольшую модель EF, всего 2 объекта. Таким образом, объекты, соответствующие двум объектам, были определены с помощью ICollections для свойств навигации и т. Д., Как и в первом коде, но не было связи между этими объектами и структурой объектов. Это объекты, которые были объединены как хранилища в единицу работы. Но здесь я начинаю задавать следующие вопросы.

  1. Зачем создавать в основном дубликаты объектов из объектов, уже находящихся в каркасе сущностей, когда вы можете использовать объекты в каркасе сущностей? Вы не можете?
  2. Если я создаю эти объекты ... мне нужно отобразить всю модель ef? Имея ICollections для свойств навигации, я получаю ошибки, если объекты не совпадают точно, и это в основном продолжает каскадироваться на протяжении большей части модели. Модель довольно большая и включает в себя более десятка объектов, и мне не нужно создавать объекты, которые не будут использоваться в хранилище.
  3. Если это классы, которые вы отправляете в свои представления ... разве классы моделей представлений не должны быть урезаны версиями ваших реальных классов моделей? Это означало бы, что в моем контроллере я использовал бы единицы работы и классы репозитория для извлечения данных, а затем также разбирал их и передавал только определенные фрагменты данных в модель представления. Это означает, что вся та чистота, которую мы только что получили, используя шаблоны единиц работы и хранилища, теперь начинает исчезать.

У меня есть следующий код ...

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 Ответ

4 голосов
/ 04 апреля 2012
  1. Объектами, определенными в EF, являются Модели постоянства , то есть они моделируют, какие данные сохраняются с какой структурой. Приложение обычно имеет модель, которая моделирует реальные бизнес-проблемы и решения, которые являются поведением. Хранилище получает объекты этого приложения, а затем извлекает из них любую информацию, необходимую для хранения. Большую часть времени две модели (приложение и постоянство) будут сильно напоминать, поскольку простые вещи идентичны, но во многих случаях есть различия из-за намерения (намерение модели приложения - моделирование поведения, намерение сохранения - моделирование хранилища).

    При загрузке сохраненных данных репозиторий воссоздает объекты приложения из их сохраненной формы, поэтому он сопоставляет модель персистентности с моделью приложения. Если модель очень проста, то вы можете напрямую использовать сущности EF, но если вы знаете, что в будущем модели будут другими, безопаснее будет выполнить «преобразование» с самого начала, что будет выглядеть так, будто вы просто дублируете сущности .

    Кроме того, идея состоит в том, чтобы отделить приложение от деталей реализации доступа к базе данных, а сущность EF - деталь реализации. Если завтра вы решите, что EF sux и Nhibernate лучше, или вам нужен более легкий и эффективный способ, вы измените только реализацию репозитория, не затрагивая остальную часть приложения. Приведенный вами пример кода плохо работает, потому что интерфейс репозитория предоставляет детали реализации, такие как IQueryable (это называется неплотной абстракцией)

  2. Вам не нужно отображать всю модель персистентности. Вы отображаете только для текущих потребностей приложения. Если приложение хочет вернуть объект Foo, вы получаете все необходимые данные Foo с помощью Ef (оно может охватывать несколько таблиц и, возможно, сложные запросы) и отображаете их в Foo. Если объект Bar очень прост и почти 1 к 1 для объекта EF, вы просто копируете объект в объект (чтобы позже было легко переключиться с EF на другой объект)

  3. Это не так. Это классы постоянства, а не классы модели View. Большинство учебных пособий по EF делают очень плохую работу, заставляя вас думать, что вы непосредственно используете сущности EF в качестве модели повсюду. Модель View - это отдельная модель, отвечающая потребностям View. Это построено, хотя из модели постоянства. Хранилище (другое специализированное хранилище для запросов) будет отображать объекты EF, полученные в результате запроса, непосредственно в биты модели представления. Еще раз, если вы переключаетесь на другую Orm, вам не нужно ничего менять для модели представления.

Как вы видите, репозиторий много делает и имеет важную ответственность за поддержание правильно наслоенного приложения. Но EF - это деталь реализации репозитория, и при разработке модели для приложения или представлений вы должны забыть о структуре EF или db и знать только об интерфейсе репозитория (который разработан в соответствии с потребностями приложения).

...