Структура Entity с репозиторием и шаблоном Unit of Work и архитектурой POCO

Это моя архитектура для EF4 с использованием шаблона репозитория и шаблона работы с POCO. Я считаю, что допустил некоторые ошибки.

У меня есть решение с 5 проектами:

  • MyApp.Common
  • MyApp.Data.EF4
  • MyApp.Domain.Company
  • MyApp.Domain.Transmission
  • MyApp.Web

Кроме того, я использую StructureMap в этом приложении, чтобы ввести слой данных.

Теперь я опишу, что каждый проект содержит - с источником:

MyApp.Common - содержит общие классы и интерфейсы

Интерфейс репозитория:

public interface IRepository<T> where T : class
{
    IQueryable<T> GetQuery(IEnumerable<Expression<Func<T, object>>> includes);

    IPaged<T> GetQuery(IQueryable<T> query,
        Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageNumber, int pageSize);

    IPaged<T> GetQuery(IEnumerable<T> query,
        Func<IEnumerable<T>, IOrderedEnumerable<T>> orderBy, int pageNumber, int pageSize);

    IEnumerable<T> GetObjectStateManagerChanges();

    void Insert(T entity);
    void MarkModified(T entity);
    void Delete(T entity);
    void Attach(T entity);
    void Detach(T entity);
    T GetOriginalEntity(Func<T, bool> predicate);
}

Единица рабочего интерфейса:

public interface IUnitOfWork : IDisposable
{
    int Commit();
}

Интерфейс рабочего интерфейса:

public interface IUnitOfWorkFactory
{
    IUnitOfWork Create();
}

Единица работы:

  public static class UnitOfWork
    {
        private const string HTTPCONTEXTKEY = "MyApp.Common.HttpContext.Key";

        private static IUnitOfWorkFactory _unitOfWorkFactory;
        private static readonly Hashtable _threads = new Hashtable();

        public static void Commit()
        {
            IUnitOfWork unitOfWork = GetUnitOfWork();

            if (unitOfWork != null)
            {
                unitOfWork.Commit();
            }
        }

        public static IUnitOfWork Current
        {
            get
            {
                IUnitOfWork unitOfWork = GetUnitOfWork();

                if (unitOfWork == null)
                {
                    _unitOfWorkFactory = ObjectFactory.GetInstance<IUnitOfWorkFactory>();
                    unitOfWork = _unitOfWorkFactory.Create();
                    SaveUnitOfWork(unitOfWork);
                }

                return unitOfWork;
            }
        }

        public static void Dispose()
        {
            IUnitOfWork unitOfWork = GetUnitOfWork();

            if (unitOfWork != null)
            {
                unitOfWork.Dispose();
            }
        }

        private static IUnitOfWork GetUnitOfWork()
        {
            if (HttpContext.Current != null)
            {
                if (HttpContext.Current.Items.Contains(HTTPCONTEXTKEY))
                {
                    return (IUnitOfWork)HttpContext.Current.Items[HTTPCONTEXTKEY];
                }

                return null;
            }
            else
            {
                Thread thread = Thread.CurrentThread;
                if (string.IsNullOrEmpty(thread.Name))
                {
                    thread.Name = Guid.NewGuid().ToString();
                    return null;
                }
                else
                {
                    lock (_threads.SyncRoot)
                    {
                        return (IUnitOfWork)_threads[Thread.CurrentThread.Name];
                    }
                }
            }
        }

        private static void SaveUnitOfWork(IUnitOfWork unitOfWork)
        {
            if (HttpContext.Current != null)
            {
                HttpContext.Current.Items[HTTPCONTEXTKEY] = unitOfWork;
            }
            else
            {
                lock (_threads.SyncRoot)
                {
                    _threads[Thread.CurrentThread.Name] = unitOfWork;
                }
            }
        }
    }

Базовый репозиторий для всех объектов:

public abstract class BaseRepository<T> where T : class
{
    protected IRepository<T> Repository { get; set; }

    public IEnumerable<Expression<Func<T, object>>> Includes { private get; set; }


    public BaseRepository()
    {
        Repository = ObjectFactory.GetInstance<IRepository<T>>();
        Includes = null;
    }

    protected virtual IQueryable<T> GetQuery()
    {
        return Repository.GetQuery(Includes).AsEnumerable().AsQueryable();
    }

    protected virtual IQueryable<T> GetQuery(params Expression<Func<T, object>>[] includes)
    {
        return Repository.GetQuery(includes);
    }

    protected virtual IPaged<T> GetQuery(IEnumerable<T> query,
        Func<IEnumerable<T>, IOrderedEnumerable<T>> orderBy, int pageNumber, int pageSize)
    {
        return Repository.GetQuery(query, orderBy, pageNumber, pageSize);
    }


    protected virtual IPaged<T> GetQuery(IQueryable<T> query,
        Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageNumber, int pageSize)
    {
        return Repository.GetQuery(query, orderBy, pageNumber, pageSize);
    }

    protected virtual IEnumerable<T> GetObjectStateManagerChanges()
    {
        return Repository.GetObjectStateManagerChanges();
    }

    protected virtual void Insert(T entity)
    {
        Repository.Insert(entity);
    }

    protected virtual void MarkModified(T entity)
    {
        Repository.MarkModified(entity);
    }

    protected virtual void Delete(T entity)
    {
            Repository.Delete(entity);
    }

    protected virtual void Attach(T entity)
    {
        Repository.Attach(entity);
    }

    protected virtual void Detach(T entity)
    {
        Repository.Detach(entity);
    }

    protected virtual T GetOriginalEntity(Func<T, bool> predicate)
    {
        return Repository.GetOriginalEntity(predicate);
    }
}

MyApp.Data.EF4 . Удерживает ссылку MyApp.Common и реализует все интерфейсы, используя способ EF. Он также включает MyAppModel.edmx, который содержит модели для всех объектов в системе. В MyAppModel.edmx есть «Стратегия генерации кода», установленная по умолчанию. Имя контейнера сущности MyAppEntities.

Репозиционирование:

public class Repository<T> : IRepository<T> where T : class
{
    ObjectContext _context;
    IObjectSet<T> _objectSet;

    protected ObjectContext Context
    {
        get
        {
            if (_context == null)
            {
                _context = GetCurrentUnitOfWork<EFUnitOfWork>().Context;
            }

            return _context;
        }
    }

    protected IObjectSet<T> ObjectSet
    {
        get
        {
            if (_objectSet == null)
            {
                _objectSet = this.Context.CreateObjectSet<T>();
            }

            return _objectSet;
        }
    }

    public TUnitOfWork GetCurrentUnitOfWork<TUnitOfWork>() where TUnitOfWork : IUnitOfWork
    {
        return (TUnitOfWork)UnitOfWork.Current;
    }

    public virtual IQueryable<T> GetQuery(IEnumerable<Expression<Func<T, object>>> includes)
    {
        return ObjectSet.IncludeMultiple(includes);
    }

    public virtual IPaged<T> GetQuery(IQueryable<T> query,
        Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageNumber, int pageSize)
    {
        if (orderBy != null)
        {
            query = orderBy(query);
        }

        IPaged<T> page = new Paged<T>(query, pageNumber, pageSize);

        return page;
    }

    public virtual IPaged<T> GetQuery(IEnumerable<T> query,
        Func<IEnumerable<T>, IOrderedEnumerable<T>> orderBy, int pageNumber, int pageSize)
    {
        if (orderBy != null)
        {
            query = orderBy(query);
        }

        IPaged<T> page = new Paged<T>(query, pageNumber, pageSize);

        return page;
    }

    public virtual IEnumerable<T> GetObjectStateManagerChanges()
    {
        return this.Context.ObjectStateManager.
            GetObjectStateEntries(EntityState.Added | EntityState.Modified).
            Select(e => e.Entity).
            OfType<T>();
    }

    public virtual void Insert(T entity)
    {
        this.ObjectSet.AddObject(entity);
    }

    public virtual void Delete(T entity)
    {
        this.ObjectSet.DeleteObject(entity);
    }

    public virtual void MarkModified(T entity)
    {
        this.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
    }

    public virtual void Attach(T entity)
    {
        ObjectStateEntry entry = null;
        if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == false)
        {
            this.ObjectSet.Attach(entity);
        }
    }

    public virtual void Detach(T entity)
    {
        ObjectStateEntry entry = null;
        if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == true)
        {
            this.ObjectSet.Detach(entity);
        }
    }

    public virtual T GetOriginalEntity(Func<T, bool> predicate)
    {
        T originalEntity = null;
        EFUnitOfWorkFactory factory = new EFUnitOfWorkFactory();
        using (EFUnitOfWork uow = (EFUnitOfWork)factory.Create())
        {
            originalEntity = uow.Context.CreateObjectSet<T>().Single(predicate);
        }
        return originalEntity;
    }
}

Расширение, используемое в реализации репозитория:

public static class Extensions
    {
        public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
            IEnumerable<Expression<Func<T, object>>> includes)
            where T : class
        {
            if (includes != null)
            {
                query = includes.Aggregate(query,
                          (current, include) => current.Include(include));
            }

            return query;
        }
    }

Реализация для IUnitOfWork:

public class EFUnitOfWork : IUnitOfWork, IDisposable
{
    public ObjectContext Context { get; private set; }
    public int Id { get; private set; }

    public EFUnitOfWork(ObjectContext context, int id)
    {
        Id = id;
        Context = context;
        Context.ContextOptions.LazyLoadingEnabled = false;
    }

    public int Commit()
    {
        return Context.SaveChanges();
    }

    public void Dispose()
    {
        if (Context != null)
        {
            Context.Dispose();
            Context = null;
        }

        GC.SuppressFinalize(this);
    }
}

Реализация IUnitOfWorkFactory:

public class EFUnitOfWorkFactory : IUnitOfWorkFactory
    {
        private static int Counter = 0;
        private static Func<ObjectContext> _objectContextDelegate;
        private static readonly Object _lockObject = new object();

        public static void SetObjectContext(Func<ObjectContext> objectContextDelegate)
        {
            _objectContextDelegate = objectContextDelegate;
        }

        public IUnitOfWork Create()
        {
            ObjectContext context;

            lock (_lockObject)
            {
                Counter++;
                context = _objectContextDelegate();
            }

            return new EFUnitOfWork(context, Counter);
        }
    }

MyApp.Domain.Company и MyApp.Domain.Transmission - содержит объекты POCO, которые должны содержать результаты запроса и быть бизнес-логикой решения. Каждый проект отражает другие и различные потребности в системе. Но объекты из MyApp.Domain.Company имеют отношения в БД и в .edmx с объектами MyApp.Domain.Transmission.

Например: Vehicle, который существует в MyApp.Domain.Company с помощью VehicleTransmission, который существует в MyApp.Domain.Transmission. Кроме того, VehicleTransmission содержит ссылку Vehicle, и поэтому MyApp.Domain.Transmission содержит ссылку MyApp.Domain.Company. Оба проекта содержат ссылку на MyApp.Common. Важно помнить, что T_TBL_VEHICLE и T_TBL_VEHICLE_TRANSMISSION (и соединения между ними), представленные в том же файле .edmx. Каждый файл .cs в этих проектах содержит объект POCO и диспетчер репозитория.

Vehicle.cs (из MyApp.Domain.Company)

MyApp.Domain.Company

VehicleTransmission.cs (из MyApp.Domain.Transmission)

public class Vehicle
    {
        public virtual long Id { get; set; }
        public virtual string VehicleNumber { get; set; }

        public virtual DateTime? PurchaseDate { get; set; }
        public virtual string InsuranceAgency { get; set; }
        public virtual DateTime? InsuranceValidity { get; set; }
        public virtual string Comments { get; set; }
        public virtual IList<Worker> Workers { get; set; }        

        public string GetPhoneNumber()
        {
            string phoneNumber = null;
            if (PhoneId.HasValue)
            {
                phoneNumber = Phone.PhoneNumber1;
                if (string.IsNullOrEmpty(phoneNumber))
                {
                    phoneNumber = Phone.PhoneNumber2;
                }
            }
            return phoneNumber;
        }

        public string GetManufacturerName()
        {
            string name = null;
            if (ManufacturerId.HasValue)
            {
                name = Manufacturer.Name;
            }
            return name;
        }
    }

    public class VehicleRepository : BaseRepository<Vehicle>
    {
        private bool IsCounterExists(Guid companyId, long counter)
        {
            return GetQuery().
                Where(x => x.CompanyId == companyId).
                Where(x => x.IsDeleted == false).
                AsEnumerable().
                Any(x => x.Code.ToNullable<long>() == counter);
        }

        public void Attach(Vehicle entity)
        {
            base.Attach(entity);
        }

        public void MarkModified(Vehicle entity)
        {
            base.MarkModified(entity);
        }

        public void Delete(Vehicle entity)
        {
            base.Delete(entity);
        }

        public void Insert(Vehicle entity)
        {
            if (entity.VehicleNumber != null) { entity.VehicleNumber.Trim(); }
            if (entity.VehicleNumber == null || entity.VehicleNumber == string.Empty)
            {
                throw new MissingFieldException(
                    CompanyExceptionMessage.MissingFieldException_Vehicle_Number.Message);
            }
            if (string.IsNullOrEmpty(entity.Code))
            {
                StaffCounterRepository rep = new StaffCounterRepository();
                entity.Code = rep.GetAndAdvanceCounter(entity.CompanyId,
                    StaffCounterEnum.VEHICLE,
                    counter => IsCounterExists(entity.CompanyId, counter)).ToString();
            }

            int equals = GetQuery().
                 Where(x => x.CompanyId == entity.CompanyId).
                 Where(x => x.IsDeleted == false).
                 Where(x =>
                     (x.Code != null && x.Code == entity.Code) ||
                     (x.VehicleNumber == entity.VehicleNumber)
                     ).Count();
            if (equals > 0)
            {
                throw new ExistsNameOrCodeException(
                    CompanyExceptionMessage.ExistsNumberOrCodeException_Vehicle);
            }
            base.Insert(entity);
        }

        public Vehicle Get(Guid companyId, long vehicleId)
        {
            return GetQuery().Where(x => x.CompanyId == companyId).
                Single(x => x.Id == vehicleId);
        }

        public IQueryable<Vehicle> Get(Guid companyId)
        {
            return GetQuery().
                Where(x => x.CompanyId == companyId).
                Where(x => x.IsDeleted == false).
                OrderBy(x => x.VehicleNumber);
        }

        public Vehicle Get(Guid companyId, string code)
        {
            return GetQuery().
                Where(x => x.CompanyId == companyId).
                Where(x => x.IsDeleted == false).
                FirstOrDefault(x => x.Code == code);
        }

        public IQueryable<Vehicle> Search(Guid companyId, string vehicleNumber)
        {
            var query = GetQuery().
                Where(x => x.CompanyId == companyId).
                Where(x => x.IsDeleted == false);
            if (vehicleNumber != null)
            {
                query = query.Where(x => x.VehicleNumber.Contains(vehicleNumber));
            }
            return query.OrderBy(x => x.VehicleNumber);
        }

        public IEnumerable<Vehicle> Get(Guid companyId, bool? freeVehicles,
            bool includeContractorVehicles)
        {
            IEnumerable<Vehicle> query = GetQuery().
                Where(x => x.CompanyId == companyId).
                Where(x => x.IsDeleted == false);

            if (freeVehicles == true)
            {
                query = query.Where(x => x.Workers.Count == 0);
            }
            else if (freeVehicles == false)
            {
                query = query.Where(x => x.Workers.Count > 0);
            }
            query = query.AsEnumerable();

            if (includeContractorVehicles == true)
            {
                WorkerRepository rep = new WorkerRepository();
                IEnumerable<Vehicle> contractorsVehicles = rep.Get(companyId).
                    Where(x => x.ContractorVehicleNumber != null &&
                        x.ContractorVehicleNumber != string.Empty).
                    AsEnumerable().Select(x => new Vehicle()
                    {
                        VehicleNumber = x.ContractorVehicleNumber
                    });
                query = query.Union(contractorsVehicles);
            }

            return query;
        }        

        public IQueryable<Vehicle> Get(Guid companyId, VehicleStatusEnum? status,
            string code, string vehicleNumber)
        {
            var query = GetQuery().
                Where(x => x.CompanyId == companyId).
                Where(x => x.IsDeleted == false);

            if (status.HasValue)
            {
                long? statusId = VehicleStatusWrapper.GetId<VehicleStatusWrapper>(status.Value);
                query = query.Where(x => x.StatusId == statusId);
            }
            if (!string.IsNullOrEmpty(code))
            {
                query = query.Where(x => x.Code.Contains(code));
            }
            if (!string.IsNullOrEmpty(vehicleNumber))
            {
                query = query.Where(x => x.VehicleNumber.Contains(vehicleNumber));
            }
            return query;
        }
    }

public class VehicleTransmission { public long? VehicleId { get; set; } public string VehicleNumber { get; set; } public long? SubcontractorId { get; set; } public bool WorkerOnHold { get; set; } public Vehicle Vehicle { get; set; } public void SetVehicleId(long? vehicleId) { VehicleId = vehicleId; } //Possibility to set vehicle that is not exists on the db public void SetVehicleNumber(string vehicleNumber) { VehicleId = null; VehicleNumber = vehicleNumber; } } public class VehicleTransmissionRepository : BaseRepository<VehicleTransmission> { public IQueryable<VehicleTransmission> Get() { return GetQuery(); } } . Содержит веб-приложение. Имеет ссылки на все другие проекты.

В global.asax, в MyApp.Web, я настраиваю Application_Start:

StructureMap

Кроме того, в Application_EndRequest я делаю:

    ObjectFactory.Configure(x =>
    {
        x.For<IUnitOfWorkFactory>().Use<EFUnitOfWorkFactory>();
        x.For(typeof(IRepository<>)).Use(typeof(Repository<>));
        x.For(typeof(IEnumRepository<,>)).Use(typeof(EnumRepository<,>));
    });
    EFUnitOfWorkFactory.SetObjectContext(() => new MyAppEntities());

У меня также есть веб-службы, где я использую репозитории и вызываю UnitOfWork.Dispose(); , когда это необходимо.

Пока это моя архитектура. Я действительно хочу знать, правильно ли я сделал все, и где мои подводные камни.

31 голос | спросил Naor 24 +04002011-10-24T12:39:57+04:00312011bEurope/MoscowMon, 24 Oct 2011 12:39:57 +0400 2011, 12:39:57

3 ответа


12

Не понимая слишком много вашего сценария, вот несколько указателей:

Я не знаком с Entity Framework, но общий интерфейс для UoW обычно имеет следующие типы поведения:

public interface UnitOfWork : IDisposable {
  bool IsInTransaction { get; }
  bool IsDirty { get; } // Same as your MarkModified()
  void BeginTransaction();
  void Commit();
  void Rollback();
}

Где происходит обработка ошибок и ведения журнала? Ниже приведен пример простой обертки, которую вы можете использовать.

// Example call
ExceptionHandler.Try("Could not load Vechicle", () => _vehicleRepository.Insert(vehicle));

 // Example class
 public class ExceptionHandler
    public void Try(string errorMessage, Action func)
    {
        try
        {
           func();
        }
        catch (Exception e)
        {
           Handle(e, errorMessage); // handle and log exception
        }
    }

И измените имя Common-Project на нечто менее распространенное.;)

ответил Mattias 27 +04002011-10-27T12:42:54+04:00312011bEurope/MoscowThu, 27 Oct 2011 12:42:54 +0400 2011, 12:42:54
5

Это на самом деле очень похоже на мою реализацию ... Одно существенное отличие заключается в том, что я не реализовал управление жизненным циклом для UOW. Я обрабатываю это с помощью инъекции зависимостей (Ninject using Request Scope), который дает тот же результат.

Другим существенным отличием в моей реализации является то, что у меня также есть ObjectContextResolver и ObjectContextFactory. Модуль работы и репозитории используют ObjectContextResolver для доступа к текущему Контексту, где ObjectContextFactory используется ObjectContextResolver для создания новых экземпляров Контекста, когда его не существует. ObjectContextFactory также использует ObjectContextActivationInfo, который является оболочкой для строки и параметров подключения, что позволяет мне легко изменять способ создания контекста.

У меня также есть два типа репозиториев ... У меня есть EntityRepository, который очень похож на вашу реализацию и позволяет мне запрашивать и работать с определенными объектами. У меня также есть ContextRepository, который предоставляет метод GetSet<T>, поэтому я могу запросить любой тип сущности в моей модели.

Я также допускаю, чтобы область «Единица работы» была немного более гибкой и позволяла потребительскому приложению определять начало и конец единицы работы. Типичное использование выглядит так ...

using(var unitOfWork = this.UnitOfWorkFactory.NewUnitOfWork())
{
     try
     {
          var myVehicle = this.VehicleService.GetVehicleById(123);
          myVehicle.VehicleNumber = this.MyExternalWebService.GetNewVehicleNumber();
          myVehicle.DateModified = DateTime.Now();

          unitOfWork.Commit();
     }
     catch(Exception ex)
     {
          // The Web Service Call failed, don't save the changes
          unitOfWork.Rollback();
     }

}

Это также позволяет выполнять отдельные Единицы работы с одним Запросом, что важно, если некоторые вещи нужно сохранить, прежде чем могут быть созданы другие вещи ... это часто происходит, когда внешние службы или устаревшие хранилища данных используются в сочетании с EF.

Поскольку я завернул свой код в используемом блоке, как только выполнение завершено, оно автоматически удаляется.

У меня также есть метод Rollback () на моем модуле UnitOfWork, который позволяет отменять изменения в случаях, когда какой-то код не удался, и вы не хотите сохранять изменения.

Одним из ограничений этой архитектуры (как вашей, так и моей) является неспособность выполнять операции с использованием UnitOfWork параллельно. Недавно мне пришлось выполнять два запроса EF через мою структуру параллельно и не удалось сделать это, потому что код существенно заменяет текущий UnitOfWork, так как моя архитектура спроектирована вокруг одного запроса Http.

Я размышлял о способах расширения моего текущего подхода, чтобы это разрешить.

ответил ctorx 18 +04002012-10-18T09:27:46+04:00312012bEurope/MoscowThu, 18 Oct 2012 09:27:46 +0400 2012, 09:27:46
4

Entity Framework - это единица работы в системе, поэтому вам не нужно изобретать велосипед. Только тот, который вы должны создать: общая база репозитория:

interface IRepository<TEntity>
    where TEntity : class
{
    //Add<TEntity>, Remove<TEntity> ...
}

class /* don't have to be abstract */ BaseRepository<TObjectContext, TEntity> : IRepository<TEntity>
    where TObjectContext : ObjectContext
    where TEntity : class
{
    public RepositoryBase(TObjectContext context)
    {
        //... store context
    }

    //implementations
}

И затем вы можете создавать специализированные репозитории.

ответил Peter Kiss 18 +04002012-10-18T14:52:54+04:00312012bEurope/MoscowThu, 18 Oct 2012 14:52:54 +0400 2012, 14:52:54

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132