Vlad Stryapko
Vlad Stryapko

Reputation: 1057

Adding a class which uses DbContext to ASP.NET Identity 2 project

I'm working with ASP.NET MVC application which is based on Identity sample available via NuGet. Because of this I already have some classes to work with the database e.g. ApplicationDbContext.

Say, I decided to let users leave requests for the administrator. I've added the Request class to the models:

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

Since the sample uses different managers to work with users, roles, etc, I've decided to create another one called ApplicationRequestManager inside the Identity.config file (though I'm not sure it's a good practice).

 public class ApplicationRequestManager : IRequestManager
{

    private ApplicationDbContext db = new ApplicationDbContext();

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

This class uses the ApplicationDbContext to work with the database and has some methods to create a request, find it and so on.

I've created a method responsible for sending request inside the Manage controller:

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

When this method is invoked, I get the following exception:

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

If I understood correctly, the reason of exception is that I use one ApplicationDbContext to get User - via UserManager and I use another ApplicationDbContext to add the request - via RequestManager, so my request is attached to two contexts. As far as I know, such mistake can be avoided by passing the same context to both UserManager and RequestManager. However, UserManager gets its context via the OwinContext together with other managers:

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

How can I make my own manager follow that pattern as well? I've tried to use the CreatePerOwinContext method like

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

And I've also tried to implement the Create method following the RoleManager example

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

But I don't have any Store for my requests so I don't know what I should do with the 'new RoleStore' part. How could I solve that problem?

Updated:

I've tried Gert's solution and it worked:

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

I've also tired another way using HttpConext.Current.GetOwinContext().Get method. I've added the following line to my ApplicationRequestMananger:

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

And it worked fine with the original Request class.

The question is, what advantages and disadvantages does each way have? I've read about foreign keys and I understand the general idea quite well; but I don't really understand what problems can 'HttpContext.Current.GetOwinContext().Get()' cause. Should I use it since it's simpler than adding foreign keys?

Upvotes: 1

Views: 2287

Answers (1)

Gert Arnold
Gert Arnold

Reputation: 109080

The trouble with your design is that each manager has its own context. Seeing this example, I think each manager should call...

db = context.Get<ApplicationDbContext>();

...or receive the request-bounded context in their constructor.

Apart from that, you could make this much simpler by exposing the foreign field to ApplicationUser (ApplicationUserId?) as a primitive property in 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; }
}

And then create Request like so:

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

This is refered to as foreign key associations, as opposed to independent associations that only have a reference navigation property.

Upvotes: 2

Related Questions