Loading custom ApplicationUser object from database - c#

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

Related

Entity Framework relationship to IdentityUser not resolving

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

Entity Framework - inheriting from model

I'm new to the Entity Framework and I've followed tutorials online to create my SQL Server database and made several models and a context class to include properties to access them.
This is my account model:
public class Account
{
public int ID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
This is my context class:
public class DashContext : DbContext
{
public DashContext()
: base(Constants.ConnectionString)
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<DashContext>(null);
}
public DbSet<Account> Accounts { get; set; }
}
This works - when I access the DbSet property I can access all the account entires in my database.
However, I want to create an implmentation of the Account class that contains more properties than just columns because it has to interact with my program.
So, I tried to do the following:
public class GameAccount : Account
{
public int SomeSpecialProperty { get; set; }
}
However, when I'm using my context class to get the Account object, I'm not sure how to convert it to GameAccount. I know I can create a constructor that copies the properties from Account to GameAccount, like this:
public class GameAccount
{
public int ID { get; private set; }
public string Username { get; private set; }
public string Password { get; private set; }
public GameAccount(Account model)
{
this.ID = model.ID;
this.Username = model.Username;
this.Password = model.Password;
}
}
...but that seems a bit inefficent to me and I'm sure there's a simpler way.
What do you think?
You have a few options:
Option 1
Use a partial class as indicated by Fruchtzwerg.
Option 2
You can use AutoMapper to map the items from one type to the other. Here is an example:
// Notice the ReverseMap call. That will allow mapping in both directions.
Mapper.Initialize(cfg =>
cfg.CreateMap<Account, GameAccount>().ReverseMap());
var account = Mapper.Map<Account>(new GameAccount());
var gameAccount = Mapper.Map<GameAccount>(account);
Copy Constructors could be very costly to develop and maintenance. Typically generated classes of the Entity Framework are partial.
BradleyDotNET explains:
When code is generated; you don't want your additional methods/properties/whatever blown away, so the designers mark such classes partial to allow users to put additional code in a different file.
So a possible approach is extending the class
public partial class Account
{
public int ID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
with additional properties like
public partial class Account
{
public int SomeSpecialProperty { get; set; }
}

How to Specify Entity Framework Core Table Mapping?

I've made a simple Entity Framework ASP Core Application that works but I do not know why:
I've made a context like this:
public class AstootContext : DbContext
{
public AstootContext(DbContextOptions<AstootContext> options)
: base(options)
{ }
public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; }
}
And I have two tables with models like this:
public class Account
{
public int Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public DateTime Created { get; set; }
List<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime Birthday { get; set; }
public Account Account { get; set; }
}
The interesting thing is that when I run my application it actually can pick up the data. It just seems weird because I have not specified any table mapping.
I'm assuming this just automaps because the specified tables are the same name.
My questions are:
How do I specify Table explicit table mapping in case I do not want my model names to be exactly the same as the DB?
How do I specify Custom Column Mapping.
Is there anything special I have to specify for Primary/Foreign Keys
edit
To clarify
Say I had a table in the DB MyAccounts and I wanted to map that to an entity Accounts.
Say I had a column password and I wanted that to map to a POCO property PasswordHash
To specify the name of the database table, you can use an attribute or the fluent API:
Using Attributes:
[Table("MyAccountsTable")]
public class Account
{
public string PasswordHash { get; set; }
}
Using Fluent API:
public class YourContext : DbContext
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Language>(entity => {
entity.ToTable("MyAccountsTable");
});
}
}
To name your columns manually, it's very similar and you can use an attribute or the fluent API:
Using Attributes:
public class Account
{
[Column("MyPasswordHashColumn")]
public string PasswordHash { get; set; }
}
Using Fluent API:
public class YourContext : DbContext
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Language>(x => x
.ToTable("MyAccountsTable")
.Property(entity => entity.PasswordHash)
.HasColumnName("MyPasswordHashColumn")
);
}
}

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.

Categories