实体框架 6 电子邮件检查会导致“附加实体失败”异常

本文关键字:实体 失败 异常 框架 电子邮件 检查 | 更新日期: 2023-09-27 18:31:10

我有一个困扰了我几个小时的问题,我已经能够将其缩小到确切的代码块,但我似乎无法取得任何进一步的进展,我对 EF6 仍然相当陌生,所以我可能不是 100% 最新的最佳实践。

我有一个用户模型;

public class User
{
    public Guid ID { get; set; }
    [DisplayName("Display Name")]
    [Required]
    public string DisplayName { get; set; }
    [DisplayName("Email Address")]
    [DataType(DataType.EmailAddress, ErrorMessage = "Please enter a valid email address")]
    [Required]
    public string EmailAddress { get; set; }
    public string Company { get; set; }
    [DisplayName("Internal ID")]
    public string InternalId { get; set; }
    [DisplayName("User Status")]
    public UserStatus Status { get; set; }
    [DisplayName("Access Request Notifications")]
    public bool SendNotifications { get; set; }
    [DisplayName("Admin")]
    public bool IsAdmin { get; set; }
    public virtual ICollection<Transaction> Submissions { get; set; }
}

在我的视图中,数据库中的用户将被枚举并显示给用户,这一切都运行良好。

在用户控制器([HTTP Post])上的创建操作中,我正在运行下面的检查,以查看正在传递的电子邮件地址是否已存在于数据库中,如果存在,它将向用户返回一条消息,通知他们并阻止创建用户。

    public ActionResult Create([Bind(Include = "DisplayName,EmailAddress,Company,InternalId,Status,SendNotifications,IsAdmin")] User user)
    {
        try
        {
            user.ID = Guid.NewGuid();
            if (ModelState.IsValid)
            {
                var existingUser = db.Users.FirstOrDefault(x => x.EmailAddress.Equals(user.EmailAddress, StringComparison.InvariantCultureIgnoreCase));
                if(existingUser == null)
                {
                    db.Users.Add(user);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                else
                {
                    StaticConfig.Trace.Trace(SFTraceEvents.DbFailedAddingUser1, string.Format("User with email address '{0}' already exists in database", user.EmailAddress));
                    ViewData.Add("DbError", string.Format("Creation failed. User with email address '{0}' already exists", user.EmailAddress));
                }
            }
        }
        catch (Exception ex)
        {
            StaticConfig.Trace.Trace(SFTraceEvents.DbFailedAddingUser1, ex);
            ViewData.Add("DbError", "Unable to create user, an internal error has occured. Please try again, if the problem persists, please contact your system administrator.");
        }
        return View(user);
    }

这个过程工作正常,我没有使用内置的"Find()"方法,因为这似乎只搜索实体上的主键,而我想在 PK 以外的其他东西上查找。

当我尝试在 Edit 方法上实现相同的逻辑时,我遇到了以下错误。

异常: [无效操作异常:

系统。无效操作异常: 附加类型为 Models.User 的实体失败,因为相同类型的另一个实体已具有相同的主键值。使用"附加"方法或将实体的状态设置为"未更改"或"已修改"(如果图形中的任何实体具有冲突的键值)时,可能会发生这种情况。这可能是因为某些实体是新的,尚未收到数据库生成的键值。在这种情况下,请使用"添加"方法或"已添加"实体状态来跟踪图形,然后根据需要将非新实体的状态设置为"未更改"或"已修改"。

我的编辑方法代码当前如下:

    public ActionResult Edit([Bind(Include = "ID,DisplayName,EmailAddress,Company,InternalId,Status,SendNotifications,IsAdmin")] User user)
    {
        try
        {
            if (ModelState.IsValid)
            {
                var existingUser = db.Users.FirstOrDefault(x => x.EmailAddress.Equals(user.EmailAddress, StringComparison.InvariantCultureIgnoreCase));
                if(existingUser == null || existingUser.ID.Equals(user.ID))
                {
                    db.Entry(user).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                else
                {
                    StaticConfig.Trace.Trace(SFTraceEvents.DbFailedUpdatingUser2, user.DisplayName, string.Format("Email address '{0}' already exists in database", user.EmailAddress));
                    ViewData.Add("DbError", string.Format("Unable to save changes, email address '{0}' already exists", user.EmailAddress));
                }
            }
        }
        catch(Exception ex)
        {
            StaticConfig.Trace.Trace(SFTraceEvents.DbFailedUpdatingUser2, user.DisplayName, ex);
            ViewData.Add("DbError", "Unable to save changes, an internal error has occured. Please try again, if the problem persists, please contact your system administrator.");
        }
        return View(user);
    }

所以我正在检查是否有一个现有用户具有在编辑页面上输入的电子邮件地址,然后我正在检查正在编辑的用户的 ID 是否与数据库中找到的用户的 ID 匹配,如果是这样,那么当然应该允许相同的电子邮件地址,因为我们正在编辑该地址所属的用户。但是,如果 ID 不同,则数据库中有另一个用户使用在编辑页面上输入的电子邮件地址。

如果我删除"var existingUser"和随后执行 ID 检查的 if() 语句,那么编辑就会顺利进行,但随后我冒着在系统上有多个用户具有相同电子邮件地址的风险。当我把支票放回去时,我得到了上面的异常。

任何人都对我可能做错了什么有任何建议......是否有更有效的方法可以检查可能已经包含某些数据的实体?

编辑我发现在EF6.1中,它支持"索引"数据注释,该注释似乎也允许在其中设置唯一属性。我需要好好看看,但这可能会提供我正在寻找的东西。

EF6.1 索引属性

我也知道我可以做一些原始的SQL查询来解决这个问题,但如果可能的话,我想避免这种情况。

实体框架 6 电子邮件检查会导致“附加实体失败”异常

如果您想确保数据库中没有重复的电子邮件地址,包括阻止用户输入已在使用的新电子邮件地址,我会这样解决:

try
{
    // Make sure that there is an existing user, since this is an edit
    var existingUser = db.Users.FirstOrDefault(x => x.ID.Equals(user.Id));
    if (existingUser == null)
        throw new ValidationException("User not found");
    // Validate that the email address is unique, but only if it has changed
    if (!existingUser.EmailAddress.Equals(user.EmailAddress) &&
        db.Users.Any(x => x.EmailAddress.Equals(user.EmailAddress, StringComparison.InvariantCultureIgnoreCase)))
        throw new Exception(string.Format("Email address '{0}' is already in use", user.EmailAddress));
    // Move data to existing entity
    db.Entry(existingUser).CurrentValues.SetValues(user);
    db.SaveChanges();
}
catch (Exception ex)
{
    StaticConfig.Trace.Trace(SFTraceEvents.DbFailedUpdatingUser2, user.DisplayName,
        string.Format(ex.Message));
    ViewData.Add("DbError", ex.Message);
}

根据我的经验,这种提取现有数据和映射属性的方式是解决更新的最佳方法。您将能够进行涉及新旧数据等的广泛验证。

有几种不同的方法可以处理错误。我更喜欢抛出例外,然后在 catch 子句中对它们采取行动,但当然还有其他方法。

你在这里遇到的问题是你设定的期望。 函数唯一可以正常运行而不会出错的情况是更改电子邮件地址。 如果您正在编辑任何其他字段,则会遇到一致性问题。

user传递给此函数时,实体框架当前不会跟踪该实例。 然后,在数据库中执行搜索,并从数据库中查找实体框架现在正在跟踪的existingUser。 然后,您尝试将第一个user实例"附加"到实体框架(通过.State更改),但实体框架已跟踪原始版本,而不是修改版本。

相反,您应该做的是合并对象,如果数据库中存在现有的跟踪项,如下所示:

if(existingUser == null || existingUser.ID.Equals(user.ID))
{
    attachedUser = db.Entry(existingUser);
    attachedUser.CurrentValues.SetValues(user);
    db.SaveChanges();
    return RedirectToAction("Index");
}
相关文章: