简体   繁体   中英

The relationship could not be changed because one or more of the foreign-key properties is non-nullable. (2)

I'm using Entity Framework 6.1.3 in a .NET 4.5 application with Code First and a hand-crafted table schema on an Oracle database server. Most things work fine. For a new function the following exception is thrown in SaveChanges :

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

Stack trace:

  • System.Data.Entity.Core.Objects.ObjectContext.PrepareToSaveChanges(System.Data.Entity.Core.Objects.SaveOptions)
  • System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(System.Data.Entity.Core.Objects.SaveOptions, bool)
  • System.Data.Entity.Internal.InternalContext.SaveChanges()
  • (my code)

It says nothing about the problem. It doesn't help me to find it. The SQL log is empty so I guess the problem was detected by EF locally before even trying to reach out to the database.

My question is: How can I debug this thing? Where's the details about which foreign key in which object has which value but shouldn't have it? Are there trace logs available for Entity Framework that contain internal data about what was done?

The situation is way too complex to show it here so please don't ask for it. I want to help myself out of this, I just need assistence with that.

You can override SaveChanges method of DBContext, and see what all properties are update/deleted/modified, it will minimize the list you need to check for your error. I needed just deleted/detached ones, but you can modify .where for what you need.

public override int SaveChanges()
        {
            try
            {
                var debug = false;

                if (debug)
                {
                    var modifiedEntities = ChangeTracker.Entries()
                            .Where(p => p.State == EntityState.Deleted || p.State == EntityState.Detached).ToList();

                    foreach (var change in modifiedEntities)
                    {
                        var entityName = change.Entity.GetType().Name;
                        System.Diagnostics.Debug.WriteLine(string.Format("Entity {0}", entityName));

                    }
                }


                return base.SaveChanges();
            }
            catch (DbEntityValidationException e)
            {
                foreach (var eve in e.EntityValidationErrors)
                {
                    Debug.WriteLine("Error while Save Changes:");
                    Debug.WriteLine("Entity {0} has the following validation errors:", eve.Entry.Entity.GetType().Name);
                    foreach (var ve in eve.ValidationErrors)
                    {
                        Debug.WriteLine("Property:{0}, Error: {1}",
                            ve.PropertyName, ve.ErrorMessage);
                    }
                }
                throw;
            }
            catch (Exception)
            {
                throw;
            }
        }

You can log SQL

using (var context = new BlogContext())
{
    context.Database.Log = Console.Write;

    // Your code here...
}

Even more you can make a custom log formatter like this

public class OneLineFormatter : DatabaseLogFormatter
{
    public OneLineFormatter(DbContext context, Action<string> writeAction)
        : base(context, writeAction)
    {
    }

    public override void LogCommand<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
        Write(string.Format(
            "Context '{0}' is executing command '{1}'{2}",
            Context.GetType().Name,
            command.CommandText.Replace(Environment.NewLine, ""),
            Environment.NewLine));
    }

    public override void LogResult<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
    }
}

You can intercept the exact exception by your own log formatter for that exact exception assuming it is 'DbEntityValidationException'

public class MyExcpetionCommandInterceptor : IDbCommandInterceptor
{


    public void NonQueryExecuting(
        DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        LogIfNonAsync(command, interceptionContext);
    }

    public void NonQueryExecuted(
        DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        LogIfError(command, interceptionContext);
    }

    public void ReaderExecuting(
        DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        LogIfNonAsync(command, interceptionContext);
    }

    public void ReaderExecuted(
        DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        LogIfError(command, interceptionContext);
    }

    public void ScalarExecuting(
        DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        LogIfNonAsync(command, interceptionContext);
    }

    public void ScalarExecuted(
        DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        LogIfError(command, interceptionContext);
    }

    private void LogIfNonAsync<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
        if (!interceptionContext.IsAsync)
        {
            Logger.Warn("Non-async command used: {0}", command.CommandText);
        }
    }

    private void LogIfError<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
        if (interceptionContext.Exception.GetType() == typeof(DbEntityValidationException) || typeof(DbEntityValidationException).IsAssignableFrom(interceptionContext.Exception.GetType()) )
        {
            Logger.Error("Command {0} failed with exception {1}",
                command.CommandText, interceptionContext.Exception);
        }
    }
}

There is a nice article about that in Microsoft Logging and intercepting database operations

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM