1

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?

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Does that mean I'll have to apply such solution for every additional class I'll add in future? I though there was a better way to handle it. I've tried to use context.Get but it didn't work out. Perhaps I'll try once again.
You will have to if you are not using DI container. DbContext should be only one instance per HTTP request, otherwise you'll see issues like this. But DI Container can manage it for you, reducing the amount of boiler-plate code you have to write.
Not sure to which solution you refer now, but foreign key associations is the recommended way to handle associations in EF. You don't have to, of course, but then it's always harder to ensure that an object graph belongs to one context only. And, as said, one context instance per request is also recommended. It should be possible by this app.CreatePerOwinContext(ApplicationDbContext.Create) statement.
Yeah, I was referring to the foreign key solution. I didn't find out how to use app.CreatePerOwinContext, so I've tried another way which you can see in my post right now. Is it OK too? @trailmax Could you please give me a hint how to create and use the DI container with my RequestManager? I've updated my post with what I've done, perhaps it's what you meant.
DI Container is quite complex solution and looks like you don't need it just now. Getting ApplicationDbContext from HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>() should work for you just fine - only make sure this is the only way you create your dbContext. If you'd like to peek how DI container is implemented with identity, I've written a blog-post about it: tech.trailmax.info/2014/09/…
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.