【问题标题】:Is there any advantage in using a "repository factory" with ASP.NET MVC4 and Entity Framework?将“存储库工厂”与 ASP.NET MVC4 和实体框架一起使用有什么优势吗?
【发布时间】:2013-06-19 18:09:30
【问题描述】:

我正在开发一个应用程序,并开始使用 John Papa 的示例中的一些代码作为我的基础。在网上我发现了同样的代码,它出现在一个问题的答案中 堆栈溢出。问题来了:

How to de-attach an entity from a Context in Entity Framework?

它在由 SynerCoder 给出的答案中

答案的一部分建议使用以下类,该类用于从缓存存储库的字典中提供存储库。有人可以帮我告诉我真的存在吗 这样做的一个优势。我理解代码,但看不到将存储库保存在字典中的意义。不是每个新的网络请求都会看到一个 空字典,无论如何都必须获取/创建一个新的存储库。

Data/Helpers/IRepositoryProvider.cs

using System;
using System.Collections.Generic;
using System.Data.Entity;
using Data.Contracts;

namespace Data.Helpers
{
    /// <summary>
    /// A maker of Repositories.
    /// </summary>
    /// <remarks>
    /// An instance of this class contains repository factory functions for different types.
    /// Each factory function takes an EF <see cref="DbContext"/> and returns
    /// a repository bound to that DbContext.
    /// <para>
    /// Designed to be a "Singleton", configured at web application start with
    /// all of the factory functions needed to create any type of repository.
    /// Should be thread-safe to use because it is configured at app start,
    /// before any request for a factory, and should be immutable thereafter.
    /// </para>
    /// </remarks>
    public class RepositoryFactories
    {
        /// <summary>
        /// Return the runtime repository factory functions,
        /// each one is a factory for a repository of a particular type.
        /// </summary>
        /// <remarks>
        /// MODIFY THIS METHOD TO ADD CUSTOM FACTORY FUNCTIONS
        /// </remarks>
        private IDictionary<Type, Func<DbContext, object>> GetFactories()
        {
            return new Dictionary<Type, Func<DbContext, object>>
                {
                   //If you have an custom implementation of an IRepository<T>
                   //{typeof(IArticleRepository), dbContext => new ArticleRepository(dbContext)}
                };
        }

        /// <summary>
        /// Constructor that initializes with runtime repository factories
        /// </summary>
        public RepositoryFactories()
        {
            _repositoryFactories = GetFactories();
        }

        /// <summary>
        /// Constructor that initializes with an arbitrary collection of factories
        /// </summary>
        /// <param name="factories">
        /// The repository factory functions for this instance. 
        /// </param>
        /// <remarks>
        /// This ctor is primarily useful for testing this class
        /// </remarks>
        public RepositoryFactories(IDictionary<Type, Func<DbContext, object>> factories)
        {
            _repositoryFactories = factories;
        }

        /// <summary>
        /// Get the repository factory function for the type.
        /// </summary>
        /// <typeparam name="T">Type serving as the repository factory lookup key.</typeparam>
        /// <returns>The repository function if found, else null.</returns>
        /// <remarks>
        /// The type parameter, T, is typically the repository type 
        /// but could be any type (e.g., an entity type)
        /// </remarks>
        public Func<DbContext, object> GetRepositoryFactory<T>()
        {

            Func<DbContext, object> factory;
            _repositoryFactories.TryGetValue(typeof(T), out factory);
            return factory;
        }

        /// <summary>
        /// Get the factory for <see cref="IRepository{T}"/> where T is an entity type.
        /// </summary>
        /// <typeparam name="T">The root type of the repository, typically an entity type.</typeparam>
        /// <returns>
        /// A factory that creates the <see cref="IRepository{T}"/>, given an EF <see cref="DbContext"/>.
        /// </returns>
        /// <remarks>
        /// Looks first for a custom factory in <see cref="_repositoryFactories"/>.
        /// If not, falls back to the <see cref="DefaultEntityRepositoryFactory{T}"/>.
        /// You can substitute an alternative factory for the default one by adding
        /// a repository factory for type "T" to <see cref="_repositoryFactories"/>.
        /// </remarks>
        public Func<DbContext, object> GetRepositoryFactoryForEntityType<T>() where T : class
        {
            return GetRepositoryFactory<T>() ?? DefaultEntityRepositoryFactory<T>();
        }

        /// <summary>
        /// Default factory for a <see cref="IRepository{T}"/> where T is an entity.
        /// </summary>
        /// <typeparam name="T">Type of the repository's root entity</typeparam>
        protected virtual Func<DbContext, object> DefaultEntityRepositoryFactory<T>() where T : class
        {
            return dbContext => new EFRepository<T>(dbContext);
        }

        /// <summary>
        /// Get the dictionary of repository factory functions.
        /// </summary>
        /// <remarks>
        /// A dictionary key is a System.Type, typically a repository type.
        /// A value is a repository factory function
        /// that takes a <see cref="DbContext"/> argument and returns
        /// a repository object. Caller must know how to cast it.
        /// </remarks>
        private readonly IDictionary<Type, Func<DbContext, object>> _repositoryFactories;
    }
}

这是调用工厂的代码:

using System;
using Data.Contracts;
using Data.Helpers;
using Models;

namespace Data
{
    /// <summary>
    /// The "Unit of Work"
    ///     1) decouples the repos from the controllers
    ///     2) decouples the DbContext and EF from the controllers
    ///     3) manages the UoW
    /// </summary>
    /// <remarks>
    /// This class implements the "Unit of Work" pattern in which
    /// the "UoW" serves as a facade for querying and saving to the database.
    /// Querying is delegated to "repositories".
    /// Each repository serves as a container dedicated to a particular
    /// root entity type such as a <see cref="Url"/>.
    /// A repository typically exposes "Get" methods for querying and
    /// will offer add, update, and delete methods if those features are supported.
    /// The repositories rely on their parent UoW to provide the interface to the
    /// data layer (which is the EF DbContext in this example).
    /// </remarks>
    public class UnitOfWork : IUnitOfWork, IDisposable
    {
        public UnitOfWork(IRepositoryProvider repositoryProvider)
        {
            CreateDbContext();

            repositoryProvider.DbContext = DbContext;
            RepositoryProvider = repositoryProvider;       
        }

        // Repositories
        public IRepository<Event> Events { get { return GetStandardRepo<Event>(); } }
        public IRepository<Candidate> Candidates { get { return GetStandardRepo<Candidate>(); } }

        /// <summary>
        /// Save pending changes to the database
        /// </summary>
        public void Commit()
        {
            //System.Diagnostics.Debug.WriteLine("Committed");
            DbContext.SaveChanges();
        }

        protected void CreateDbContext()
        {
            DbContext = new UnicornsContext();

            // Do NOT enable proxied entities, else serialization fails
            DbContext.Configuration.ProxyCreationEnabled = false;

            // Load navigation properties explicitly (avoid serialization trouble)
            DbContext.Configuration.LazyLoadingEnabled = false;

            // Because Web API will perform validation, I don't need/want EF to do so
            DbContext.Configuration.ValidateOnSaveEnabled = false;
        }

        protected IRepositoryProvider RepositoryProvider { get; set; }

        private IRepository<T> GetStandardRepo<T>() where T : class
        {
            return RepositoryProvider.GetRepositoryForEntityType<T>();
        }
        private T GetRepo<T>() where T : class
        {
            return RepositoryProvider.GetRepository<T>();
        }

        private UnicornsContext DbContext { get; set; }

        #region IDisposable

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (DbContext != null)
                {
                    DbContext.Dispose();
                }
            }
        }

        #endregion
    }
}

在我看来,工厂让事情变得比他们需要的更复杂。我是否正确,我是否应该以更简单的方式来执行此操作,例如:

private IRepository<xx> = new GenericRepository<xx>(dbContext);

还有一点。在我的应用程序中,我使用的是 Unity。那么在构造函数中指定所需的存储库并让 Unity 为我创建存储库会更容易吗?如果我这样做了,那么有没有一种方法可以传递 dbContext 以便 Unity 在创建存储库时可以使用它?有没有人使用 Unity 创建过这样的存储库?

【问题讨论】:

  • 您的问题是关于引入存储库背后的一般论点,还是只是想知道为什么它们以这种特殊方式实施?
  • 我想知道为什么存储库是通过使用工厂实现的,而不是简单的:private: IRepository = new GenericRepository
  • 这不是很明显吗?想象一个包含 600 个存储库的工作单元,并将您的版本与讨论的版本进行比较。
  • 你需要看看使用工厂模型的优势。该答案可以应用于您的问题。
  • @wiktor - 很抱歉,但我不明白你想说什么。我无法想象一个 Web 应用程序在一个单元中需要更改 600 个不同的实体。

标签: asp.net-mvc asp.net-mvc-3 entity-framework asp.net-mvc-4 entity-framework-4


【解决方案1】:

好的。这是我最好的镜头:

  1. 将存储库保存在缓存中的目的是确保每个请求只启动存储库一次。存储库缓存位于RepositoryProvider 类中,并通过GetRepositoryForEntityType 方法暴露给UnitOfWork。所以优点是工作单元不关心存储库的缓存或创建。

  2. RepositoryProvider 类在每个工作单元中实例化一次。 (注意——最好为每个请求创建新的存储库)。 RepositoryProvider 使用类型作为键将存储库保存在字典中。这在使用具有Type 参数的通用存储库库时很好。但是,如果您创建了自定义存储库怎么办?在此示例中,按类型创建存储库通过MakeRepository 方法传递给RepositoryFactories 类。优点是创建仓库和缓存是分开的。

  3. RepositoryFactories 类知道何时创建自定义存储库,因为它包含使用类型作为键和函数作为值的字典。该函数是自定义存储库的构造函数。如果字典中有值,则使用该构造函数,否则只需使用通用基本构造函数。

这意味着当您添加实体时,您无需修改​​任何这些类,除非您创建自定义存储库。当你这样做时,你所要做的就是在RepositoryFactories 中的字典中添加一个条目

【讨论】:

  • 这是否意味着所有调用者将始终获得相同的存储库实例?这是如何运作的?如果创建了两个 uow 实例,它们有自己的 DbContext,但共享 RepositoryFactory 的一个单例实例,这是否意味着您可以拥有一个包含存储库的 uow,这些存储库可能使用不同的 DbContext 实例,具有不同的更改跟踪器等?
  • 不,所有调用者都不会获得相同的存储库实例。为每个请求实例化一个新的存储库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-23
  • 1970-01-01
相关资源
最近更新 更多