【问题标题】:How do you add a new user and claim in a single transaction using ASP.NET Core 2.1 Identity?如何使用 ASP.NET Core 2.1 Identity 在单个事务中添加新用户和声明?
【发布时间】:2019-10-14 15:23:26
【问题描述】:

我正在尝试添加一个新用户和一些其他关联实体,包括作为一个交易的声明。我的课程基本上定义如下。请注意,我在 User 类上使用了 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();

我可以轻松地将User 链接到OtherEntity,因为我已经设置了导航属性,所以我可以将User 添加到其中,并且实体框架负责填充UserId 列。我不能用UserClaim 做到这一点,因为它没有导航属性。我可以在添加User 后调用context.SaveChanges(),实体框架将获得由数据库为我创建的User.Id,我可以使用它在UserClaim 上设置UserId,但这意味着两个事务。

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

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

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

InvalidOperationException:从 'UserClaim.User' 到 'User' 与外键属性 {'UserId' : int} 的关系不能以主键 {'Id' : int} 为目标,因为它不兼容。为此关系配置一个主键或一组兼容的外键属性。

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

【问题讨论】:

    标签: asp.net entity-framework asp.net-identity


    【解决方案1】:

    我的问题应该是:“如何在 ASP.NET 标识类之间添加导航属性?”

    如果我寻找答案,我会找到 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 访问数据库时我才意识到,但这仍然需要 两次 访问数据库,即使我们只调用 @987654327 @ 一次! Entity Framework 将首先保存 User 并获取插入行的 ID,然后在插入 UserClaim 时使用该 ID。

    【讨论】:

      【解决方案2】:

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

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

          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 你可以试试。

      【讨论】:

      • AddRange 取一个params数组,abs可以同上。
      • 我试图提炼我的例子以使其更清晰,显然失败了。我知道如何处理相关实体。我的问题特别是从 asp.net 身份类派生的用户和声明类之间的关系。据我所知,身份框架提供的类没有明确定义关系,尽管它必须在某个地方定义,因为我的数据库中存在外键。
      • 仅供参考 IdentityUser 没有可以添加声明的声明属性。我可以在派生的 User 类中添加一个,但我不知道如何使用实体框架正确配置它并在我的问题结束时得到错误
      • 我需要检查文档,因为我不记得每个对象的结构,IdentityUserClaim 具有 UserId 属性 docs.microsoft.com/en-us/previous-versions/aspnet/… 但没有对 User 对象的引用,正如我在那里看到的那样,它是如果要使用相关数据插入,则需要设置。您需要指定它。我可以看到你在做什么,但还是有点困惑。
      • 好的,当我回到我的电脑时,我会试着把事情弄清楚。感谢您迄今为止的宝贵时间
      猜你喜欢
      • 2018-05-17
      • 2019-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-09
      • 2018-08-14
      • 2019-05-18
      • 1970-01-01
      相关资源
      最近更新 更多