开发者

Entity Framework SaveChanges error details

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.

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

    [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


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.


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.

I do it all the time and it works perfectly.


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.

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!


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

English : http://msdn.microsoft.com/en-us/library/cc716714.aspx


Here is my sample of couple checkers: either datetime = 0 or string overflows:


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;
            }
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜