C# 实体框架 SaveChanges 错误详细信息
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2381500/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Entity Framework SaveChanges error details
提问by Marek Karbarz
When saving changes with SaveChanges
on a data context is there a way to determine which Entity causes an error? For example, sometimes I'll forget to assign a date to a non-nullable date field and get "Invalid Date Range" error, but I get no information about which entity or which field it's caused by (I can usually track it down by painstakingly going through all my objects, but it's very time consuming). Stack trace is pretty useless as it only shows me an error at the SaveChanges
call without any additional information as to where exactly it happened.
在SaveChanges
数据上下文上保存更改时,有没有办法确定哪个实体导致错误?例如,有时我会忘记将日期分配给不可为空的日期字段并得到“无效日期范围”错误,但我没有得到关于它是由哪个实体或哪个字段引起的信息(我通常可以通过煞费苦心地检查我所有的对象,但这非常耗时)。堆栈跟踪非常无用,因为它只在SaveChanges
调用时向我显示错误,而没有任何关于它究竟发生在哪里的额外信息。
Note that I'm not looking to solve any particular problem I have now, I would just like to know in general if there's a way to tell which entity/field is causing a problem.
请注意,我不打算解决我现在遇到的任何特定问题,我只是想知道是否有办法判断哪个实体/字段导致了问题。
Quick sample of a stack trace as an example - in this case an error happened because CreatedOn
date was not set on IAComment
entity, however it's impossible to tell from this error/stack trace
以堆栈跟踪的快速示例为例 - 在这种情况下,由于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
采纳答案by echo
One option is to handle the ObjectContext.SavingChanges Event, which gives you a chance to perform validation on entities before the changes are saved and even cancel the save if necessary. This way you can make sure any non-nullable properties are set before trying to save changes, and avoid having to rely on exception handling.
一种选择是处理ObjectContext.SavingChanges 事件,这使您有机会在保存更改之前对实体执行验证,甚至在必要时取消保存。通过这种方式,您可以确保在尝试保存更改之前设置了所有不可为空的属性,并避免依赖异常处理。
回答by Pablo Castilla
I think it is not possible: you could play with the states of the objects for knowing which will be saved (before saving) and which has been saved (in the exception), but in the second set you will not be able to know which one throwed the exception.
我认为这是不可能的:您可以使用对象的状态来了解哪些将被保存(保存前)和哪些已保存(在例外情况下),但在第二组中,您将无法知道哪些一个抛出异常。
Spanish version : http://msdn.microsoft.com/es-es/library/cc716714.aspx
西班牙语版本:http: //msdn.microsoft.com/es-es/library/cc716714.aspx
English : http://msdn.microsoft.com/en-us/library/cc716714.aspx
回答by John Bubriski
I think I might make separate calls to SaveChanges(). That is usually what I do for exactly this reason. Can I ask why you are saving multiple entities at a time? If you have to, I would follow the other guy's advice and validate the entities beforehand.
我想我可能会单独调用 SaveChanges()。正是出于这个原因,这通常是我所做的。我能问一下你为什么一次保存多个实体吗?如果您必须这样做,我会遵循其他人的建议并事先验证实体。
Or maybe there is a better way to structure your code so that in valid entiies won't even be attempted to be saved. Maybe detach your entities, and then run them through a validation method before attaching them to the new context. Hope that helps!
或者也许有更好的方法来构建您的代码,以便在有效的实体中甚至不会尝试保存。也许分离您的实体,然后在将它们附加到新上下文之前通过验证方法运行它们。希望有帮助!
回答by rwp
If all you need to do is see the actual inner exception instead, all you have to do is put the save changes inside of a try block, catch the exception and look at it.
如果您需要做的只是查看实际的内部异常,那么您所要做的就是将保存更改放在 try 块中,捕获异常并查看它。
I do it all the time and it works perfectly.
我一直这样做,而且效果很好。
回答by Ben
Here is my sample of couple checkers: either datetime = 0 or string overflows:
这是我的情侣跳棋示例:日期时间 = 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;
}
}
}
}