实体框架6电子邮件检查原因'附加实体失败'例外

时间:2014-11-19 21:52:33

标签: c# entity-framework entity-framework-6 asp.net-mvc-5.1

我有一个困扰我几个小时的问题,我已经能够将其缩小到确切的代码块,但我似乎无法取得任何进一步的进展,我&# 39;对于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);
    }

此过程运行正常,我没有使用内置的&#39; Find()&#39;方法,因为这似乎只搜索实体上的主键,我想找到除PK之外的其他东西。

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

异常:[InvalidOperationException:System.InvalidOperationException:附加Models.User类型的实体失败,因为同一类型的另一个实体已具有相同的主键值。使用&#39;附加&#39;方法或将实体的状态设置为“未更改”#39;或者&#39;修改&#39;如果图中的任何实体具有冲突的键值。这可能是因为某些实体是新的并且尚未收到数据库生成的键值。在这种情况下,请使用&#39;添加&#39;方法或“添加”#39;实体状态跟踪图形,然后将非新实体的状态设置为“未更改”。或者&#39;修改&#39;酌情。

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

    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不同,则数据库中的另一个用户使用已在编辑页面上输入的电子邮件地址。

如果我删除了&#39; var existingUser&#39;以及执行ID检查的后续if()语句然后编辑很好,但是我冒着让几个用户在系统上使用相同电子邮件地址的风险。当我把支票放回去时,我得到了上面的例外。

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

修改 我发现在EF6.1中,它支持一个&#39;索引&#39;数据注释似乎也允许在其中设置唯一属性。我需要正确看看,但这可能会提供我正在寻找的东西。

EF6.1 Index Attribute

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

2 个答案:

答案 0 :(得分:1)

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

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子句中对它们进行操作,当然还有其他方法。

答案 1 :(得分:1)

你在这里遇到的问题是你已经设定的期望。如果您要更改电子邮件地址,此功能无错误运行的 ONLY 时间。如果您正在编辑任何其他字段,则会出现一致性问题。

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

如果数据库中存在现有的跟踪项,则应该执行的操作是合并对象,如下所示:

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