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

标签 c# entity-framework repository-pattern asp.net-identity unit-of-work

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

我已经看了很多教程和文章,但几乎所有的都是自相矛盾的。一些人说存储库和工作单元模式很好,其他人说 DbContext 已经是一个存储库和工作单元,其他人说类似的话,但提供了一种完全不同的方法。我尝试了所有这些不同的方法(好吧,也许不是所有的方法),但仍在纠结于哪种方法是最正确的。

我目前拥有的是:

  • IRepository 和 GenericRepository 实现 IRepository
  • IUnitOfWork 和 UnitOfWork 实现 IUnitOfWork
  • IDbContext 和 MyDbContext 继承自 IdentityDbContext 并实现了 IDbContext

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

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

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

我尝试将 DbContext 公开为 UnitOfWork 类的公共(public)属性,例如:

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

但我认为它不对 - 它不适用于自定义 IDbContext 接口(interface),并且使代码不利于单元测试。

我还尝试实现 CustomUserStore 和 CustomRoleStore - 总的来说它是有效的,但是当我测试它时,它需要实现越来越多的方法。这个解决方案看起来太复杂了 - 我真的希望有更简单的方法。

最佳答案

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

我正在使用 WebApi 2.0、EF6 和 ASP.Net Identity 2.0。起初很难开始,但一旦开始工作,就很好了。

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

自定义角色

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; }
}

自定义用户声明

public class CustomUserClaim : IdentityUserClaim<int> { }

自定义用户登录

public class CustomUserLogin : IdentityUserLogin<int> { }

自定义用户角色

public class CustomUserRole : IdentityUserRole<int> {}

用户

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 表的命名,所以我更改了名称。

数据上下文

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();

用户创建和用户登录

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

测试

我不会尝试测试 ASP.NET Identity。首先,我确信微软在测试它方面做得很好。我相信他们比你或我做得更好。如果您真的想围绕 ASP.NET Identity 代码进行测试,请将其放在接口(interface)后面并模拟接口(interface)。

关于c# - 具有存储库和工作单元的 ASP.NET 标识,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23226140/

相关文章:

entity-framework - 关于 Entity Framework + DDD 的问题

android - (Android Repository Pattern) 使用从 web 删除的项目更新电话数据库数据

c# - 如何在c#中添加检查条件

c# - 使用 lambda 的 Linq 子查询同一个表

c# - Entity Framework 中可重用的映射方法,用于检索选定的列

mysql - MSTest 预期异常测试失败

c# - 存储库模式和本地化查找表

c# - 以编程方式将证书添加到个人商店

c# - 如何在 DataGridView 显示对象的值中自定义控件?

c# - 将大量 (~1000-2000) Jpeg 图像合并为一个