How to setup a many to many relationship - entity-framework-6

I had to change a one to many to a many to many relationship. With the former A user was assigned a companyId upon registering. The only documents they could return from the database was controlled with a where statement in my Web Api. Now that a User can be assigned many companies I need to change that where statement to do the same thing. So far I have created the junction table. I am having problems accessing it and returning it correctly.
Company Class
public class Company
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public int UserCompanyId { get; set; }
public virtual UserCompany UserCompany { get; set; }
}
UserClass
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity>
GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager
.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
public int UserCompanyId { get; set; }
public virtual UserCompany UserCompany { get; set; }
}
Junction Table
public class UserCompany
{
[Key]
public int UCompanyId { get; set; }
public string Id { get; set; }
public int CompanyId { get; set; }
}
ApiController
public IEnumerable<Document> GetDocuments()
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = manager.FindById(User.Identity.GetUserId());
using (var context = new ApplicationDbContext())
{
return context.UserCompanies
.Where(j => j.CompanyId == user.UserCompany.CompanyId)
.ToList();
}
}
The Error is coming at the .ToList()
Error 1 Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)
Update
// GET api/<controller>
public List<Document> GetDocuments()
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = manager.FindById(User.Identity.GetUserId());
using (var context = new ApplicationDbContext())
{
return context.Documents
.Where(j => j.CompanyId == user.UserCompany.UCompanyId)
.ToList();
}
}
Document Class
public class Document
{
public int DocumentId { get; set; }
public DateTime DocumentDate { get; set; }
public string DocumentUrl { get; set; }
public DateTime DocumentUploadDate { get; set; }
public string DocumentUploadedBy { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
I changed IEnumberable to List. I am still not doing it right,I am getting a error in my ApiController
Non-static method requires a target.
I posted my Document Class as well. I am lost on how to make this work. First time with a many to many relationship
Here is the stacktrace
at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
at System.Data.Entity.Core.Objects.ELinq.QueryParameterExpression.TryGetFieldOrPropertyValue(MemberExpression me, Object instance, Object& memberValue)
at System.Data.Entity.Core.Objects.ELinq.QueryParameterExpression.TryEvaluatePath(Expression expression, ConstantExpression& constantExpression)
at System.Data.Entity.Core.Objects.ELinq.QueryParameterExpression.EvaluateParameter(Object[] arguments)
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery1.<>c__DisplayClass7.b__6()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery1.<>c__DisplayClass7.b__5()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func1 operation)
at System.Data.Entity.Core.Objects.ObjectQuery1.GetResults(Nullable1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery1..GetEnumerator>b__0()
at System.Data.Entity.Internal.LazyEnumerator1.MoveNext()
at System.Collections.Generic.List1..ctor(IEnumerable1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
at TransparentEnergy.ControllersAPI.apiDocumentUserController.GetDocuments() in c:\Development\TransparentEnergy\TransparentEnergy\ControllersAPI\apiDocumentUserController.cs:line 29
at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.b__9(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)

I think it's because you're returning a List and the type your function return is IEnumerable, try:
public List<Document> GetDocuments()
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = manager.FindById(User.Identity.GetUserId());
using (var context = new ApplicationDbContext())
{
return context.UserCompanies
.Where(j => j.CompanyId == user.UserCompany.CompanyId)
.ToList();
}
}

Related

Complex custom user management identity asp.net core 3.1 running with breakpoints, and not running without breakpoints

I'm working on a new Visual Studio 2019 webapp project with asp.net core MVC 3.1, razor pages and scaffolded Identity.
I'm using a custom IdentityUser class
public class VivaceApplicationUser : IdentityUser
{
[PersonalData]
public string FirstName { get; set; }
[PersonalData]
public string LastName { get; set; }
//[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
[DataType(DataType.Date)]
[PersonalData]
public DateTime DateOfBirth { get; set; }
[PersonalData]
public Address Address { get; set; }
[PersonalData]
public string AdditionaInfos { get; set; }
[PersonalData]
public LoginUserStatus Status { get; set; }
[PersonalData]
public bool hasPaidQuote { get; set; }
[DataType(DataType.Date)]
[PersonalData]
public DateTime paidOnDate { get; set; }
[DataType(DataType.Date)]
[PersonalData]
public DateTime paidValidity { get; set; }
[DataType(DataType.Date)]
[PersonalData]
public DateTime registeredSince { get; set; }
}
public enum LoginUserStatus
{
Submitted,
Approved,
Rejected
}
This class use an Address class defined as follows:
public class Address
{
public int AddressID { get; set; }
public string City { get; set; }
public int PostalCode { get; set; }
public string Street { get; set; }
public string CivicNumber { get; set; }
}
My ApplicationDbContext class looks like that:
public class ApplicationDbContext : IdentityDbContext<VivaceApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
I can login in my webapp and create a new user.
In the database I can see the new user and his address as well (in the address table).
In the dbo.AspNetUsers I can also see the right AdressID.
My issue is now in deleting a user in the DeletePersonalDataModel class.
public class DeletePersonalDataModel : PageModel
{
private readonly UserManager<VivaceApplicationUser> _userManager;
private readonly SignInManager<VivaceApplicationUser> _signInManager;
private readonly ILogger<DeletePersonalDataModel> _logger;
private readonly ApplicationDbContext _context;
public DeletePersonalDataModel(
UserManager<VivaceApplicationUser> userManager,
SignInManager<VivaceApplicationUser> signInManager,
ILogger<DeletePersonalDataModel> logger,
ApplicationDbContext context)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_context = context;
}
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public Address Address { get; set; }
}
public bool RequirePassword { get; set; }
public async Task<IActionResult> OnGet()
{
// Get user
var user = await _userManager.GetUserAsync(User);
// Get user from _context
var userFromContext = await _context.Users.FindAsync(user.Id);
// Get user address from _context
var addressFromContext = await _context.Addresses.FindAsync(user.Address.AddressID);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (addressFromContext == null)
{
return NotFound($"Unable to load user address of user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
// Get user
var user = await _userManager.GetUserAsync(User);
// Get user address from _context
var addressFromContext = await _context.Addresses.FindAsync(user.Address.AddressID);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (addressFromContext == null)
{
return NotFound($"Unable to load user address with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
if (RequirePassword)
{
if (!await _userManager.CheckPasswordAsync(user, Input.Password))
{
ModelState.AddModelError(string.Empty, "Incorrect password.");
return Page();
}
}
var result = await _userManager.DeleteAsync(user);
var resultRemoveAddress = _context.Addresses.Remove(addressFromContext);
var userId = await _userManager.GetUserIdAsync(user);
if (!result.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'.");
}
//if (!resultRemoveAddress.)
//{
// throw new InvalidOperationException($"Unexpected error occurred deleting user address of user with ID '{userId}'.");
//}
await _context.SaveChangesAsync();
await _signInManager.SignOutAsync();
_logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
return Redirect("~/");
}
}
I can get the current (logged) user from the _userManager class in the OnGet() method
// Get user
var user = await _userManager.GetUserAsync(User);
I can also get the current (logged) user from the _contect class in the OnGet() method
// Get user from _context
var userFromContext = await _context.Users.FindAsync(user.Id);
And I can get the user's address from the _context class as well in the OnGet() method
// Get user address from _context
var addressFromContext = await _context.Addresses.FindAsync(user.Address.AddressID);
Setting a breakpoint on
// Get user
var user = await _userManager.GetUserAsync(User);
in the OnGet() method and waiting a couple of seconds I see this
But if I run without breakpoints I get an unhandled exception
What I'm doing wrong? And how to solve this issue?
If I run with breakpoints the database gets updated correctly. Both the user and his address get deleted.
Thanks very much!

Always load entity for ApplicationUser (.NET MVC, Identity)

I am having problems loading a entity that I have assigned to the ApplicationUser in my .NET core MVC application.
I have added one of my entities to the user class, see code below:
public class ApplicationUser : IdentityUser
{
public int? AzureBlobResourceId { get; set; }
[ForeignKey("AzureBlobResourceId")]
public AzureBlobResource AzureBlobResource { get; set; }
}
Ideally I want the AzureBlobResource object to be loaded when retrieving the user from the UserManager
private Task<ApplicationUser> GetCurrentUserAsync()
{
return _userManager.GetUserAsync(HttpContext.User);
}
Unfortunately though the AzureBlobResource object always is null, even when the AzureBlobResourceId has a value.
What am I missing here?
Thanks, Nikolai
You need to implement your userstore
public class ApplicationUser : IdentityUser {
public int? AzureBlobResourceId { get; set; }
[ForeignKey("AzureBlobResourceId")]
public AzureBlobResource AzureBlobResource { get; set; }
}
public class MyAppUserStore : UserStore<ApplicationUser>
{
public MyAppUserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
public override async Task<ApplicationUser> FindByIdAsync(string userId, CancellationToken cancellationToken = new CancellationToken())
{
return await Context.Set<ApplicationUser>().Include(p => p.AzureBlobResource).FirstOrDefaultAsync(u => u.Id == userId, cancellationToken: cancellationToken);
}
}
And in Sturtup.cs add
ervices.AddIdentity<ApplicationUser, IdentityRole>()
.AddUserStore<MyAppUserStore >()
.AddUserManager<UserManager<ApplicationUser>>()
.AddDefaultTokenProviders();

How to make EF6 Include() subclass navigation properties for a List of items

I am trying to select a list of Invoices and their subclassed items.
Here are the models:
public class SalesContext : DbContext {
public DbSet<Product> Products { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceItem> InvoiceItems { get; set; }
public SalesContext() : base ("DefaultConnection") {
}
}
public class Invoice {
public int Id { get; set; }
public List<InvoiceItem> Items { get; set; }
public Invoice() {
Items = new List<InvoiceItem>();
}
}
public abstract class InvoiceItem {
public int Id { get; set; }
public int Qty { get; set; }
}
public class ProductInvoiceItem : InvoiceItem {
public int ProductId { get; set; }
public Product Product { get; set; }
}
public class Product {
public int Id { get; set; }
public string Name { get; set; }
}
Here's the seed data:
internal sealed class Configuration : DbMigrationsConfiguration<SalesContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(SalesContext context) {
context.Products.AddOrUpdate(p => p.Name,
new Product {Name = "Bible - New Living Translation"},
new Product {Name = "Bible - King James Version"}
);
context.SaveChanges();
context.Invoices.AddOrUpdate(new Invoice {
Items =
new List<InvoiceItem>() {
new ProductInvoiceItem {ProductId = 1},
new ProductInvoiceItem {ProductId = 2}
}
}
, new Invoice {
Items =
new List<InvoiceItem>() {
new ProductInvoiceItem {ProductId = 1}
}
}
);
context.SaveChanges();
}
}
Here, I'm trying to select the data out with its associated subclass properties.
public class SalesController : Controller
{
private SalesContext db = new SalesContext();
// GET: Sales
public ActionResult Index() {
var query = db.Invoices.Include(i => i.Items);
var query2 = query.Include(i => i.Items.OfType<ProductInvoiceItem>().Select(pi => pi.Product));
var list = query2.ToList();
return View(list);
}
}
In the above code sample, query2 is broken. It throws a runtime exception.
System.ArgumentException was unhandled by user code
HResult=-2147024809 Message=The Include path expression must refer
to a navigation property defined on the type. Use dotted paths for
reference navigation properties and the Select operator for collection
navigation properties. Parameter name: path ParamName=path
Source=EntityFramework StackTrace:
at System.Data.Entity.QueryableExtensions.Include[T,TProperty](IQueryable1
source, Expression1 path)
at ParentChildEntityFrameworkStuff.Web.Controllers.SalesController.Index()
in c:\users\brandon.miller\documents\visual studio
2015\Projects\ParentChildEntityFrameworkStuff\ParentChildEntityFrameworkStuff.Web\Controllers\SalesController.cs:line
20
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext
controllerContext, IDictionary2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext
controllerContext, ActionDescriptor actionDescriptor, IDictionary2
parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.ActionInvocation.InvokeSynchronousActionMethod()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.b__39(IAsyncResult
asyncResult, ActionInvocation innerInvokeState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult2.CallEndDelegate(IAsyncResult
asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase1.End()
at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult
asyncResult, Object tag)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult
asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.b__3d()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.b__3f()
InnerException:
If I use query and get rid of the query2 stuff, it returns data, but of course leaves out the Product on the ProductInvoiceItems. The ProductId field is set, so I know it is getting at least the value types for the ProductInvoiceItems. I've tried and tried and searched and searched and have been unable to find a solution for this, arguably, common use-case.
What can I do in order to eagerly load the Product navigation property?

On updating a record in EF

I am using EF code first in my asp.net mvc project. I want an updated_at(DateTime) column in every table. So if I update a record, I want the column value to be set to current DateTime.
public abstract class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Required]
public string name { get; set; }
[Required]
public string email { get; set; }
[Required]
public string password { get; set; }
private DateTime _created_at;
private DateTime _updated_at;
public User()
{
_created_at = DateTime.Now;
_updated_at = DateTime.Now;
}
public virtual DateTime created_at
{
get
{
return this._created_at;
}
}
public virtual DateTime updated_at
{
get
{
return this._updated_at;
}
}
}
How Do I make that happen in EF without writing a TRIGGER in database directly.
In you DbContext constructor use the following:
public MyDbContext()
{
((IObjectContextAdapter)this).ObjectContext.SavingChanges += ObjectContext_SavingChanges;
}
void ObjectContext_SavingChanges(object sender, EventArgs e)
{
// Ensure that we are passed an ObjectContext
ObjectContext context = sender as ObjectContext;
if (context != null)
{
// Set DateTime to UTC when
foreach (ObjectStateEntry entry in
context.ObjectStateManager.GetObjectStateEntries(
EntityState.Modified))
{
if (entry.Entity.GetType().GetProperty("_updated_at") != null)
{
dynamic entity = entry.Entity;
entity._updated_at = DateTime.UtcNow;
DateTime.SpecifyKind(entity._updated_at, DateTimeKind.Utc);
}
}
}
}
You should use an interface to define how you want to handle timestamping information in your classes. You can use this same technique for many different things.
public interface ITimeStampable
{
DateTime created_at {get; set;}
DateTime updated_at {get; set;}
}
public class User : ITimeStampable
{
public User(){
created_at = DateTime.Now;
updated_at = DateTime.Now;
}
public DateTime created_at {get; set;}
public DateTime updated_at {get; set;}
}
void ObjectContext_SavingChanges(object sender, EventArgs e)
{
var context = sender as ObjectContext;
foreach (ObjectStateEntry entry in
context.ObjectStateManager.GetObjectStateEntries(
EntityState.Modified))
{
if (typeof(IAuditable).IsAssignableFrom(entry.Entity.GetType()))
(entity as IAuditable).ChangeTS = DateTime.Now;
}
}

Problems implementing IPrincipal

Trying to implement IPrincipal (ASP.NET MVC 3) and having problems:
my custom IPrincipal:
interface IDealsPrincipal: IPrincipal
{
int UserId { get; set; }
string Firstname { get; set; }
string Lastname { get; set; }
}
public class DealsPrincipal : IDealsPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public DealsPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public int UserId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
To serialize/deserialize i use the following class:
public class DealsPrincipalSerializeModel
{
public int UserId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
The Application authenticate event is as follows (works fine!)
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//get the forms ticket
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
//instantiate a new Deserializer
JavaScriptSerializer serializer = new JavaScriptSerializer();
//deserialize the model
DealsPrincipalSerializeModel serializeModel = serializer.Deserialize<DealsPrincipalSerializeModel>(authTicket.UserData);
//put the values of the deserialized model into the HttpContext
DealsPrincipal newUser = new DealsPrincipal(authTicket.Name); //this implements IPrincipal
newUser.UserId = serializeModel.UserId;
newUser.Firstname = serializeModel.Firstname;
newUser.Lastname = serializeModel.Lastname;
HttpContext.Current.User = newUser;
}
}
As you can see in the last statement the HttpContext gets assigned this new DealsPrincipal (which works fine).
The problem is that if want to access this User in a Controller(Action) i always get a base class object. If i cast the User as follows:
User as DealsPrincipal
to get for example the UserId (sample:
( User as DealsPrincipal).UserId
this is always null!!! Why? What am i missing?
I would need to investigate more to give you correct answer but look this part of the code and it could help you (part of the source of WindowsAuthenticationModule.cs)
void OnAuthenticate(WindowsAuthenticationEventArgs e) {
////////////////////////////////////////////////////////////
// If there are event handlers, invoke the handlers
if (_eventHandler != null)
_eventHandler(this, e);
if (e.Context.User == null)
{
if (e.User != null)
e.Context.User = e.User;
else if (e.Identity == _anonymousIdentity)
e.Context.SetPrincipalNoDemand(_anonymousPrincipal, false /*needToSetNativePrincipal*/);
else
e.Context.SetPrincipalNoDemand(new WindowsPrincipal(e.Identity), false /*needToSetNativePrincipal*/);
}
}
From this code I would suggest you to check if user is anonymous before assigning instance of your custom IPrincipal inmplementation. Also, not sure if this method is executed before or after "protected void Application_AuthenticateRequest". Will try to take more time to investigate this.
Also, please look at this article:
http://msdn.microsoft.com/en-us/library/ff649210.aspx

Resources