C# 实体框架保存更改错误详细信息
在数据上下文中使用C# 实体框架保存更改错误详细信息,c#,entity-framework,C#,Entity Framework,在数据上下文中使用SaveChanges保存更改时,是否有方法确定哪个实体导致错误?例如,有时我会忘记将日期分配给不可为空的日期字段,并出现“无效日期范围”错误,但我没有得到关于哪个实体或哪个字段导致该错误的信息(我通常可以通过仔细查看所有对象来跟踪它,但这非常耗时)。堆栈跟踪非常无用,因为它只向我显示SaveChanges调用中的一个错误,而没有任何关于它到底发生在哪里的附加信息 请注意,我现在不想解决任何特定的问题,我只想知道是否有办法判断哪个实体/字段导致了问题 以堆栈跟踪的快速示例为例
SaveChanges
保存更改时,是否有方法确定哪个实体导致错误?例如,有时我会忘记将日期分配给不可为空的日期字段,并出现“无效日期范围”错误,但我没有得到关于哪个实体或哪个字段导致该错误的信息(我通常可以通过仔细查看所有对象来跟踪它,但这非常耗时)。堆栈跟踪非常无用,因为它只向我显示SaveChanges
调用中的一个错误,而没有任何关于它到底发生在哪里的附加信息
请注意,我现在不想解决任何特定的问题,我只想知道是否有办法判断哪个实体/字段导致了问题
以堆栈跟踪的快速示例为例—在本例中,由于未在
IAComment
实体上设置CreatedOn
date,因此发生了一个错误,但是无法从该错误/堆栈跟踪中分辨出来
[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
我认为这是不可能的:您可以使用对象的状态来了解哪些将被保存(保存前)和哪些已被保存(在异常中),但在第二组中,您将无法知道哪一个抛出了异常 西班牙文版本:
中文:一个选项是处理,这使您有机会在保存更改之前对实体执行验证,甚至在必要时取消保存。通过这种方式,您可以确保在尝试保存更改之前设置了任何不可为null的属性,并避免依赖异常处理。我想我可能会单独调用SaveChanges()。正是因为这个原因,我通常会这么做。我可以问一下为什么一次保存多个实体吗?如果必须的话,我会听从其他人的建议,事先验证实体
或者,也许有更好的方法来构造代码,以便在有效的实体中甚至不会试图保存。可以分离实体,然后在将它们附加到新上下文之前通过验证方法运行它们。希望有帮助 如果您所需要做的只是查看实际的内部异常,那么您所要做的就是将保存更改放在try块中,捕获异常并查看它
我一直这样做,而且效果很好。以下是我的两个检查示例: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;
}
}
}
}