【问题标题】:Adding a class which uses DbContext to ASP.NET Identity 2 project将使用 DbContext 的类添加到 ASP.NET Identity 2 项目
【发布时间】:2014-12-21 22:31:20
【问题描述】:

我正在使用 ASP.NET MVC 应用程序,该应用程序基于通过 NuGet 提供的 Identity 示例。因此,我已经有一些类可以使用数据库,例如应用程序数据库上下文。

比如说,我决定让用户为管理员留下请求。我已将 Request 类添加到模型中:

public class Request
{
    public int Id { get; set; }
    public string Message { get; set; }
    public ApplicationUser User { get; set; }
}

由于示例使用不同的管理器来处理用户、角色等,我决定在 Identity.config 文件中创建另一个名为 ApplicationRequestManager 的管理器(尽管我不确定这是一个好的做法)。

 public class ApplicationRequestManager : IRequestManager
{

    private ApplicationDbContext db = new ApplicationDbContext();

    public void Add(Request request)
    {
            db.Requests.Add(request);
            db.SaveChanges();            
    }
    ...
}

这个类使用 ApplicationDbContext 来处理数据库,并且有一些方法来创建一个请求,找到它等等。

我在 Manage 控制器中创建了一个负责发送请求的方法:

public ActionResult SendRequest(IndexViewModel model)
    {
        Request request = new Request { Message = model.Message, User = UserManager.FindById(User.Identity.GetUserId()) };
        requestManager.Add(request);
        return View();
    }

调用此方法时,出现以下异常:

一个实体对象不能被多个 IEntityChangeTracker 实例引用

如果我理解正确,异常的原因是我使用一个 ApplicationDbContext 来获取用户 - 通过 UserManager 并且我使用另一个 ApplicationDbContext 来添加请求 - 通过 RequestManager,所以我的请求附加到两个上下文。据我所知,可以通过将相同的上下文传递给 UserManager 和 RequestManager 来避免此类错误。但是,UserManager 通过 OwinContext 与其他管理器一起获取其上下文:

// Configure the db context, user manager and role manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

我怎样才能让我自己的经理也遵循这种模式?我尝试使用 CreatePerOwinContext 方法,例如

 app.CreatePerOwinContext<ApplicationRequestManager>(ApplicationRequestManager.Create);

我还尝试按照 RoleManager 示例实现 Create 方法

public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
    }

但是我的请求没有任何商店,所以我不知道应该如何处理“新角色商店”部分。我怎么能解决这个问题?

更新:

我已经尝试过 Gert 的解决方案,它奏效了:

public class Request
{
  public int Id { get; set; }
  public string Message { get; set; }

  [ForeignKey("User")]
  public int ApplicationUserId { get; set; }
  public ApplicationUser User { get; set; }
}

var userId = User.Identity.GetUserId();
Request request = new Request
                  { 
                      Message = model.Message,
                      ApplicationUserId = userId
                  };

我也厌倦了使用 HttpConext.Current.GetOwinContext().Get 方法的另一种方式。我在 ApplicationRequestMananger 中添加了以下行:

public ApplicationRequestManager()
    {
        this.db = HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>();
    }

它与原始的 Request 类一起工作得很好。

问题是,每种方式各有什么优缺点?我已经阅读了外键,并且我非常了解一般概念;但我真的不明白 'HttpContext.Current.GetOwinContext().Get()' 会导致什么问题。我应该使用它,因为它比添加外键更简单吗?

【问题讨论】:

    标签: c# asp.net asp.net-mvc entity-framework asp.net-identity


    【解决方案1】:

    您的设计的问题在于每个经理都有自己的上下文。看到this example,我觉得每个经理都应该打电话……

    db = context.Get<ApplicationDbContext>();
    

    ...或在其构造函数中接收请求边界上下文。

    除此之外,您可以通过将外部字段暴露给ApplicationUser (ApplicationUserId?) 作为Request 中的原始属性来简化此操作:

    public class Request
    {
        public int Id { get; set; }
        public string Message { get; set; }
    
        [ForeignKey("User")]
        public int ApplicationUserId { get; set; }
        public ApplicationUser User { get; set; }
    }
    

    然后像这样创建Request

        var userId = User.Identity.GetUserId();
        Request request = new Request
                          { 
                              Message = model.Message,
                              ApplicationUserId = userId
                          };
    

    这被称为外键关联,与只有参考导航属性的独立关联相对。

    【讨论】:

    • 这是否意味着我必须为以后添加的每个附加课程应用这样的解决方案?我虽然有更好的方法来处理它。我尝试使用 context.Get 但没有成功。也许我会再试一次。
    • 如果您不使用 DI 容器,则必须这样做。 DbContext 每个 HTTP 请求应该只有一个实例,否则你会看到这样的问题。但是 DI Container 可以为您管理它,减少您必须编写的样板代码量。
    • 不确定您现在指的是哪个解决方案,但外键关联是在 EF 中处理关联的推荐方法。当然,您不必必须这样做,但是要确保对象图只属于一个上下文总是比较困难的。而且,如上所述,还建议每个请求一个上下文实例。 app.CreatePerOwinContext(ApplicationDbContext.Create) 声明应该可以。
    • 是的,我指的是外键解决方案。我没有找到如何使用 app.CreatePerOwinContext,所以我尝试了另一种方法,你现在可以在我的帖子中看到。也可以吗? @trailmax 您能否给我一个提示,如何通过我的 RequestManager 创建和使用 DI 容器?我已经用我所做的更新了我的帖子,也许这就是你的意思。
    • DI Container 是一个相当复杂的解决方案,看起来你现在不需要它。从HttpContext.Current.GetOwinContext().Get&lt;ApplicationDbContext&gt;() 获取ApplicationDbContext 应该对你有用——只要确保这是你创建dbContext 的唯一方法。如果你想了解 DI 容器是如何通过身份实现的,我已经写了一篇关于它的博文:tech.trailmax.info/2014/09/…