Entity Framework relationship to IdentityUser not resolving - c#

I have a class named "MyFile" that has a Many-to-One relationship to "Workspace" and then the "Workspace" has a Many-to-One relationship to "IdentityUser". This works well when I create a Workspace the relationship to the IdentityUser is configured correctly, however when I fetch the Workspace the Owner field show up as null. In the database the value is set in the Owner column.
So what I want to do is get a list of All Files and whom they belong to, but since the Owner property is null I'm not able to figure out the owner. Database wise all looks good.
(This code has been simplified to focus on the problem)
public class MyFile
{
// Base
public Guid MyFileID { get; set; }
[Column(TypeName = "nvarchar(256)")]
public string Name { get; set; }
// Workspace
public virtual Workspace Workspace { get; set; }
}
public class Workspace
{
// Base
public Guid WorkspaceID { get; set; }
public string Name { get; set; }
// Security
public virtual IdentityUser Owner { get; set; }
}
Code to get the information that ends up with Owner = Null:
var myFiles = _applicationDbContext.MyFiles
.Include(x => x.Workspace)
.ThenInclude(y => y.Owner)
.Where(x => x.Deleted == showDeleted)
.OrderByDescending(x => x.Uploaded)
.Skip(pagesize*(page-1))
.Take(pagesize);

I managed to solve the issue by extending the IdentityUser class with ApplicationUser and then change the relationship to ApplicationUser instead.
Also i added a ApplicationUserId which I populate at the same time as I Populate the ApplicationUser then I could use to lookup the correct IdentityUser in my lamdba. There is probably a better solution but this worked for me.
public class Workspace
{
// Base
public Guid WorkspaceID { get; set; }
public string Name { get; set; }
// Security
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
Extension Class:
public class ApplicationUser : IdentityUser
{
}

Related

Object is null when configuring one to one relationship with fluent api

I have three classes: Role, Permission and RolePermission(role permission is the third table in a many to many relationship)
public class Role : Entity
{
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}
public class Permission : Entity
{
public string Name { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}
public class RolePermission : Entity
{
public int RoleId { get; set; }
public int PermissionId { get; set; }
public Permission Permission { get; set; }
public Role Role { get; set; }
}
Then I used fluentAPI in order to configure relationship:
For Role:
HasMany(role => role.RolePermissions)
.WithRequired(rolePermission => rolePermission.Role)
.HasForeignKey(rolePermission => rolePermission.RoleId);
For Permission:
HasMany(permission => permission.RolePermissions)
.WithRequired(rolePermission => rolePermission.Permission)
.HasForeignKey(rolePermission => rolePermission.PermissionId);
For RolePermission:
HasRequired(rolePermission => rolePermission.Permission)
.WithMany(permission => permission.RolePermissions)
.HasForeignKey(rolePermission => rolePermission.PermissionId);
HasRequired(rolePermission => rolePermission.Role)
.WithMany(role => role.RolePermissions)
.HasForeignKey(rolePermission => rolePermission.RoleId);
The problem is that only Role object is populated.
The code in this question pertains to setting up a relationship. The reported issue in this question pertains to related data not being loaded automatically. These are two different things that have little to do with one another.
Did you miss an Include somewhere? Have you accessed (and therefore lazily loaded) the Role nav prop, but not the Permission nav prop? I would like to see the code starting from where you launch the query up to where you inspect this object (as per your screenshot)
You responded with the requested code:
var user = _userRepository
.FirstOrDefaultAsync(us => us.Email == email);
var userPermissions =
user.UserRoles
.First()
.Role
.RolePermissions
.Select(rp => rp.Permission)
.ToList();
If you insert an Include() statement in your query, you will see that the Permission will actually be fetched correctly.
I am not quite sure which object you're inspecting. The screenshot tells me you're looking at a RolePermission, but the posted code suggests that you fetch a list of Permission objects.
Regardless, you seem to already have fixed it using an Include:
Mihai Alexandru-Ionut #Flater, yes, I have to use include and the problem is solved. This is the solution, so please post it as an answer in order to accept it.
Id Property is missing for both Role and Permission tables. When you say RoleId property in RolePermission table, EF looks for Id Property in Role table.
Update your Role and Permission tables like this and give a try:
public class Role : Entity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}
public class Permission : Entity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}

Loading custom ApplicationUser object from database

I am using ASP.NET Core 2.0 with Identity and Entity Framework. I have extended the IdentityUser class into the ApplicationUser below:
namespace TxTools.Data.Features.Shared.Models
{
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("PhotoResourceId")]
public BlobResource Photo { get; set; }
}
}
Here is my BlobResource:
namespace TxTools.Data.Features.BlobStorage.Models
{
public class BlobResource
{
[Key]
public Guid ResourceId { get; protected set; }
public string Container { get; protected set; }
public string MimeType { get; protected set; }
public string Filename => String.Format("{0}.{1}", ResourceId, MimeTypes.GetExtension(MimeType));
public BlobResource(string container, string mimeType)
{
this.ResourceId = Guid.NewGuid();
this.Container = container;
this.MimeType = mimeType;
}
}
}
Entity Framework saves the BlobResource when I add it to the ApplicationUser, but I cannot get it to load the BlobResource from the database. The object is always null. I have tried several Fluent API commands to try and get it to load, but none work.
Firstly, the User and BlobResource classes need references to one another. You need to decide whether the relation is one-to-one (one user one photo), one-to-many (one user many photos, or many users one photo) or many-to-many.
Entity Framework's way of describing relationships is here: https://learn.microsoft.com/en-us/ef/core/modeling/relationships
Secondly, EF is lazy-loading so you have to tell it to chase these relations when you load users from the database. Assuming you have a DB context class that extends IdentityDbContext...
public class MyContext: IdentityDbContext<ApplicationUser>
{
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<BlobResource> BlobResources { get; set; }
}
... you would use Include as follows:
var usersWithBlobs = myContext.Users.Include(user => user.Photo);
or a specific user given its id:
var myUser = myContext.Users.Where(u => u.Id == id).Include(user => user.Photo);
Loading is described here: https://learn.microsoft.com/en-us/ef/core/querying/related-data

Multiple relationships to single table Entity Framework asp.net MVC

I've just started using Entity Framework for my next project and I'm struggling with the following. I have the following ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
I have two classes that inherent from this class:
public class TrainerUser : ApplicationUser
{
public virtual ICollection<ClientUser> Clients { get; set; }
}
public class ClientUser : ApplicationUser
{
public string TrainerId { get; set; }
public TrainerUser Trainer { get; set; }
}
The company class looks like this:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TrainerUser> Trainers { get; set; }
public virtual ICollection<ClientUser> Clients { get; set; }
}
What I can't figure out is how I can use the fluent API to not include 3 different companyId columns in the ApplicationUsers table.
Currently I have the following fluent API configuration:
modelBuilder.Entity<TrainerUser>().HasRequired(c => c.Company).WithMany(t => t.Trainers).HasForeignKey(c => c.CompanyId);
modelBuilder.Entity<ClientUser>().HasRequired(c => c.Company).WithMany(c => c.Clients).HasForeignKey(c => c.CompanyId);
Can anyone point me in the right direction?
Try adding these to your code.
modelBuilder.Entity<TrainerUser>().ToTable("TrainerUser");
modelBuilder.Entity<ClientUser>().ToTable("ClientUser");
If I am getting you right. you are trying to create a structure representing Table Per Hierarchy (TPT). Read more about it at the link.
Basically what happens is when entity framework encounters inheritance in the entities. Its Default attempt to create tables is by creating column of the set of all properties of all the derived entities from a class with a discriminator column.
What you are trying to create is a separate table for every class in the hierarchy.

Customizing SimpleMembership

After reading this good BLOG about adding custom data to UserProfile table, I wanted to change it the way that UserProfile table should store default data + another class where all additional info is stored.
After creating new project using Interenet application template, I have created two classes:
Student.cs
[Table("Student")]
public class Student
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public virtual int StudentId { get; set; }
public virtual string Name { get; set; }
public virtual string Surname { get; set; }
public virtual int UserId { get; set; }
}
UserProfile.cs
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual int StudentId { get; set; }
public virtual Student Student { get; set; }
}
also, I've deleted the UserProfile definition from AccountModel.cs. My DB context class looks like this:
MvcLoginDb.cs
public class MvcLoginDb : DbContext
{
public MvcLoginDb()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Student> Students { get; set; }
}
again, I have deleted db context definition from AccountModel.cs.
Inside Package-Manager-Console I've written:
Enable-Migrations
and my Configuration.cs looks like this:
internal sealed class Configuration : DbMigrationsConfiguration<MvcLogin.Models.MvcLoginDb>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
protected override void Seed(MvcLogin.Models.MvcLoginDb context)
{
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
if (!WebSecurity.UserExists("banana"))
WebSecurity.CreateUserAndAccount(
"banana",
"password",
new
{
Student = new Student { Name = "Asdf", Surname = "Ggjk" }
});
}
}
That was the idea of adding student data as creating a new class, but this approach is not working because after running Update-Database -Verbose I'm getting the error:
No mapping exists from object type MvcLogin.Models.Student to a known managed provider native type.
Can anyone expain why I'm getting this error, shoud I use a different approach for storing additional data in different table?
I struggled a lot with the same issue. The key is to get the relation between the UserProfile class and your Student class correct. It have to be a one-to-one relationship in order to work correct.
Here is my solution:
Person.cs
public class Person
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
/* and more fields here */
public virtual UserProfile UserProfile { get; set; }
}
UserProfile.cs
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual Person Person { get; set; }
}
MyDbContext.cs
public class MyDbContext : DbContext
{
public DbSet<Person> Person { get; set; }
public DbSet<UserProfile> UserProfile { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProfile>()
.HasRequired(u => u.Person)
.WithOptional(p => p.UserProfile)
.Map(m => m.MapKey("PersonId"));
}
}
However, I also struggled with creating users with the WebSecurity.CreateUserAndAccount method. So I ended up creating my Person objects with Entity Framework and then create the user account with the membership provider method:
AccountRepository.cs
public Person CreatePerson(string name, string username) {
Person person = new Person { Name = name };
_dbContext.Person.add(person);
_dbContext.SaveChanges();
var membership = (SimpleMembershipProvider)Membership.Provider;
membership.CreateUserAndAccount(
model.UserName,
createRandomPassword(),
new Dictionary<string, object>{ {"PersonId" , person.Id}}
);
}
HenningJ, I'm searching for an answer to the same thing. Unfortunately (I'm sure you're aware) the problem with the way you did this is that there is no SQL transaction. Either your SaveChanges() or CreateUserAndAccount() could fail leaving your user database in an invalid state. You need to rollback if one fails.
I'm still searching for the final answer, but I'll post whatever solution I end up going with. Hoping to avoid having to write a custom membership provider (AGAIN!), but starting to think it would be faster.

Relationship troubles with Entity Framework

I need help creating the relationship in entity framework as everything I have tried gives me errors when trying to add the migration or if I get passed that then I try to update the database and get an error about indexes with the same name.
public class Profile
{
public Profile()
{
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string VersionCreated { get; set; }
public string DiskLocation { get; set; }
public string Name { get; set; }
public DateTime DateTime { get; set; }
public virtual Product Product { get; set; }
public virtual Instance OriginalInstance { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
public class Instance
{
public Instance()
{
TestResults = new HashSet<TestResult>();
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string UserFriendlyName { get; set; }
public virtual Product Product { get; set; }
public virtual Profile LastKnownProfile { get; set; }
public virtual Computer Computer { get; set; }
public virtual ICollection<TestResult> TestResults { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
The problem with the above classes is that the OrginalInstance property on the Profile class and the LastKnownProfile in the Instance class are supposed to just be foreign keys to those specific tables and they probably won't be the same very often. They can also both possibly be null.
I have tried:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile);
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance);
This gave me an Unable to determine the principal end of an association between the types 'EcuWeb.Data.Entities.Instance' and 'EcuWeb.Data.Entities.Profile'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. error.
and with:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile).WithOptional();
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance).WithOptional();
The database adds a foreign key reference back to itself.
...that the OrginalInstance property on the Profile class and the
LastKnownProfile in the Instance class are supposed to just be foreign
keys to those specific tables and they probably won't be the same very
often. They can also both possibly be null.
In this case you actually want two one-to-many relationships between Profile and Instance if I don't misunderstand your quote above. It would mean that many Profiles can have the same OriginalInstance and that many Instances can have the same LastKnownProfile. The correct mapping would look like this then:
modelBuilder.Entity<Profile>()
.HasOptional(p => p.OriginalInstance)
.WithMany()
.Map(m => m.MapKey("OriginalInstanceId"));
modelBuilder.Entity<Instance>()
.HasOptional(i => i.LastKnownProfile)
.WithMany()
.Map(m => m.MapKey("LastKnownProfileId"));
The lines with MapKey are optional. Without them EF will create a foreign key with a default name.
Also note that you must use HasOptional (instead of HasRequired) if "both can possibly be null".

Categories