由于一个或多个外键属性是非空的,所以无法更改关系。

4
我正在使用Entity Framework 6.1.3在一个.NET 4.5应用程序中,采用Code First和手工制作的表模式,在Oracle数据库服务器上。大多数功能都能正常工作。对于一个新功能,在SaveChanges中抛出以下异常:
操作失败:由于一个或多个外键属性是非空的,因此无法更改关系。当对关系进行更改时,相关的外键属性将设置为null值。如果外键不支持null值,则必须定义一个新关系,将外键属性分配给另一个非null值,或删除不相关的对象。
堆栈跟踪:
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)
它没有提供有关问题的任何信息。这并没有帮助我找到问题。SQL日志为空,所以我猜测问题在EF尝试连接数据库之前就被本地检测到了。
我的问题是: 如何调试这个问题?哪个对象的哪个外键具有不应该有的值的详细信息在哪里?Entity Framework是否提供可包含有关已执行操作的内部数据的跟踪日志?
情况太复杂了,不能在此展示,请不要要求展示。我想自己解决这个问题,只需要协助一下。

如果您在调试模式下使用Linq to Entities,则可以查看生成的SQL。请发布出现问题的代码,因为我们无法根据这些少量信息提供帮助。 - James Dev
同时发布您的模型类。 - James Dev
跟进詹姆斯的建议。http://blogs.msdn.com/b/mpeder/archive/2014/06/16/how-to-see-the-actual-sql-query-generated-by-entity-framework.aspx 如果你查看SQL(听起来会非常复杂),你应该能够看到一些外键被设置为NULL。 - Vlad274
没有SQL,那个日志已经存在且不会写入任何内容。从我看到的情况来看,错误发生在SQL被生成之前。这些模型类太长了,可能是机密的。由于我不知道哪些类受到影响,所以也不知道该发布什么。你不想调试我的整个应用程序吧。我只需要知道从EF中哪里可以找到缺失的信息。 - ygoe
阅读 EF 源代码,似乎它来自于某种“概念上的空值”。有人知道这是什么意思吗? - ygoe
2个回答

2

您可以重写DBContext的SaveChanges方法,并查看所有已更新/删除/修改的属性,这将最小化您需要检查错误的列表。我只需要删除/分离的内容,但您可以根据需要修改.where

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

2
您可以记录SQL。
using (var context = new BlogContext())
{
    context.Database.Log = Console.Write;

    // Your code here...
}

甚至更多,您可以创建一个自定义日志格式化程序,如下所示。
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)
    {
    }
}

您可以通过自己的日志格式化程序来拦截确切的异常,假设它是“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);
        }
    }
}

在微软的日志记录和拦截数据库操作文章里有很好的介绍。


无论我的项目是四年前还是现在,我都已经退出了,所以无法再验证这个答案了。 - ygoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接