首页 文章

具有存储库和工作单元的ASP.NET标识

提问于
浏览
33

我正在使用Entity Framework 6学习ASP.NET MVC 5应用程序中的存储库和工作单元模式 .

我已经阅读了很多教程和文章,但几乎所有教程和文章都是相同的 . 其他人说,存储库和工作单元模式是好的,其他人说DbContext已经是一个存储库和工作单元,其他人说类似的东西,但提供了一种完全不同的方法 . 我尝试了所有这些不同的方法(好吧,也许不是全部)并且仍然在努力确定哪种方法是最正确的方法 .

我现在拥有的是:

  • 实现IRepository的IRepository和GenericRepository

  • IUnitOfWork和UnitOfWork实现IUnitOfWork

  • IDbContext和MyDbContext继承自IdentityDbContext并实现IDbContext

不确定我是否需要粘贴它的代码,我认为这是非常通用的,问题实际上不是Repository / UnitOfWork . 我遇到的问题是将ASP.NET Identity类与我的存储库和工作单元结合使用 . 我正在为成员资格和所有其他数据共享相同的数据库 - 我认为这是一种常见的情况 . 我找不到好的解决方案如何使用我的存储库实例化ASP.NET Identity类 .

UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(_DBCONTEXT_);
this.UserManager = new UserManager<ApplicationUser>(store);

我应该用什么代替DBCONTEXT,以便它与我的UnitOfWork共享相同的DbContext?或者如何以其他方式完成使ASP.NET身份与UnitOfWork一起工作?

我尝试将DbContext暴露为UnitOfWork类的公共属性,如:

UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(this.unitOfWork.MyDbContext);

但是我不认为它是正确的 - 它不适用于自定义IDbContext接口,并使代码不适合单元测试 .

我也试图实现CustomUserStore和CustomRoleStore - 通常它可以工作,但是当我测试它时,它需要实现越来越多的方法 . 这个解决方案看起来太复杂了 - 我真的希望应该有更简单的方法 .

4 回答

  • 4

    我发现使用ASP.Net Identity 2.0和EF6有点挑战性 . 最大的缺点是缺乏文档或文档冲突 .

    我使用的是WebApi 2.0,EF6和ASP.Net Identity 2.0 . 起初它很难开始,但一旦它正在工作,它一直很好 .

    我创建了自己的Identity类 . 目前我不关心扩展身份类我只想生成表并登录系统 .

    CustomRole

    public class CustomRole : IdentityRole<int, CustomUserRole>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CustomRole"/> class.
        /// </summary>
        public CustomRole() { }
    
        /// <summary>
        /// Initializes a new instance of the <see cref="CustomRole"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        public CustomRole(string name) { Name = name; }
    }
    

    CustomUserClaim

    public class CustomUserClaim : IdentityUserClaim<int> { }
    

    CustomUserLogin

    public class CustomUserLogin : IdentityUserLogin<int> { }
    

    CustomUserRole

    public class CustomUserRole : IdentityUserRole<int> {}
    

    User

    public class User : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
    
        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        /// <value>The first name.</value>
        public string FirstName { get; set; }
    
        /// <summary>
        /// Gets or sets the last name.
        /// </summary>
        /// <value>The last name.</value>
        public string LastName { get; set; }
    
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="User"/> is active.
        /// </summary>
        /// <value><c>true</c> if active; otherwise, <c>false</c>.</value>
        public bool Active { get; set; }
    
    }
    

    我不喜欢Identity表的命名,所以我更改了名称 .

    DataContext

    public class DataContext : IdentityDbContext<User, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
        public DataContext() : base("DefaultConnection"){}
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
    
            modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles", "Security");
            modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins", "Security");
            modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims", "Security");
            modelBuilder.Entity<CustomRole>().ToTable("Roles", "Security");
            modelBuilder.Entity<User>().ToTable("Users", "Security");
    
        }
    }
    

    我发现让UserManager有点痛苦 .

    我创建了一个静态类来处理它 . UserStore确实处理DataContext的生命周期,但您必须调用dispose才能实现此目的 . 如果您在其他地方使用此DataContext引用,这可能会导致问题 . 我最终将它连接到我的DI容器中,但是现在这就是我所拥有的:

    public class Identity
    {
        /// <summary>
        /// Gets the user manager.
        /// </summary>
        /// <returns>UserManager&lt;User, System.Int32&gt;.</returns>
        public static UserManager<User, int> GetUserManager()
        {
            var store = new UserStore<User, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>(new DataContext());
            var userManager = new UserManager<User, int>(store);
    
            return userManager;
        }
    }
    

    我使用工作单元模式进行大多数数据访问 . 它运作良好 . 在某些情况下,我提供的数据需要比我暴露DataContext的这些情况所公开的工作单元更多的控制 . 如果这仍然不适合我,我将回退使用存储库 .

    public class UnitOfWork : IUnitOfWork
    {
        private readonly IContainer _container;
    
        public UnitOfWork(IContainer container) :this()
        {
            _container = container;
        }
    
        //private readonly List<CommitInterception> _postInterceptions = new List<CommitInterception>(); 
    
        public DataContext Context { get; set; }
    
        /// <summary>
        /// Initializes a new instance of the <see cref="UnitOfWork"/> class.
        /// </summary>
        public UnitOfWork()
        {
            Context = new DataContext();
        }
    
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <exception cref="System.NotImplementedException"></exception>
        public void Dispose()
        {
            //Chuck was here
            try
            {
                Commit();
            }
            finally
            {
                Context.Dispose();   
            }
        }
    
        /// <summary>
        /// Begins the transaction.
        /// </summary>
        /// <returns>IUnitOfWorkTransaction.</returns>
        public IUnitOfWorkTransaction BeginTransaction()
        {
            return new UnitOfWorkTransaction(this);
        }
    
        /// <summary>
        /// Commits this instance.
        /// </summary>
        public void Commit()
        {
            Commit(null);
        }
    
        /// <summary>
        /// Commits transaction.
        /// </summary>
        public void Commit(DbContextTransaction transaction)
        {
            //Lee was here.
            try
            {
                Context.SaveChanges();
    
                if (transaction != null)
                {
                    transaction.Commit();
                }
    
                //foreach (var interception in _postInterceptions)
                //{
                //    interception.PostCommit(interception.Instance, this);
                //}
    
            }
            catch (DbEntityValidationException ex)
            {
                var errors = FormatError(ex);
                throw new Exception(errors, ex);
            }
            catch
            {
                if (transaction != null)
                {
                    transaction.Rollback();
                }
                throw;
            }
            finally
            {
               // _postInterceptions.Clear();
            }
        }
    
        /// <summary>
        /// Formats the error.
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <returns>System.String.</returns>
        private static string FormatError(DbEntityValidationException ex)
        {
            var build = new StringBuilder();
            foreach (var error in ex.EntityValidationErrors)
            {
                var errorBuilder = new StringBuilder();
    
                foreach (var validationError in error.ValidationErrors)
                {
                    errorBuilder.AppendLine(string.Format("Property '{0}' errored:{1}", validationError.PropertyName, validationError.ErrorMessage));
                }
    
                build.AppendLine(errorBuilder.ToString());
            }
            return build.ToString();
        }
    
        /// <summary>
        /// Inserts the specified entity.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity.</param>
        /// <returns>``0.</returns>
        public T Insert<T>(T entity) where T: class
        {
            var instance = _container.TryGetInstance<IUnitOfWorkInterception<T>>();
    
            if (instance != null)
            {
                instance.Intercept(entity, this);
               // _postInterceptions.Add(new CommitInterception() { Instance = entity, PostCommit = (d,f) => instance.PostCommit(d as T, f) });
            }
    
            var set = Context.Set<T>();
            var item = set.Add(entity);
    
            return item;
        }
    
        public T Update<T>(T entity) where T : class
        {
            var set = Context.Set<T>();
            set.Attach(entity);
            Context.Entry(entity).State = EntityState.Modified;
    
            return entity;
        }
    
        /// <summary>
        /// Deletes the specified entity.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity.</param>
        public void Delete<T>(T entity) where T : class
        {
            var set = Context.Set<T>();
            set.Remove(entity);
        }
    
        /// <summary>
        /// Finds the specified predicate.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">The predicate.</param>
        /// <returns>IQueryable{``0}.</returns>
        public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var set = Context.Set<T>();
           return set.Where(predicate);
        }
    
        /// <summary>
        /// Gets all.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>IQueryable{``0}.</returns>
        public IQueryable<T> GetAll<T>() where T : class
        {
            return Context.Set<T>();
        }
    
        /// <summary>
        /// Gets the by identifier.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id">The identifier.</param>
        /// <returns>``0.</returns>
        public T GetById<T>(int id) where T : class
        {
            var set = Context.Set<T>();
            return set.Find(id);
        }
    
        /// <summary>
        /// Executes the query command.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql">The SQL.</param>
        /// <returns>DbSqlQuery{``0}.</returns>
        public DbSqlQuery<T> ExecuteQueryCommand<T>(string sql) where T : class
        {
            var set = Context.Set<T>();
            return set.SqlQuery(sql);
        }
    
        private class CommitInterception
        {
            public object Instance { get; set; }
    
            public Action<object, IUnitOfWork> PostCommit { get; set; } 
        }
    }
    
    public class UnitOfWorkTransaction : IUnitOfWorkTransaction
    {
        private readonly UnitOfWork _unitOfWork;
        private readonly DbContextTransaction _transaction;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="UnitOfWorkTransaction"/> class.
        /// </summary>
        /// <param name="unitOfWork">The unit of work.</param>
        public UnitOfWorkTransaction(UnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
            _transaction = _unitOfWork.Context.Database.BeginTransaction();
            Context = unitOfWork.Context;
        }
    
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            _unitOfWork.Commit(_transaction);
        }
    
        public DataContext Context { get; set; }
    
        /// <summary>
        /// Commits this instance.
        /// </summary>
        public void Commit()
        {
            _unitOfWork.Commit();
        }
    
        /// <summary>
        /// Rollbacks this instance.
        /// </summary>
        public void Rollback()
        {
            _transaction.Rollback();
        }
    
        /// <summary>
        /// Inserts the specified entity.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity.</param>
        /// <returns>T.</returns>
        public T Insert<T>(T entity) where T : class
        {
            return _unitOfWork.Insert(entity);
        }
    
        /// <summary>
        /// Updates the specified entity.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity.</param>
        /// <returns>T.</returns>
        public T Update<T>(T entity) where T : class
        {
            return _unitOfWork.Update(entity);
        }
    
        /// <summary>
        /// Deletes the specified entity.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity.</param>
        public void Delete<T>(T entity) where T : class
        {
            _unitOfWork.Delete(entity);
        }
    
        /// <summary>
        /// Finds the specified predicate.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">The predicate.</param>
        /// <returns>IQueryable&lt;T&gt;.</returns>
        public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
        {
           return _unitOfWork.Find(predicate);
        }
    
        /// <summary>
        /// Gets all.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>IQueryable&lt;T&gt;.</returns>
        public IQueryable<T> GetAll<T>() where T : class
        {
            return _unitOfWork.GetAll<T>();
        }
    
        /// <summary>
        /// Gets the by identifier.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id">The identifier.</param>
        /// <returns>T.</returns>
        public T GetById<T>(int id) where T : class
        {
           return _unitOfWork.GetById<T>(id);
        }
    
        /// <summary>
        /// Executes the query command.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql">The SQL.</param>
        /// <returns>DbSqlQuery&lt;T&gt;.</returns>
        public DbSqlQuery<T> ExecuteQueryCommand<T>(string sql) where T : class
        {
           return _unitOfWork.ExecuteQueryCommand<T>(sql);
        }
    }
    

    以下是它的一些实例 . 我有一个nHibernate背景,就像在 using 范围内定义一个事务所以我在我的工作单元中实现了 .

    using (var trans = _unitOfWork.BeginTransaction())
            {
                var newAgency = trans.Insert(new Database.Schema.Agency() { Name = agency.Name, TaxId = agency.TaxId });
    
            }
    

    使用工作单元“查找”的另一个例子:

    var users = _unitOfWork.Find<Database.Schema.User>(s => s.Active && s.Agency_Id == agencyId)
                .Select(u=> new {Label = u.FirstName + " " + u.LastName, Value = u.Id})
                .ToList();
    

    User Creation and User Sign-In

    我使用ASP.NET Identity进行登录和用户创建,使用我的工作单元进行其他操作 .

    Testing

    我不会尝试测试ASP.NET身份 . 首先,我确信微软在测试方面做得非常好 . 我相信他们做得比你或我做得更好 . 如果你真的想要测试ASP.NET身份代码,请将它放在接口后面并模拟接口 .

  • 9

    找到某种解决方案,看起来很通用,但我仍然不确定它是否真的很好并且不会破坏Repository / UnitOfWork模式原则 .

    我在我的IUnitOfWork中添加了通用的GetDbContext()方法:

    public interface IUnitOfWork : IDisposable
    {
       void Save();    
       IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;    
       TContext GetDbContext<TContext>() where TContext : DbContext, IDbContext;
    }
    

    它在UnitOfWork类中的实现:

    public class UnitOfWork<TContext> : IUnitOfWork where TContext : IDbContext, new()
    {
        private IDbContext dbContext;
    
        public UnitOfWork()
        {
            this.dbContext = new TContext();
        }
    
        public T GetDbContext<T>() where T : DbContext, IDbContext
        {
            return this.dbContext as T;
        }
    
        ...
    }
    

    如何在Controller中使用,初始化UserManager:

    public class AccountController : ControllerBase
    {
        private readonly IUnitOfWork unitOfWork;
    
        public UserManager<ApplicationUser> UserManager { get; private set; }
    
        public AccountController()
            : this(new UnitOfWork<MyDbContext>())
        {
        }
    
        public AccountController(IUnitOfWork unitOfWork)
        {
            this.unitOfWork = unitOfWork;    
            UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(unitOfWork.GetDbContext<MyDbContext>());
            this.UserManager = new UserManager<ApplicationUser>(store);
        }
    
        ...
    }
    

    我怀疑GetDbContext()将仅用于解决ASP.Identity的一些困难,所以可能不是那么糟糕..

  • 2

    “需要注意的一个问题是使用工作单元设计模式时,UserStore类不能很好地运行 . 具体来说,默认情况下,UserStore几乎在每个方法调用中调用SaveChanges,这使得过早提交工作单元变得容易 . 若要更改此行为,请更改UserStore上的AutoSaveChanges标志 . “

    var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
    store.AutoSaveChanges = false;
    

    来自Scott Allen:http://odetocode.com/blogs/scott/archive/2014/01/03/asp-net-identity-with-the-entity-framework.aspx

  • 4

    如果您正在使用Repository和UnitofWork模式,那么您可能正在使用DDD(域驱动设计),您可以在 Core project 中声明IRepository或IUnitofWork以及所有其他域模型和抽象类 .

    现在,您使用具体数据访问对象为此实例实体框架生成 Infrastructure project ,它在Core项目中实现这些接口 . 所以DbContext在那里很好,但是不要将它暴露给表示层 . 因此,在某些时候,如果您想将EF更改为任何其他ORM,那么在不触及表示层的情况下将更容易,您将Identity类与Data Access或Infrastructure项目分开 . 当然,您可以使用IOC容器从Presentation层的控制器中的基础架构实例化那些具体的存储库 .

相关问题