Ошибка проверки для одного или нескольких объектов при сохранении изменений в базе данных SQL Server с использованием Entity Framework
Я хочу сохранить свою правку в базу данных и использую Entity FrameWork Code-First в ASP.NET MVC 3 / C#, но получаю ошибки. В моем классе Event у меня есть типы данных DateTime и TimeSpan, но в моей базе данных у меня соответственно Date и Time. Может ли это быть причиной? Как я могу привести к соответствующему типу данных в коде перед сохранением изменений в базе данных.
public class Event
{
public int EventId { get; set; }
public int CategoryId { get; set; }
public int PlaceId { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
public DateTime EventDate { get; set; }
public TimeSpan StartTime { get; set; }
public TimeSpan EndTime { get; set; }
public string Description { get; set; }
public string EventPlaceUrl { get; set; }
public Category Category { get; set; }
public Place Place { get; set; }
}
Метод в контроллере >>>> Проблема у storeDB.SaveChanges();
// POST: /EventManager/Edit/386
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var theEvent = storeDB.Events.Find(id);
if (TryUpdateModel(theEvent))
{
storeDB.SaveChanges();
return RedirectToAction("Index");
}
else
{
ViewBag.Categories = storeDB.Categories.OrderBy(g => g.Name).ToList();
ViewBag.Places = storeDB.Places.OrderBy(a => a.Name).ToList();
return View(theEvent);
}
}
с
public class EventCalendarEntities : DbContext
{
public DbSet<Event> Events { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Place> Places { get; set; }
}
База данных SQL Server 2008 R2 / T-SQL
EventDate (Datatype = date)
StartTime (Datatype = time)
EndTime (Datatype = time)
Http Форма
EventDate (Datatype = DateTime) e.g. 4/8/2011 12:00:00 AM
StartTime (Datatype = Timespan/time not sure) e.g. 08:30:00
EndTime (Datatype = Timespan/time not sure) e.g. 09:00:00
Ошибка сервера в приложении '/'
Проверка не удалась для одного или нескольких объектов. См. Свойство EntityValidationErrors для более подробной информации.
Описание: во время выполнения текущего веб-запроса произошло необработанное исключение. Пожалуйста, просмотрите трассировку стека для получения дополнительной информации об ошибке и о том, где она возникла в коде.
Сведения об исключении: System.Data.Entity.Validation.DbEntityValidationException: проверка не удалась для одного или нескольких объектов. См. Свойство EntityValidationErrors для более подробной информации.
Ошибка источника:
Line 75: if (TryUpdateModel(theEvent))
Line 76: {
Line 77: storeDB.SaveChanges();
Line 78: return RedirectToAction("Index");
Line 79: }
Исходный файл: C:\sep\MvcEventCalendar\MvcEventCalendar\Controllers\EventManagerController.cs Строка: 77
Трассировки стека:
[DbEntityValidationException: проверка не удалась для одного или нескольких объектов. См. Свойство EntityValidationErrors для более подробной информации.]
16 ответов
Вы можете извлечь всю информацию из DbEntityValidationException
с помощью следующего кода (вам нужно добавить пространства имен: System.Data.Entity.Validation
а также System.Diagnostics
на ваш using
список):
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}",
validationError.PropertyName,
validationError.ErrorMessage);
}
}
}
Изменение кода не требуется:
Пока вы находитесь в режиме отладки в catch {...}
блок откройте окно "QuickWatch" (Ctrl+Alt+Q) и вставьте туда:
((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors
или же:
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
Если вы не участвуете в программе try/catch или не имеете доступа к объекту исключения.
Это позволит вам углубиться в ValidationErrors
дерево. Я нашел самый простой способ получить мгновенное представление об этих ошибках.
В случае, если у вас есть классы с одинаковыми именами свойств, вот небольшое расширение ответа Правина:
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation(
"Class: {0}, Property: {1}, Error: {2}",
validationErrors.Entry.Entity.GetType().FullName,
validationError.PropertyName,
validationError.ErrorMessage);
}
}
}
В качестве улучшения как Правина, так и Тони я использую переопределение:
public partial class MyDatabaseEntities : DbContext
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
validationErrors.Entry.Entity.GetType().FullName,
validationError.PropertyName,
validationError.ErrorMessage);
}
}
throw; // You can also choose to handle the exception here...
}
}
}
Эта реализация переносит исключение объекта в исключение с подробным текстом. Это обрабатывает DbEntityValidationException
, DbUpdateException
, datetime2
ошибки диапазона (MS SQL) и включение ключа недействительной сущности в сообщение (полезно, когда сохраняется несколько сущностей одновременно) SaveChanges
вызов).
Во-первых, переопределить SaveChanges
в классе DbContext:
public class AppDbContext : DbContext
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException dbEntityValidationException)
{
throw ExceptionHelper.CreateFromEntityValidation(dbEntityValidationException);
}
catch (DbUpdateException dbUpdateException)
{
throw ExceptionHelper.CreateFromDbUpdateException(dbUpdateException);
}
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
try
{
return await base.SaveChangesAsync(cancellationToken);
}
catch (DbEntityValidationException dbEntityValidationException)
{
throw ExceptionHelper.CreateFromEntityValidation(dbEntityValidationException);
}
catch (DbUpdateException dbUpdateException)
{
throw ExceptionHelper.CreateFromDbUpdateException(dbUpdateException);
}
}
ExceptionHelper класс:
public class ExceptionHelper
{
public static Exception CreateFromEntityValidation(DbEntityValidationException ex)
{
return new Exception(GetDbEntityValidationMessage(ex), ex);
}
public static string GetDbEntityValidationMessage(DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
return exceptionMessage;
}
public static IEnumerable<Exception> GetInners(Exception ex)
{
for (Exception e = ex; e != null; e = e.InnerException)
yield return e;
}
public static Exception CreateFromDbUpdateException(DbUpdateException dbUpdateException)
{
var inner = GetInners(dbUpdateException).Last();
string message = "";
int i = 1;
foreach (var entry in dbUpdateException.Entries)
{
var entry1 = entry;
var obj = entry1.CurrentValues.ToObject();
var type = obj.GetType();
var propertyNames = entry1.CurrentValues.PropertyNames.Where(x => inner.Message.Contains(x)).ToList();
// check MS SQL datetime2 error
if (inner.Message.Contains("datetime2"))
{
var propertyNames2 = from x in type.GetProperties()
where x.PropertyType == typeof(DateTime) ||
x.PropertyType == typeof(DateTime?)
select x.Name;
propertyNames.AddRange(propertyNames2);
}
message += "Entry " + i++ + " " + type.Name + ": " + string.Join("; ", propertyNames.Select(x =>
string.Format("'{0}' = '{1}'", x, entry1.CurrentValues[x])));
}
return new Exception(message, dbUpdateException);
}
}
Этот код помог найти мою проблему, когда у меня возникла проблема с моей ошибкой Entity VAlidation. Он сказал мне точную проблему с моим определением сущности. Попробуйте следующий код, где вам нужно покрыть storeDB.SaveChanges(); в следующем попробуйте перехватить блок.
try
{
if (TryUpdateModel(theEvent))
{
storeDB.SaveChanges();
return RedirectToAction("Index");
}
}
catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
{
Exception raise = dbEx;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
string message = string.Format("{0}:{1}",
validationErrors.Entry.Entity.ToString(),
validationError.ErrorMessage);
// raise a new exception nesting
// the current instance as InnerException
raise = new InvalidOperationException(message, raise);
}
}
throw raise;
}
Сегодня я получал эту ошибку и некоторое время не мог ее исправить, но понял, что это после добавления некоторых RequireAttribute
с моими моделями и что некоторые исходные данные разработки не заполняют все обязательные поля.
Так что просто обратите внимание, что если вы получаете эту ошибку при обновлении базы данных через какую-то стратегию инициализации, такую как DropCreateDatabaseIfModelChanges
Затем вы должны убедиться, что ваши начальные данные удовлетворяют и удовлетворяют любым атрибутам проверки данных модели.
Я знаю, что это немного отличается от проблемы в вопросе, но это популярный вопрос, поэтому я решил добавить немного больше к ответу для других, имеющих такую же проблему, как и я.
Надеюсь, что это помогает другим:)
Я думаю, что добавление попробовать / поймать для каждого SaveChanges()
операция не является хорошей практикой, лучше централизовать это:
Добавить этот класс в основной DbContext
учебный класс:
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
string errorMessages = string.Join("; ", ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage));
throw new DbEntityValidationException(errorMessages);
}
}
Это перезапишет ваш контекст SaveChanges()
метод, и вы получите разделенный запятыми список, содержащий все ошибки проверки сущности.
это также может быть улучшено, чтобы регистрировать ошибки в рабочей среде, а не просто выдавать ошибку.
надеюсь, что это полезно.
Я не люблю исключения, я зарегистрировал OnSaveChanges и имею это
var validationErrors = model.GetValidationErrors();
var h = validationErrors.SelectMany(x => x.ValidationErrors
.Select(f => "Entity: "
+(x.Entry.Entity)
+ " : " + f.PropertyName
+ "->" + f.ErrorMessage));
Вот расширение к расширению Тони...:-)
Для Entity Framework 4.x, если вы хотите получить имя и значение ключевого поля, чтобы вы знали, с каким экземпляром объекта (записью БД) возникла проблема, вы можете добавить следующее. Это обеспечивает доступ к более мощным членам класса ObjectContext из вашего объекта DbContext.
// Get the key field name & value.
// This assumes your DbContext object is "_context", and that it is a single part key.
var e = ((IObjectContextAdapter)_context).ObjectContext.ObjectStateManager.GetObjectStateEntry(validationErrors.Entry.Entity);
string key = e.EntityKey.EntityKeyValues[0].Key;
string val = e.EntityKey.EntityKeyValues[0].Value;
Убедитесь, что если у вас есть nvarchar(50) в строке БД, вы не пытаетесь вставить в нее более 50 символов. Глупая ошибка, но мне понадобилось 3 часа, чтобы понять это.
Эта ошибка также происходит, когда вы пытаетесь сохранить объект, имеющий ошибки проверки. Хороший способ вызвать это - забыть проверить ModelState.IsValid перед сохранением в вашей БД.
Я столкнулся с той же проблемой пару дней назад при обновлении базы данных. В моем случае было немного новых необнуляемых столбцов, добавленных для обслуживания, которые не были включены в код, который вызывает исключение. Я выяснил эти поля и предоставил значения для них и их решения.
Это может быть вызвано Свойством, которое не заполняется моделью.. вместо этого оно заполняется Контроллером.. что может вызвать эту ошибку.. Решением этой проблемы является присвоение свойства перед применением проверки ModelState. и это второе предположение. возможно, у вас уже есть данные в вашей базе данных, и вы пытаетесь обновить их, но теперь извлекаете их.
В моем случае у меня есть имя столбца таблицы Path, тип данных которого я установил как varchar(200). После обновления до nvarchar(max) я удалил таблицу из edmx, а затем снова добавил таблицу, и она работала правильно для меня.
Спасибо за ваши ответы, это очень помогает мне. как я кодирую в Vb.Net, этот код болта для Vb.Net
Try
Return MyBase.SaveChanges()
Catch dbEx As Validation.DbEntityValidationException
For Each [error] In From validationErrors In dbEx.EntityValidationErrors
From validationError In validationErrors.ValidationErrors
Select New With { .PropertyName = validationError.PropertyName,
.ErrorMessage = validationError.ErrorMessage,
.ClassFullName = validationErrors.Entry.Entity
.GetType().FullName}
Diagnostics.Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
[error].ClassFullName,
[error].PropertyName,
[error].ErrorMessage)
Next
Throw
End Try
Это может быть связано с максимальным количеством символов, разрешенным для определенного столбца, как в sql одно поле может иметь следующий тип данных nvarchar(5)
но количество символов, введенных пользователем, превышает указанное, поэтому возникает ошибка.