Сведения об ошибке Entity Framework SaveChanges
При сохранении изменений с помощью SaveChanges
в контексте данных есть способ определить, какая Entity вызывает ошибку? Например, иногда я забываю назначить дату недействительному полю даты и получить ошибку "Недействительный диапазон дат", но я не получаю никакой информации о том, какой объект или какое поле оно вызвало (я обычно могу отслеживать его кропотливо просматривая все мои объекты, но это очень много времени). Трассировка стека довольно бесполезна, так как она только показывает мне ошибку при вызове SaveChanges
без дополнительной информации о том, где именно это произошло.
Обратите внимание, что я не ищу для решения какой-либо конкретной проблемы, которую у меня есть сейчас, я просто хотел бы узнать в целом, если есть способ определить, какое сущность/поле вызывает проблему.
Быстрый пример трассировки стека в качестве примера - в этом случае произошла ошибка, поскольку дата CreatedOn
не была установлена на объект IAComment
, однако из этой ошибки/стека трассировки
[SqlTypeException: SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.]
System.Data.SqlTypes.SqlDateTime.FromTimeSpan(TimeSpan value) +2127345
System.Data.SqlTypes.SqlDateTime.FromDateTime(DateTime value) +232
System.Data.SqlClient.MetaType.FromDateTime(DateTime dateTime, Byte cb) +46
System.Data.SqlClient.TdsParser.WriteValue(Object value, MetaType type, Byte scale, Int32 actualLength, Int32 encodingByteSize, Int32 offset, TdsParserStateObject stateObj) +4997789
System.Data.SqlClient.TdsParser.TdsExecuteRPC(_SqlRPC[] rpcArray, Int32 timeout, Boolean inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, Boolean isCommandProc) +6248
System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async) +987
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) +162
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) +32
System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) +141
System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) +12
System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior) +10
System.Data.Mapping.Update.Internal.DynamicUpdateCommand.Execute(UpdateTranslator translator, EntityConnection connection, Dictionary`2 identifierValues, List`1 generatedValues) +8084396
System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter) +267
[UpdateException: An error occurred while updating the entries. See the inner exception for details.]
System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter) +389
System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache) +163
System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options) +609
IADAL.IAController.Save(IAHeader head) in C:\Projects\IA\IADAL\IAController.cs:61
IA.IAForm.saveForm(Boolean validate) in C:\Projects\IA\IA\IAForm.aspx.cs:198
IA.IAForm.advance_Click(Object sender, EventArgs e) in C:\Projects\IA\IA\IAForm.aspx.cs:287
System.Web.UI.WebControls.Button.OnClick(EventArgs e) +118
System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +112
System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13
System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +36
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5019
Ответы
Ответ 1
Один из вариантов - обработать событие ObjectContext.SavingChanges, которое дает вам возможность выполнить проверку на сущности до сохранения изменений и даже отмените сохранение, если необходимо. Таким образом, вы можете убедиться, что все свойства, отличные от нуля, установлены перед тем, как пытаться сохранить изменения, и избегайте полагаться на обработку исключений.
Ответ 2
Если все, что вам нужно сделать, это увидеть фактическое внутреннее исключение, все, что вам нужно сделать, это поместить изменения сохранения внутри блока try, поймать исключение и посмотреть на него.
Я делаю это все время, и он отлично работает.
Ответ 3
Я думаю, что это невозможно: вы могли бы играть с состояниями объектов, зная, что будет сохранено (до сохранения) и которое было сохранено (в исключении), но во втором наборе вы не сможете чтобы узнать, какой из них выбрал исключение.
Испанская версия: http://msdn.microsoft.com/es-es/library/cc716714.aspx
Русский: http://msdn.microsoft.com/en-us/library/cc716714.aspx
Ответ 4
Думаю, я могу сделать отдельные вызовы SaveChanges(). Обычно это то, что я делаю именно по этой причине. Могу ли я спросить, почему вы одновременно сохраняете несколько объектов? Если вам нужно, я буду следовать совету другого парня и заранее проверять сущности.
Или, может быть, есть лучший способ структурировать ваш код, чтобы в действительных целях даже не было попытки сохранить их. Возможно, отсоедините ваши сущности, а затем запустите их с помощью метода проверки, прежде чем присоединять их к новому контексту. Надеюсь, это поможет!
Ответ 5
Вот мой пример проверки пары: либо datetime = 0, либо переполнение строк:
public partial class MyContext
{
private static Dictionary> _fieldMaxLengths;
partial void OnContextCreated()
{
InitializeFieldMaxLength();
SavingChanges -= BeforeSave;
SavingChanges += BeforeSave;
}
private void BeforeSave(object sender, EventArgs e)
{
StringOverflowCheck(sender);
DateTimeZeroCheck(sender);
CheckZeroPrimaryKey(sender);
}
private static void CheckZeroPrimaryKey(object sender)
{
var db = (CTAdminEntities)sender;
var modified = db.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified);
foreach (var entry in modified.Where(entry => !entry.IsRelationship))
{
var entity = (EntityObject)entry.Entity;
Debug.Assert(entity != null);
var type = entity.GetType();
foreach (var prop in type.GetProperties().Where(
p => new[] { typeof(Int64), typeof(Int32), typeof(Int16) }.Contains(p.PropertyType)))
{
var attr = prop.GetCustomAttributes(typeof (EdmScalarPropertyAttribute), false);
if (attr.Length > 0 && ((EdmScalarPropertyAttribute) attr[0]).EntityKeyProperty)
{
long value = 0;
if (prop.PropertyType == typeof(Int64))
value = (long) prop.GetValue(entity, null);
if (prop.PropertyType == typeof(Int32))
value = (int) prop.GetValue(entity, null);
if (prop.PropertyType == typeof(Int16))
value = (short) prop.GetValue(entity, null);
if (value == 0)
throw new Exception(string.Format("PK is 0 for Table {0} Key {1}", type, prop.Name));
break;
}
}
}
}
private static void DateTimeZeroCheck(object sender)
{
var db = (CTAdminEntities)sender;
var modified = db.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified);
foreach (var entry in modified.Where(entry => !entry.IsRelationship))
{
var entity = (EntityObject)entry.Entity;
Debug.Assert(entity != null);
var type = entity.GetType();
foreach (var prop in type.GetProperties().Where(p => p.PropertyType == typeof(DateTime)))
{
var value = (DateTime)prop.GetValue(entity, null);
if (value == DateTime.MinValue)
throw new Exception(string.Format("Datetime2 is 0 Table {0} Column {1}", type, prop.Name));
}
foreach (var prop in type.GetProperties().Where(
p => p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable) &&
p.PropertyType.GetGenericArguments()[0] == typeof(DateTime)))
{
var value = (DateTime?)prop.GetValue(entity, null);
if (value == DateTime.MinValue)
throw new Exception(string.Format("Datetime2 is 0 Table {0} Column {1}", type, prop.Name));
}
}
}
private static void StringOverflowCheck(object sender)
{
var db = (CTAdminEntities)sender;
var modified = db.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified);
foreach (var entry in modified.Where(entry => !entry.IsRelationship))
{
var entity = (EntityObject)entry.Entity;
Debug.Assert(entity != null);
var type = entity.GetType();
var fieldMap = _fieldMaxLengths[type.Name];
foreach (var key in fieldMap.Keys)
{
var value = (string)type.GetProperty(key).GetValue(entity, null);
if (value != null && value.Length > fieldMap[key])
throw new Exception(string.Format("String Overflow on Table {0} Column {1}: {2} out of {3}", type, key, value.Length, fieldMap[key]));
}
}
}
private void InitializeFieldMaxLength()
{
if (_fieldMaxLengths != null)
return;
_fieldMaxLengths = new Dictionary>();
var items = MetadataWorkspace.GetItems(DataSpace.CSpace);
Debug.Assert(items != null);
var tables = items.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType);
foreach (EntityType table in tables)
{
var fieldsMap = new Dictionary();
_fieldMaxLengths[table.Name] = fieldsMap;
var stringFields = table.Properties.Where(p => p.DeclaringType.Name == table.Name && p.TypeUsage.EdmType.Name == "String");
foreach (var field in stringFields)
{
var value = field.TypeUsage.Facets["MaxLength"].Value;
if (value is Int32)
fieldsMap[field.Name] = Convert.ToInt32(value);
else
// unbounded
fieldsMap[field.Name] = Int32.MaxValue;
}
}
}
}