您如何使用 ASP.NET Core 2.1 Identity 在单笔交易中添加新用户和声明?

How do you add a new user and claim in a single transaction using ASP.NET Core 2.1 Identity?

我正在尝试添加一个新用户和一些其他相关实体,包括将索赔作为一项交易。我的 classes 基本上定义如下。请注意,我在 User class 上使用 int 主键,它是一个标识列(值由数据库在插入时提供)。

public class User : IdentityUser<int>
{
    // custom props here
}

public class UserClaim : IdentityUserClaim<int>
{
    // i actually haven't added anything to this class, it's mainly here so 
    // i don't have to keep specifying the user's primary key type
}

public class OtherEntity 
{
    public int UserId { get; set; }

    [ForeignKey(nameof(UserId))]
    public User User { get; set; }

    // other stuff
}

然后我想像这样将用户等添加到数据库中:

User user = new User(){ /* set props */ };

OtherEntity other = new OtherEntity()
{
    User = user
};

UserClaim claim = new UserClaim()
{
    /* this is where the problem is */
    UserId = user.Id,
    ClaimType = "my-claim-type",
    ClaimValue = "my-claim-value"
};

context.AddRange(user, other, claim);
context.SaveChanges();

我可以轻松地 link UserOtherEntity 因为我已经设置了导航 属性 所以我可以将 User 添加到它和 entity framework 负责 UserId 列中的填充。我无法使用 UserClaim 执行此操作,因为它没有导航 属性。我可以在添加 User 之后调用 context.SaveChanges(),entity framework 会得到数据库为我创建的 User.Id,我可以用它来设置 UserId UserClaim,但这将意味着两次交易。

我已尝试将导航 属性 添加到我对 UserClaim 的定义中,如下所示:

public class UserClaim : IdentityUserClaim<int>
{
    [ForeignKey(nameof(UserId))]
    public User User { get; set; }
}

但我收到以下运行时错误:

InvalidOperationException: The relationship from 'UserClaim.User' to 'User' with foreign key properties {'UserId' : int} cannot target the primary key {'Id' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.

有没有办法在同一个交易中同时创建用户和声明?

插入相关数据记录在Entity Framework:https://docs.microsoft.com/pl-pl/ef/core/saving/related-data 并且在其他主题中也有很好的描述,例如:Entity Framework Foreign Key Inserts with Auto-Id

你们每个实体都需要在实体模型中正确设置关系(外键)(没有它们,EF 不知道该怎么做),当你添加它时,你需要从头开始,因此必须从您的用户实体设置 UserClaim,例如

    var user = new User(){
    //properites
    OtherEntity = new OtherEntity()
    {
        Id = 0, /*will be set automatically*/
        UserId = 0 /*will be set automatically*/
        /* other properites */
    };
    Claim = new UserClaim(){
        Id = 0, /*will be set automatically*/
        UserId = 0 /*will be set automatically*/
        /* other properites */
    }
}

ontext.Add(user);
context.SaveChanges();

你没有提供关于你们关系的所有信息,我只是从你的代码中假设了这一点。

PS。 AddRange只有一个参数。

编辑: 为了澄清我的回答,为了使一切正常,AddRange/Add 需要以树形方式调用您的基础实体和内部关系。 但首先你需要配置你的模型,诸如此类。

public class User : IdentityUser<int>
{
    [ForeignKey("UserClaimId")]
    public virtual UserClaim Claim {get;set;}
}

public class UserClaim : IdentityUserClaim<int>
{
    [ForeignKey("UserId")]
    public virtual User User {get;set;}
}

public class OtherEntity 
{
    public int UserId { get; set; }

    [ForeignKey("UserId")]
    public User User { get; set; }

    // other stuff
}

您还可以使用 OnModelCreating 来设置实体,如文档中所述:https://docs.microsoft.com/en-us/ef/core/modeling/relationships

Entity Framework 现在对数据库中的关系、数据类型等一无所知

更新了代码示例

编辑,没有足够的声誉向您的答案添加评论

好吧,EF 仍然需要跟踪实体。我需要编写 运行 一些示例来检查是否有某种方法可以改进对数据库的访问。也许有人可以告诉您更多关于添加相关数据背后的机制以及是否有改进它的方法。 有一些库有助于提高将更改保存到数据库的性能,例如:https://entityframework-extensions.net 你可以试试。

我的问题应该是:"How do I add navigation properties between ASP.NET Identity classes?"

如果我要寻找答案,我会找到 microsoft docs 解释如何去做的!

上面链接的文档告诉您如何添加 User.UserClaims 导航 属性:

public class User : IdentityUser<int>
{
    public virtual ICollection<UserClaim> Claims { get; set; }
}

public class DataContext : IdentityDbContext</* Identity classes */>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>(e =>
        {
            e.HasMany(u => u.Claims)
             .WithOne()
             .HasForeignKey(c => c.UserId) 
             .IsRequired();
        });
    }
}

但它没有说明如何进行反向 UserClaim.User 导航 属性。我发现可以这样做:

public class UserClaim : IdentityUserClaim<int>
{
    public virtual User User { get; set; }
}

public class DataContext : IdentityDbContext</* Identity classes */>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>(e =>
        {
            e.HasMany(u => u.Claims)
             .WithOne(c => c.User) // <- this line is different
             .HasForeignKey(c => c.UserId) 
             .IsRequired();
        });
    }
}

然后您可以根据我的问题创建一个新用户并同时声明:

User user = new User(){ /* set props */ };

UserClaim claim = new UserClaim()
{
    User = user, // <- this is the bit you can't do without the nav. property
    ClaimType = "my-claim-type",
    ClaimValue = "my-claim-value"
};

context.AddRange(user, claim);
context.SaveChanges();

我想这是常识,虽然我直到检查实际 SQL 访问数据库才意识到,但这仍然需要 两次 次访问数据库,即使我们只调用 .SaveChanges() 一次! Entity Framework 将首先保存 User 并获取插入行的 ID,然后在插入 UserClaim.

时使用该 ID