2014-11-19 1 views
0

У меня есть проблема, которая меня беспокоила в течение нескольких часов, я смог сузить ее до точного кода, но я не могу похоже, делают какой-то дальнейший прогресс, я до сих пор довольно новичок в EF6, поэтому я не могу быть на 100% актуальным для лучших практик.Entity Framework 6 Проверка электронной почты причины «Привязка сущности не удалось»

У меня есть модель пользователя;

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-сообщение]), я запускаю проверку ниже, чтобы узнать, существует ли уже существующий в базе данных почтовый адрес, и если он это делает, он возвращает сообщение обратно пользователю, информирующему их и запрещающему создание пользователя.

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() метод, так как это, кажется, только для поиска по первичному ключу сущности, и я хочу, чтобы найти на нечто иное, чем ПК ,

Когда я пытаюсь реализовать ту же логику в методе Edit, я сталкиваюсь с приведенной ниже ошибкой.

Исключение: [InvalidOperationException: System.InvalidOperationException: привязка объекта типа 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); 
    } 

Так я проверяю, чтобы видеть, есть ли существующий пользователь с адресом электронной почты, который был введен на странице редактирования, я тогда проверку чтобы убедиться, что идентификатор пользователя, редактируемого, совпадает с идентификатором пользователя, находящегося в базе данных, если это так, то, конечно, тот же адрес электронной почты должен быть разрешен, так как мы редактируем пользователя, которому принадлежит этот адрес. Если, однако, идентификаторы разные, то в базе данных есть другой пользователь, используя адрес электронной почты, который был введен на странице редактирования.

Если я удалю 'var existingUser' и последующий оператор if(), который выполняет проверки идентификатора, то редактирование проходит через штраф, но тогда я рискую иметь несколько пользователей с тем же адресом электронной почты в системе , Когда я верну чек, я получаю исключение выше.

У кого-нибудь есть предложения по поводу того, что я могу делать неправильно .... есть ли более эффективный способ проверить объекты, которые могут уже содержать определенные данные?

Редактировать Я обнаружил, что в EF6.1, он поддерживает «индекс» аннотацию данных, который, кажется, чтобы уникальное свойство быть установлены в нем, как хорошо. Мне нужно посмотреть правильно, но это может предложить то, что я ищу.

EF6.1 Index Attribute

Я также знаю, что я мог бы сделать некоторые исходные запросы SQL, чтобы решить эту проблему, но, если это возможно, я хотел бы избежать этого.

+0

Кроме того, уникальные ограничения были отбирали раненый и помечены как отложенный: «Update: это функция была отложена и не будет включена в Entity Framework 5 », довольно круто, если она вернулась к EF 6.1 – Claies

+0

Да, я использую EF 6.1, поэтому я думал, что они могут быть возможным решением, я использовал ответ ниже, и он работает так же хорошо, похоже, он будет намного более гибким, так что я могу его расширить и добавить дополнительные проверки в будущем, если мне нужно, чтобы –

+0

Возможно, вы можете взглянуть на мой ответ на [ASP.NET MVC - привязка объекта типа «MODELNAME» не удалась, потому что другой объект того же типа уже имеет такое же значение первичного ключа] (http://stackoverflow.com/questions/23201907/asp-net-mvc -attaching-ан-объект-в-типа-ModelName-не удалось, потому-другой-лор/39557606 # 39557606). –

ответ

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, но есть и другие способы, конечно.

+0

У меня уже есть «@ Html.HiddenFor (model => model.ID)», присутствующий в моем представлении Edit.cshtml. Существующий пользователь имеет значение null, так как это будет пользователь, который уже может присутствовать в базе данных, если он равен нулю, тогда адрес электронной почты, который отправляется, в настоящее время не используется в базе данных. Если существующийUser не является нулевым, то пользователь в базе данных уже использует этот адрес, и мне нужно выяснить, является ли пользователь пользователем, который сейчас редактируется, или если он совсем другой. –

+0

ОК, я вижу. Итак, что вы пытаетесь сделать, убедитесь, что в базе данных нет дубликатов адресов электронной почты, и это должно также охватывать пользователя, имеющего старый действительный адрес электронной почты, который пытается изменить адрес на тот, который уже используется? –

+0

Правильно, извините, мое первоначальное описание могло быть немного запутанным. Существует третий случай, когда пользователь обновляет некоторые данные, но не их адрес электронной почты, следовательно, проверка на пользователе. Я обновил вопрос выше с ресурсом, который я только что нашел, который может решить мою проблему, но я все еще смотрю на него. –

1

Проблема, которую вы имеете здесь, - это ожидание, которое вы установили. ТОЛЬКО время, в течение которого эта функция запускается без ошибок, является изменение адреса электронной почты. Если вы редактируете какое-либо другое поле, у вас проблема согласованности.

При передаче функции user этой функции Entity Framework в настоящее время не отслеживает этот экземпляр. Затем перейдите и выполните поиск в базе данных и найдите existingUser из базы данных, которую теперь отслеживает Entity Framework. Затем вы пытаетесь «прикрепить» первый экземпляр user к Entity Framework (с помощью изменения .State), но Entity Framework уже отслеживает исходную, а не измененную версию.

Что вы должны сделать вместо этого объединить объекты, если есть существующий гусеничный элемент в базе данных, например, так:

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

Но это не работает, если существующееUser равно null, не так ли? 'attachUser = db.Entry (null);', я имею в виду ... –

+1

честно, правильный способ сделать это - это комбинация моего кода и кода @DanielPersson. Его код более полный, и он обновил свой ответ, чтобы интегрировать мою часть решения. – Claies

+1

Да, решение CurrentValues.SetValues ​​было приятным! Я не знал, что существует, поэтому я беззастенчиво обновил свой вопрос с этим сейчас :) Thx –