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")
);
}
}
Related
I have a question.
I'm using ASP.NET CORE Identity(Individual User Accounts). It's already creating a separate database independent of my project. In my project, there are things I need to list that belong to the user. How can I do this ?
Database1-> ASP.NET CORE Identity Database (this database was created automatically)
Database2-> My App database
In Database2 Example Code;
public class Report:BaseModel
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime DateTime { get; set; }
public int UserId { get; set; } //(IT MUST BE ASP.NET USER TABLE)
public User User { get; set; } //(IT MUST BE ASP.NET USER TABLE)
}
First make sure that you have OnModelCreating on your DbContext class with base call
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
then add your class
public class Report : BaseModel
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime DateTime { get; set; }
public ApplicationUser User { get; set; }
}
then call migrations add-migration Report and Update-Database
I'm using a EF with .Net Core 2.2. And I cannot figurated out how to solve the following problem:
I have the PQL and PM fields, these fields are the key of a user (Based in the table User).
So, How I can create two relations of two fields to the same field (In the secondary table) using EF...
Example, a requirement is to create a query to get all projects per user, where the user be the PM or the PQL...
The main table is:
public class ProjectHeader
{
[Key]
public int IdProjectHeader { get; set; }
[StringLength(200)]
public string ProjectName { get; set; }
[ForeignKey("FK_IdUser")]
[Column("IdUser")]
public int PM { get; set; }
[ForeignKey("FK_IdUser")]
[Column("IdUser")]
public int PQL { get; set; }
// NOT LINK WITH USER TABLE CORRECTLY
public User User { get; set; }
}
The User table is:
public class User
{
[Key]
public int IdUser { get; set; }
[Required]
[StringLength(100)]
public string UserName { get; set; }
[Required]
[StringLength(50)]
public string ShortName { get; set; }
[Required]
[StringLength(50)]
public string Email { get; set; }
}
A link to a question similar to this one. I would suggest you to research and use Entity Framework's Fluent API. Basically in your context class (that inherits DbContext class) you override the OnModelCreating method like so:
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts);
}
}
Inside you can configure some advanced relations for your database. You can look it up here.
I solved the problem... on main table I included:
[ForeignKey("PM")]
public User PmUser { get; set; }
[ForeignKey("PQL")]
public User PqlUser { get; set; }
...using the User Entity for both fields (Using a Foreign Key - Equal to the field on the main table)...
so, the PM match with idUser... and PQL match with idUser
Now... I have a circular reference when I tried to update the database using Code-first...
So, in the class public class ApplicationDbContext : DbContext I override the foreign keys:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProjectHeader>().HasOne(m => m.PmUser).WithMany().OnDelete(DeleteBehavior.ClientSetNull);
modelBuilder.Entity<ProjectHeader>().HasOne(m => m.PqlUser).WithMany().OnDelete(DeleteBehavior.ClientSetNull);
}
I have an existing application that I am re-writing as .NET Core API with a ReactJS front-end. I am still in the API end, and I've run into a problem.
CODE
I have a BbUser.cs entity class with the following code:
public class BbUser : IdentityUser
{
public int Points { get; set; } = 0;
public string DisplayUsername { get; set; }
}
And I also have an Artist.cs entity class:
public class Artist
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(100)]
public string UrlFriendly { get; set; }
public string ImageUrl { get; set; }
public bool IsVerified { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public ICollection<Lyric> Lyrics { get; set; } = new List<Lyric>();
public string UserId { get; set; }
public BbUser User { get; set; }
}
I need a one-to-many relationship between BbUser and Artist. One user can submit many artists and lyrics ...etc. Simple stuff really.
PROBLEM
The application builds fine, but when I attempt to run it by hitting a controller that requires access to the Database, I get the following error:
The entity type 'IdentityUserLogin' requires a primary key to be defined.
I had this issues with regular EF Code First (not Core) and the fix for that, does not work here.
This model worked for me(compiled, and no exceptions at runtime) if I used next code in the DbContext class:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<BbUser>(b => b.ToTable("AspNetUsers"));
base.OnModelCreating(builder);
}
Without calling base.OnModelCreating(builder) I get the same error, because in this case context isn't applying the Identity related schema.
UPDATE:
Everything works fine for me as you can see from the screenshot below:
I have one more idea why you can have such an error. Did your BbContext inherit from DbContext class or IdentityDbContext<IdentityUser>? Because I got the same error that was on your screenshot if I used usual DbContext class.
In order to Idenity tables work fine you should use IdentityDbContext<IdentityUser>. Below the whole code for my working DbContext class
public class BbContext :IdentityDbContext<IdentityUser>
{
public BbContext(DbContextOptions options):base(options)
{
Database.EnsureCreated();
}
public DbSet<Artist> Artists { get; set; }
public DbSet<Lyric> Lyrics { get; set; }
public DbSet<Heart> Hearts { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<BbUser>(b => b.ToTable("AspNetUsers"));
builder.Entity<Heart>().HasKey(h => new {h.UserId, h.LyricId});
base.OnModelCreating(builder);
}
}
Please do try declaring a Guid property named Id, with both Get and Set on the IdentityUserLogin entity.
Another option is to declare a property and decorate it with [Key] attribute
I'm using EntityFramework with a link table and I have created a OnModelCreating that creates a link table called "RolePrivileges". But I have another dbcontext that uses the same database but with the relation the other way so I get this error:
Invalid object name 'dbo.PrivilegeRoles'
My Privilege class has a public virtual ICollection<Role> Roles { get; set; } property.
How can I tell EF that it's from RolePrivileges and not PrivilegeRoles?
UPDATE
I have added some code, this a big project and I am using repository pattern and all that. This is just to show the error
I have many projects, one for only generating the database that has all entities, migrations and all that.
in the dbcontext in this project I have
public class EasyhoursDbContext : IdentityDbContext<ApplicationUser>
{
...
public DbSet<Role> AccessRoles { get; set; }
public DbSet<Privilege> Privileges { get; set; }
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<Role>()
.HasMany(role => role.Privileges)
.WithMany(p => p.AccessRoles)
.Map(ap =>
{
ap.MapLeftKey("RoleId");
ap.MapRightKey("PrivilegeId");
ap.ToTable("RolePrivileges");
});
...
}
}
public class Role
{
[Key]
public string Id { get; protected set; }
public string Name { get; protected set; }
public virtual List<Privilege> Privileges { get; private set; }
}
public class Privilege
{
[Key]
public string Id { get; set; }
...
public virtual ICollection<Role> AccessRoles { get; set; }
}
And then in another project I have a dbcontext that just contains this
public class RoleDbContext : DbContext
{
public RoleDbContext()
: base("DefaultConnection")
{
}
public DbSet<Role> AccessRoles { get; set; }
public DbSet<Privilege> Privileges { get; set; }
}
public class Privilege
{
public string Id { get; set; }
...
[JsonIgnore]
[IgnoreDataMember]
public virtual ICollection<Role> AccessRoles { get; set; }
}
public class Role
{
public string Id { get; protected set; }
public string Name { get; protected set; }
public List<Privilege> Privileges { get; set; } = new List<Privilege>();
}
And here's an example where I get the error:
var db = new RoleDbContext();
var role = db.AccessRoles.FirstOrDefault(r => true);
var privilege = db.Privileges.FirstOrDefault(p => true);
role.Privileges.Add(privilege);
db.Entry(role).State = EntityState.Modified;
db.SaveChanges();
On save changes I get the error:
Running this exact same code, but with EasyhoursDbContext instead it works fine
UPDATE
I updated the RoleDbContext now to contain the exact same rule for the onmodelcreation as EasyhoursDbContext.
But now I'm getting this error:
Violation of PRIMARY KEY constraint 'PK_dbo.RolePrivileges'. Cannot insert duplicate key in object 'dbo.RolePrivileges'. The duplicate key value is (03fd67b6-277f-43f6-b276-5bafbdbe55af, A657a693-0961-Role-b86b-381261aApply).\r\nThe statement has been terminated.
I wanna thank Steve Greene for helping me with finding the problem.
All I needed was to add OnModelCreation to my RolesDbContext
modelBuilder.Entity<Role>()
.HasMany(role => role.Privileges)
.WithMany(p => p.AccessRoles)
.Map(ap =>
{
ap.MapLeftKey("RoleId");
ap.MapRightKey("PrivilegeId");
ap.ToTable("RolePrivileges");
});
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.