重复键值违反唯一约束 EF 核心

时间:2021-06-21 21:28:29

标签: c# entity-framework .net-core entity-framework-core

这是因为我有

public class Language
    {
        [Column("id")]
        public int Id { get; set; }
        [Column("name")]
        public string Name { get; set; }
        [Column("native_name")]
        public string NtiveName { get; set; }
        [Column("code_1")]
        public string Code1 { get; set; }
        [Column("code_2")]
        public string Code2 { get; set; }
    }
      public class UserCreateModel
    {
        [Required]
        public string FirstName { get; set; }
        [Required]
        public string LastName { get; set; }
        [Required]
        public string UserName { get; set; }
        [EmailAddress]
        [Required]
        public string VerificationEmail { get; set; }
        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
        [Required]
        public bool IsBusinessAccount { get; set; }
        [Required]
        public Language Language { get; set; }
    }

这两个模型,当尝试创建语言时,它已经存在我可以防止在语言表中创建语言而只引用它吗?

2 个答案:

答案 0 :(得分:0)

创建一对多关系。将 Id 和 LanguageId 属性添加到 UserCreateModel

public class UserCreateModel
    { 
         [Key]
         public int Id {get; set;}

        .....

        [Required]
        public int? LanguageId { get; set; }

       [ForeignKey(nameof(LanguageId))]
        [InverseProperty("UserCreateModels")]
        public Language Language { get; set; }
    }

public class Language
    {
        [Column("id")]
        [Key]
        public int Id { get; set; }
        ....
        [InverseProperty(nameof(UserCreateModel.Language))]
        public virtual ICollection<UserCreateModel> UserCreateModels { get; set; }
    }

答案 1 :(得分:0)

看起来您将实体(语言)与视图模型混合在一起。 (用户创建模型)

UserCreateModel 通常根本不需要语言实体或对象引用。这可能是您从从 Languages 实体加载的下拉列表中选择的内容,在这种情况下,我通常建议在 UserCreateModel 中只包含 LanguageId。

然后,当您从 UserCreateModel 创建一个 User 实体时,您的代码将如下所示:

using (var context = new AppDbContext())
{ 
    Language language = context.Languages.Single(x => x.LanguageId == userCreateModel.LanguageId);

    User user = new User
    {
         FirstName = userCreateModel.FirstName,
         LastName = userCreateModel.LastName,
         UserName = userCreateModel.UserName,
         Language = language
    };
    context.Users.Add(user);
    context.SaveChanges();
}

您的当前视图模型中可能有一个看起来像 Language 实体的东西,但是当从您的视图中反序列化时,它是一个全新的数据引用,看起来就像一个 Language 实体。 DbContext 对此一无所知。您可以通过在 DbContext 上使用 Attach 将 Language 对象与 DbContext 关联来解决此问题,但如果该 DbContext 可能已经加载/附加了该语言引用,则应始终首先检查本地中的现有引用缓存并使用该引用。在这种情况下,我假设 DbContext 的范围在此代码之外,例如注入控制器并由 IoC 容器管理:

Language language = context.Languages.Local.SingleOrDefault(x => x.LanguageId == userCreatedModel.Language.LanguageId);

if (language == null)
{
    context.Attach(userCreatedModel.Language);
    language = userCreatedModel.Language;
}
User user = new User
{
    FirstName = userCreateModel.FirstName,
    LastName = userCreateModel.LastName,
    UserName = userCreateModel.UserName,
    Language = language
};
context.Users.Add(user);
context.SaveChanges();

这里的区别在于我们首先检查 .Local DbSet 缓存以获取对该语言的引用。这不会影响数据库。如果我们找不到一种语言,我们可以附加提供的语言并关联该语言。否则我们将使用本地的。这仍然不是万无一失的,因为如果传入调用的语言不存在,我们仍然可以获得异常。 (被其他请求删除,或被篡改无效)

如果 User 实体公开 LanguageId FK 属性,那么您可以放弃加载 Language 并仅使用 LanguageId = userCreateModel.LanguageId 但是通过加载 Language 您确保创建的 User 实体(并可能返回等)具有完整有效的语言参考集。在 SaveChanges 之后,将更新任何 LanguageId FK 属性。如果传递了无效的 LanguageId,则通过加载语言,您将在该点出现异常,指出未找到该语言。如果您只设置 LanguageId,您将在 SaveChanges 上对 User 进行 FK 约束违规。