当fk与pk - EntityFramework不同时,如何链接一对一的关系 [英] How to link one-to-one relationship when fk is different from pk - EntityFramework
问题描述
我正在尝试与我的用户表添加一对一的关系,但是fk与pk不同
- 用户表[列]
- 用户名
- 密码
- 学校代码
- userId [pk]
ASPNETUsers表[标准列]
- Id ,[pk]
- UserName,
- PasswordHash,
- SecurityStamp
- 鉴别器
- 电子邮件
- EmailConfirmed,
- PhoneNumber,
- PhoneNumberConfirmed,
- TwoFactorEnabled,
- LockoutEndDateUtc,
- LockoutEnabled,
- AccessFailedCount
- userId [fk to users table]
我已经在 ApplicationUser 类中添加了属性
public cla ss ApplicationUser:IdentityUser
{
public virtual User user {get;组; }
public int UserID {get;组; }
}
public class ApplicationDbContext:IdentityDbContext< ApplicationUser>
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity< ApplicationUser>()
.HasRequired(i => i.user).WithRequiredDependent(i => i.appUser);
}
}
public class User
{
public int UserID {get;组; }
public string Username {get;组; }
public string Password {get;组; }
public virtual ApplicationUser appUser {get;组; }
}
我还需要做什么,是否正确?我们已经尝试在这里解释我的应用程序结构:
更新
运行时会给我错误
MySql.Data.MySqlClient.MySqlException:未知列'Extent8.appUser_Id'in'on条款'
如果我定义
modelBuilder.Entity< ApplicationUser> ().HasKey(u => u.UserID);
内部 OnModelCreating ,然后尝试通过此链接将AspNetRoles和Claims etc表链接UserID和thecourse崩溃。
ApplicationUser_Claims_Source_ApplicationUser_Claims_Target::引用约束的从属角色中的所有属性的类型必须与主体角色中相应的属性类型相同。实体IdentityUserClaim上的属性UserId的类型与引用约束ApplicationUser_Claims中的实体ApplicationUser上的属性UserID的类型不匹配。
ApplicationUser_Logins_Source_ApplicationUser_Logins_Target::引用约束的从属角色中的所有属性的类型必须与主体角色中相应的属性类型相同。实体IdentityUserLogin上的属性UserId的类型与引用约束ApplicationUser_Logins中的实体ApplicationUser上的属性UserID的类型不匹配。
ApplicationUser_Roles_Source_ApplicationUser_Roles_Target:参考约束的从属角色中的所有属性的类型必须与主体角色中相应的属性类型相同。实体IdentityUserRole上的属性UserId的类型与引用约束ApplicationUser_Roles中实体ApplicationUser上的属性UserID的类型不匹配。
EF的一对一关系如何工作,简而言之...
当使用一个关系,实体框架使用父项的 Id
列作为父项的FK,它不会为FK创建第二列,因为这将被认为是一对多关系,因为第二列将允许父项 Id
存在于许多子条目中,而PK作为FK
如何配置两个键?
由于您的 ApplicationUser
继承自 IdentityUser
,它将使用它现有的属性和关系。它已经定义了一个 Id
列,因此创建 UserId
不是必需的,而且尝试使 UserId
键属性。您应该使用现有的 Id
列,默认情况下为 string
,它显示您现有的用户
表有一个整数
PK,所以它们不匹配。
然而,这不是世界的尽头。 Identity的内置实现使用 string
作为他们的类(
所以...我该怎么办?
身份为每个表定义两个类,所以有两个 IdentityUser
类,两个 IdentityRole
等等。一个是基类泛型类,它接受一些泛型。另一个是该基础的内置实现,它提供了这些通用类型。
这个基类看起来像这样:
public class IdentityUser 其中TLogin:IdentityUserLogin< TKey>
其中TRole:IdentityUserRole< TKey>
其中TClaim:IdentityUserClaim< TKey>
第一个通用类型是 TKey
,其中定义键的类型,即 string
。第二个是一个 TLogin
,它是一个 IdentityUserLogin< TKey>
,这意味着它应该是继承 IdentityUserLogin
使用相同的 TKey
类型(可能 string
也许一个 int
)。
同时,这些基类的内置实现如下所示: p>
public class IdentityUser
>,反之亦然。
:IdentityUser< string,IdentityUserLogin,IdentityUserRole,IdentityUserClaim> IUser,IUser< string>其中将TKey
定义为
string
用作关键字。像$IdentityUserLogin
和IdentityUserRole
的其他泛型是内置的实现基类的类,一个string
键。看看如何IdentityUserRole
看起来像:public class IdentityUserRole IdentityUserRole<串GT; {}
看到?
string
以及。许多其他类使用string
作为TKey
的默认值。
所以如果你这样改变你的类:
public class ApplicationUser:IdentityUser< int,IdentityUserLogin,IdentityUserRole,IdentityUserClaim>
{
public virtual User user {get;组; }
}
它还不行,因为我们更改了
TKey
为int
,但内置的IdentityUserLogin
仍然使用字符串
。因此,我们需要创建自己的IdentityUserLogin
,如下所示:public class MyNiceUserLogin:IdentityUserLogin< int>
{
}
...并使用如下: p>
public class ApplicationUser:IdentityUser< int,MyNiceUserLogin,IdentityUserRole,IdentityUserClaim>
{
public virtual User user {get;组; }
}
...但是我们还要和其他类一样做像
IdentityUserRole
和IdentityUserClaim
。
我将其配置为使用
int
。现在,一对一的关系怎么样?
你的代码在这里:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity< ApplicationUser>()
.HasRequired(i => i.user).WithRequiredDependent(i => i.appUser);
}
...工作正常。您正在以必须的方式进行配置,没有
用户
ApplicationUser
现在,我建议您阅读这个Stack Overflow答案,以确切地知道要改变Identity的步骤
我也强烈推荐
/ strong>你阅读这篇文章 by John Atten,所以你可以深入了解如何自定义/扩展身份。
I have a custom users table in my database and i want to create a one-to-one relationship with aspnetusers table so that when i register a new customer, applicationuser class through UserManager should add the username, email, password and school code to the users table and add an fk in its own table. IS there any tutorial/example implementing this kind of scenario?
I am trying to add a one-to-one relationship with my users table but the fk is different than pk
- Users Table [columns]
- username
- password
- school code
- userId [pk]
ASPNETUsers Table [standard columns]
- Id, [pk]
- UserName,
- PasswordHash,
- SecurityStamp,
- Discriminator,
- Email,
- EmailConfirmed,
- PhoneNumber,
- PhoneNumberConfirmed,
- TwoFactorEnabled,
- LockoutEndDateUtc,
- LockoutEnabled,
- AccessFailedCount
- userId [fk to users table]
I have added property inside ApplicationUser class
public class ApplicationUser : IdentityUser
{
public virtual User user { get; set; }
public int UserID { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasRequired(i => i.user).WithRequiredDependent(i => i.appUser);
}
}
public class User
{
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ApplicationUser appUser { get; set; }
}
What else do i need to do and is it correct? How does entityframework work with this one?
I have tried to explain My application structure here:
UPDATE On running it gives me error MySql.Data.MySqlClient.MySqlException: Unknown column 'Extent8.appUser_Id' in 'on clause'
If I define
modelBuilder.Entity<ApplicationUser>().HasKey(u => u.UserID);
inside OnModelCreating then it tries to link AspNetRoles and Claims etc tables through this UserID and ofcourse crashes.
ApplicationUser_Claims_Source_ApplicationUser_Claims_Target: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'UserId' on entity 'IdentityUserClaim' does not match the type of property 'UserID' on entity 'ApplicationUser' in the referential constraint 'ApplicationUser_Claims'. ApplicationUser_Logins_Source_ApplicationUser_Logins_Target: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'UserId' on entity 'IdentityUserLogin' does not match the type of property 'UserID' on entity 'ApplicationUser' in the referential constraint 'ApplicationUser_Logins'. ApplicationUser_Roles_Source_ApplicationUser_Roles_Target: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'UserId' on entity 'IdentityUserRole' does not match the type of property 'UserID' on entity 'ApplicationUser' in the referential constraint 'ApplicationUser_Roles'.
How EF's one-to-one relationship works, in short...
When working with one-to-one relationship, Entity Framework uses the parent's Id
column in the child as the FK to the parent, it won't create a second column for FK because that would be considered one-to-many relationship, because that second column would allow a parent Id
to exist in many child entries, whereas the PK as FK wouldn't.
How should I configure both keys?
Since your ApplicationUser
inherits from IdentityUser
, it will use it's existing properties and relationships. It already defines an Id
column, so creating UserId
is not necessary, as well as trying to make UserId
the key property. You should use the existing Id
column, which by default is string
and it looks your existing User
table has an integer
PK, so they don't match.
However, this is not the end of the world. Identity's built-in implementation uses string
as key for their classes (AspNetUsers, AspNetRoles, etc...) but they allows us to use another type of primary key.
So...what should I do?
Identity defines two classes for each table, so there are two IdentityUser
classes, two IdentityRole
, and so on. One is the base generic class, which accepts some generics. The other is the built-in implementation of that base class, which supplies those generic types.
This base class looks like this:
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
where TLogin : IdentityUserLogin<TKey>
where TRole : IdentityUserRole<TKey>
where TClaim : IdentityUserClaim<TKey>
The first generic type is TKey
, which defines the key's type, which is string
. The second is a TLogin
, which is a IdentityUserLogin<TKey>
, which means it should be some class inheriting IdentityUserLogin
using the same TKey
type used as key (maybe string
, maybe an int
).
Meanwhile, the built-in implementation for these base classes looks like this:
public class IdentityUser
: IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser, IUser<string> {}
Which defines TKey
as a string
to be used as key. The other generics like IdentityUserLogin
and IdentityUserRole
, are built-in classes implementing the base classes which a string
key. Take a look how IdentityUserRole
looks like:
public class IdentityUserRole IdentityUserRole<string> {}
See? string
as well. Many other classes use string
as default for TKey
.
So if you were to change your class like this:
public class ApplicationUser : IdentityUser<int, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public virtual User user { get; set; }
}
It wouldn't work yet, because we changed TKey
to be int
, but the built-in IdentityUserLogin
still uses string
. So we need to create our own IdentityUserLogin
like this:
public class MyNiceUserLogin : IdentityUserLogin<int>
{
}
...and use it like this:
public class ApplicationUser : IdentityUser<int, MyNiceUserLogin, IdentityUserRole, IdentityUserClaim>
{
public virtual User user { get; set; }
}
...but we still had to do the same with the other classes like IdentityUserRole
and IdentityUserClaim
.
I configured it to use int
. Now what about one-to-one relationship?
Your code here:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasRequired(i => i.user).WithRequiredDependent(i => i.appUser);
}
...works fine. You're configuring in a way both must exist, there is no ApplicationUser
without a User
and vice-versa.
Now, I recommend you read this Stack Overflow answer to know exactly what steps to follow in order to change Identity's classes to use int
as key.
I also strongly recommend you to read this post by John Atten, so you can understand deeply about how to customize/extend Identity.
这篇关于当fk与pk - EntityFramework不同时,如何链接一对一的关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!