Подтвердить что ты не робот

Сведения об ошибке 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
4b9b3361

Ответ 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;
            }
        }
    }
}