简体   繁体   English

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

[英]ASP.NET Identity with Repository and Unit of Work

I'm learning Repository and Unit of Work patterns in ASP.NET MVC 5 application with Entity Framework 6.我正在使用实体框架 6 学习 ASP.NET MVC 5 应用程序中的存储库和工作单元模式。

I had already read a lot of tutorials and articles, but almost all of them are condradictory.我已经看了很多教程和文章,但几乎都是自相矛盾的。 Ones say that Repository and Unit of Work patterns are good, others say DbContext is already a repository and unit of work, others say something similar, but offer a completely different approach.有人说存储库和工作单元模式很好,有人说 DbContext 已经是存储库和工作单元,有人说类似,但提供了完全不同的方法。 I tried all these different approaches (well, maybe not all of them) and still struggling regarding which approach is the most correct one.我尝试了所有这些不同的方法(好吧,也许不是所有方法),但仍在为哪种方法最正确而苦苦挣扎。

What I currently have is:我目前拥有的是:

  • IRepository and GenericRepository implementing IRepository IRepository 和 GenericRepository 实现 IRepository
  • IUnitOfWork and UnitOfWork implementing IUnitOfWork IUnitOfWork 和 UnitOfWork 实现 IUnitOfWork
  • IDbContext and MyDbContext inherited from IdentityDbContext and implementing IDbContext IDbContext 和 MyDbContext 继承自 IdentityDbContext 并实现 IDbContext

Not sure if I need to paste the code for it, I think it's pretty generic and the problem actually is not with Repository/UnitOfWork as such.不确定是否需要为它粘贴代码,我认为它非常通用,而且问题实际上不在于 Repository/UnitOfWork 本身。 The issue I have is with using ASP.NET Identity classes in combination with my Repositories and Unit of Work.我遇到的问题是将 ASP.NET Identity 类与我的存储库和工作单元结合使用。 I'm sharing same database for membership and for all other data - and I think it's a common scenario.我正在为成员资格和所有其他数据共享相同的数据库 - 我认为这是一个常见的情况。 I cannot find the good solution how can I instantiate ASP.NET Identity classes using my repositories.我找不到好的解决方案如何使用我的存储库实例化 ASP.NET Identity 类。

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

What should I put in place of DBCONTEXT , so that it would share same DbContext with my UnitOfWork?我应该用什么代替DBCONTEXT ,以便它与我的 UnitOfWork 共享相同的 DbContext? Or how it can be done in some other way to make ASP.NET Identity to work with UnitOfWork?或者如何以其他方式使 ASP.NET Identity 与 UnitOfWork 一起工作?

I tried exposing DbContext as public property of UnitOfWork class, something like:我尝试将 DbContext 作为 UnitOfWork 类的公共属性公开,例如:

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

However I don't think it's right - it doesn't work with custom IDbContext interface, and makes the code not good for unit testing.但是我认为这是不对的 - 它不适用于自定义 IDbContext 接口,并使代码不适用于单元测试。

I also tried to implement CustomUserStore and CustomRoleStore - in general it worked, but as I was testing it, it was requiring to implement more and more methods.我还尝试实现 CustomUserStore 和 CustomRoleStore - 总的来说它是有效的,但是当我测试它时,它需要实现越来越多的方法。 This solution looks too complicated - I really hope there should more simple way.这个解决方案看起来太复杂了 - 我真的希望应该有更简单的方法。

I have found working with ASP.Net Identity 2.0 and EF6 a bit challenging.我发现使用 ASP.Net Identity 2.0 和 EF6 有点挑战。 The biggest drawback is the lack of documentation or conflicting documentation.最大的缺点是缺乏文档或相互冲突的文档。

I am using WebApi 2.0, EF6 and ASP.Net Identity 2.0.我正在使用 WebApi 2.0、EF6 和 ASP.Net Identity 2.0。 At first it was tough to get going but once it's working, it's been good.起初很难开始,但一旦它起作用了,它就很好了。

I created my own Identity classes.我创建了自己的 Identity 类。 At the moment I don't care about extending the identity classes I just want to generate the tables and log into the system.目前我不关心扩展身份类,我只想生成表并登录系统。

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

}

I don't like the naming of the Identity tables, so I changed the names.我不喜欢 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");

    }
}

I found getting the UserManager a bit of a pain.我发现获取 UserManager 有点痛苦。

I created a static class to handle it.我创建了一个静态类来处理它。 The UserStore does handle the lifecycle of the DataContext, but you'll have to call dispose for this to happen. UserStore 确实处理 DataContext 的生命周期,但您必须调用 dispose 才能实现这一点。 This could cause problems if you are using this DataContext reference elsewhere.如果您在其他地方使用此 DataContext 引用,这可能会导致问题。 I'll eventually wire it into my DI container, but for now this is what I have:我最终会将它连接到我的 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;
    }
}

I use the Unit of Work pattern for most my data access.我在大多数数据访问中使用工作单元模式。 It works good.它运作良好。 There are some cases where I have data that needs more control than the unit of work exposes for these cases I exposed the DataContext.在某些情况下,我的数据需要比工作单元公开的更多控制,因为这些情况我公开了 DataContext。 If that still does not work for me, I'll fallback to using a repository.如果这对我来说仍然不起作用,我将回退到使用存储库。

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

Here are a few examples of it in action.以下是它的一些实际应用示例。 I have an nHibernate background and like defining a transaction in the scope of a using so I implemented in my unit of work.我有 nHibernate 背景,喜欢在using范围内定义事务,所以我在我的工作单元中实现了。

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

        }

Another example of using the "Find" off of the Unit of Work:使用工作单元中的“查找”的另一个示例:

        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用户创建和用户登录

I use ASP.NET Identity for the sign-In and user creation and my Unit of Work for everything else.我使用 ASP.NET Identity 进行登录和用户创建,并使用我的工作单元进行其他所有操作。

Testing测试

I would not try to test ASP.NET Identity.我不会尝试测试 ASP.NET Identity。 For one I'm sure Microsoft did a pretty good job testing it.一方面,我确信微软在测试方面做得非常好。 I'm sure they did a better job than you or I could do.我敢肯定,他们比你或我做得更好。 If you really want to test around the ASP.NET Identity code put it behind an interface and mock out the interface.如果您真的想围绕 ASP.NET Identity 代码进行测试,请将其放在接口后面并模拟该接口。

"One issue to be aware of is that the UserStore class does not play well when using the unit of work design pattern. Specifically, the UserStore invokes SaveChanges in nearly every method call by default, which makes it easy to prematurely commit a unit of work. To change this behavior, change the AutoSaveChanges flag on the UserStore." “需要注意的一个问题是 UserStore 类在使用工作单元设计模式时不能很好地发挥作用。具体来说,UserStore 在默认情况下几乎在每个方法调用中都会调用 SaveChanges,这使得过早提交工作单元变得容易。要更改此行为,请更改 UserStore 上的 AutoSaveChanges 标志。”

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

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

Found some sort of solution, which looks generic enough, but I'm still not sure if it's really good and doesn't break Repository/UnitOfWork pattern principles.找到了某种看起来足够通用的解决方案,但我仍然不确定它是否真的很好并且不会破坏 Repository/UnitOfWork 模式原则。

I added generic GetDbContext() method to my IUnitOfWork:我在 IUnitOfWork 中添加了通用 GetDbContext() 方法:

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

Its implementation in UnitOfWork class:它在 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;
    }

    ...
}

How it's used in a Controller, initializing UserManager:如何在控制器中使用它,初始化 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);
    }

    ...
}

I suspect GetDbContext() will be used just to workaround some difficulties with ASP.Identity, so might it's not so bad..我怀疑 GetDbContext() 将仅用于解决 ASP.Identity 的一些困难,所以它可能还不错..

If you are using Repository and UnitofWork pattern may be you are using it with DDD (Domain Driven Design) where you declare IRepository or IUnitofWork in Core project along with all other domain model and abstract classes.如果您正在使用 Repository 和 UnitofWork 模式,则可能是将它与 DDD(域驱动设计)一起使用,在其中您在Core 项目中声明 IRepository 或 IUnitofWork 以及所有其他域模型和抽象类。

Now you make Infrastructure project which implements those interfaces in Core project using concrete data access object for this instance Entity Framework.现在,您使用此实例实体框架的具体数据访问对象创建在 Core 项目中实现这些接口的基础设施项目 so DbContext is fine there but yes don't expose it to presentation layer.所以 DbContext 在那里很好,但是不要将它暴露给表示层。 So at some point if you want to change EF to any other ORM then it will be easier without touching presentation layer where you put your Identity classes separate from Data Access or Infrastructure project.因此,在某些时候,如果您想将 EF 更改为任何其他 ORM,那么在不触及将身份类与数据访问或基础设施项目分开的表示层的情况下会更容易。 And of course you can use IOC container to instantiate those concrete Repositories from Infrastructure in Controllers of Presentation layer.当然,您可以使用 IOC 容器从表示层控制器中的基础设施中实例化那些具体的存储库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ASP.NET 内核上的身份工作单元 - Unit of Work with Identity on ASP.NET Core Asp.Net身份用户管理器和工作单元-如何配对? - Asp.Net Identity Usermanager and Unit Of Work - how to mate them? 在ASP.NET MVC中正确使用存储库和工作单元模式 - Correct use of Repository and Unit Of Work patterns in ASP.NET MVC 在ASP.NET中实现存储库和工作单元模式 - Implementing the Repository and Unit of Work Patterns in ASP.NET 用于发布实体的 Asp.Net 核心存储库和工作单元模式 - Asp.Net core Repository and Unit Of Work Pattern for Posting an entity ASP.NET MVC,Repository,工作单元和Entity框架单元测试 - ASP.NET MVC, Repository, Unit of work and Entity framework unit testing 在ASP.NET Core中以现有存储库模式正确实现工作单元 - Proper Implementation of Unit of Work in Existing Repository Pattern in ASP.NET Core asp.net Identity Roles 组织单元结构创建 - asp.net Identity Roles organisation unit structure creation 依赖注入 ASP.NET 身份用户(和单元测试) - Dependency injecting an ASP.NET Identity user (and unit testing) ASP.NET MVC 5 - 仅使用身份的单元测试服务 - ASP.NET MVC 5 - Unit Testing Service that uses only Identity
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM