【问题标题】:Entity Framework Null Reference When Adding New Record添加新记录时的实体框架空引用
【发布时间】:2011-10-17 19:15:52
【问题描述】:

我目前有一个项目,它使用 Entity Framework 4.1 来登录数据库,这样我们就可以跟踪部署在多个 Web 服务器上的 Web 应用程序。我使用Scott Gu's Code First 解决方案构建了它。

所以,我有这样的代码:

logging.Logs.Add(newLog);

有时会抛出这个错误:

System.NullReferenceException : 对象引用未设置为 对象的实例。在 System.Data.Objects.ObjectStateManager.DetectConflicts(IList1 entries) at System.Data.Objects.ObjectStateManager.DetectChanges() at System.Data.Entity.Internal.Linq.InternalSet1.ActOnSet(动作动作, EntityState newState, Object entity, String methodName) at System.Data.Entity.Internal.Linq.InternalSet1.Add(Object entity) at System.Data.Entity.DbSet1.Add(TEntity entity)

大多数情况下它工作正常,但有时会打嗝。当我有多个服务器使用此代码访问/写入同一个数据库时,是否应该注意最佳实践?

现在使用的方法是每个请求都会导致系统将几个新的日志对象添加到集合中,然后保存它们的一组,而不是保存每个单独的日志记录。这是我的课程大纲。

public class LoggingService : ILoggingService
{
    Logging.Model.MyLogging logging;

    public LoggingService()
    {
        InitializeLog();
    }

    /// <summary>
    /// Save any pending log changes (only necessary if using the Add methods)
    /// </summary>
    public void SaveChanges()
    {
        //ensure that logging is not null
        InitializeLog();

        logging.SaveChanges();
    }

    #region Private Methods
    private void InitializeLog()
    {
        if (logging == null)
            logging = new Model.MyLogging();
    }

    private void Log(Level level, int sourceId, string message, bool save, int? siteId = null, int? epayCustomerId = null, 
        string sessionId = null, int? eventId = null, Exception exception = null)
    {
        if (sourceId == 0)
            throw new ArgumentNullException("sourceId", "The Source Id cannot be null and must be valid.");

        var source = (from s in logging.Sources
                     where s.SourceId == sourceId
                     select s).FirstOrDefault();

        if (source == null)
            throw new ArgumentNullException("sourceId", String.Format("No valid source found with Id [{0}].", sourceId));

        if (eventId.HasValue)
        {
            if (eventId.Value > 0)
            {
                var code = (from e in logging.Events
                            where e.EventId == eventId.Value
                            select e).FirstOrDefault();

                //if event id was passed in but no event exists, create a "blank" event
                if (code == null)
                {
                    Event newCode = new Event()
                    {
                        EventId = eventId.Value, 
                        Description = "Code definition not specified."
                    };

                    InitializeLog();
                    logging.Events.Add(newCode);
                    logging.SaveChanges();
                }
            }
        }

        var newLog = new Log()
        {
            Created = DateTime.Now,
            Message = message,
            Source = source,
            Level = level,
            EventId = eventId,
            SessionId = sessionId,
            SiteId = siteId,
            MachineName = System.Environment.MachineName,
        };

        if (exception != null)
            newLog.Exception = String.Format("{0}{1}{2}{1}", exception.Message, Environment.NewLine, exception.StackTrace);

        //ensure that the logging is not null
        InitializeLog();

        logging.Logs.Add(newLog);

        if (save)
        {
            logging.SaveChanges();
        }
    }

    #endregion

}

我将 IoC 与 StructureMap 一起使用,并且我没有将此类初始化为单例。

For<ILoggingService>().Use<LoggingService>();

我的上下文类看起来像这样:

internal class MyLogging : DbContext
{
    public DbSet<Source> Sources { get; set; }
    public DbSet<Event> Events { get; set; }
    public DbSet<Log> Logs { get; set; }

    /// <summary>
    /// DO NOT ADD ITEMS TO THIS COLLECTION
    /// </summary>
    public DbSet<LogArchive> LogArchives { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer(new MyDbContextInitializer());

        modelBuilder.Entity<Event>().Property(p => p.EventId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        modelBuilder.Entity<Source>().Property(p => p.SourceId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        modelBuilder.Entity<LogArchive>().Property(p => p.LogId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        base.OnModelCreating(modelBuilder);
    }

    //public class MyDbContextInitializer : DropCreateDatabaseIfModelChanges<MyLogging>
    public class MyDbContextInitializer : CreateDatabaseIfNotExists<MyLogging>
    {
        protected override void Seed(MyLogging dbContext)
        {
            // seed data

            base.Seed(dbContext);
        }
    }
}

我可能在做一些明显错误的事情,但我只是没有看到。

编辑: 根据要求,这是我如何调用日志记录服务代码的示例。此特定方法记录与 HTTP 请求相关的信息。我将日志项附加到一个尝试捕获中并保存在一个单独的尝试捕获中,因此如果出现问题,它至少会保存所添加的内容。处理程序是另一个通过 IoC 注入到此类的服务,它通过电子邮件将错误的详细信息发送给我。

发到服务器的单个帖子可以记录多达 50-70 个单独的详细信息,分成 10-15 个块(http 请求、发送到 Web 服务的数据、Web 服务调用的结果、返回给客户端的响应) ,这就是为什么我要添加分组然后保存分组,而不是打开和关闭与每个单独项目的连接。

public void LogHttpPostStart(HttpPostRequest request)
{
        try
        {
            //if no session is set, use the ASP.NET session
            request.SessionId = GetSessionId(request.SessionId);
            int eventId = (int)Model.Enums.Logging.Event.SubmittedByClient;

            var current = HttpContext.Current;

            if (current != null)
            {
                logService.AddDebug((int)request.Source, String.Format("{0}  HTTP Request Details  {0}", Header2Token.ToString().PadRight(HeaderTokenCount, Header2Token)),
                    siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                    eventId: eventId);

                //Server Information
                logService.AddDebug((int)request.Source, String.Format("Machine Name: {0}", current.Server.MachineName),
                    siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                    eventId: eventId);

                //User Information
                logService.AddDebug((int)request.Source, String.Format("User Host Address: {0}", current.Request.UserHostAddress),
                    siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                    eventId: eventId);
                logService.AddDebug((int)request.Source, String.Format("User Host Name: {0}", current.Request.UserHostName),
                    siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                    eventId: eventId);

                //Browser Information
                if (current.Request.Browser != null)
                {
                    logService.AddDebug((int)request.Source, String.Format("Browser: {0}", current.Request.Browser.Browser),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);
                    logService.AddDebug((int)request.Source, String.Format("Browser Version: {0}", current.Request.Browser.Version),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);
                    logService.AddDebug((int)request.Source, String.Format("User Agent: {0}", current.Request.UserAgent),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);
                    logService.AddDebug((int)request.Source, String.Format("Is Mobile Device: {0}", current.Request.Browser.IsMobileDevice.ToString()),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);

                    if (current.Request.Browser.IsMobileDevice)
                    {
                        logService.AddDebug((int)request.Source, String.Format("Mobile Device Manufacturer: {0}", current.Request.Browser.MobileDeviceManufacturer),
                            siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                            eventId: eventId);
                        logService.AddDebug((int)request.Source, String.Format("Mobile Device Model: {0}", current.Request.Browser.MobileDeviceModel),
                            siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                            eventId: eventId);
                    }

                    logService.AddDebug((int)request.Source, String.Format("Platform: {0}", current.Request.Browser.Platform),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);
                    logService.AddDebug((int)request.Source, String.Format("Cookies Enabled: {0}", current.Request.Browser.Cookies.ToString()),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);
                    logService.AddDebug((int)request.Source, String.Format("Frames Enabled: {0}", current.Request.Browser.Frames.ToString()),
                        siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                        eventId: eventId);

                    if (current.Request.Browser.JScriptVersion != null)
                    {
                        logService.AddDebug((int)request.Source, String.Format("Javascript Version: {0}", current.Request.Browser.JScriptVersion.ToString()),
                            siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                            eventId: eventId);
                    }

                }

                logService.AddDebug((int)request.Source, String.Format("{0}  End HTTP Request Details  {0}", Header2Token.ToString().PadRight(HeaderTokenCount, Header2Token)),
                    siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
                    eventId: eventId);
            }
        }
        catch (Exception ex)
        {
            handler.HandleError(true, ex);
        }

        try
        {
            logService.SaveChanges();
        }
        catch (Exception ex)
        {
            handler.HandleError(true, ex);
        }
    }

【问题讨论】:

  • SQL Server?还是另一个数据库和提供者?为什么你到处都有这些InitializeLog 电话?你在构造函数中初始化上下文,这还不够吗?如果您真的在 SaveChanges 方法中创建一个新上下文,然后立即调用该上下文的 SaveChanges,则不会发生任何事情,因为上下文是空的。如果上下文是null,我宁愿让应用程序崩溃,因为那时可能有问题。如果你默默地创建一个新的上下文,你可能会错过一个重要的错误。
  • Sql Server 2008 R2。感谢您的指点。我会把它清理干净。

标签: entity-framework-4 ef-code-first


【解决方案1】:

第一个问题是您在应用程序期间保持打开 DbContext。最佳实践是仅在需要时实例化它,然后将其释放。但这里更大的问题是你在多线程环境中使用你的LoggingService(如果我理解正确的话)但 DbContext 不是线程安全的。请参阅此 SO question 以及 ScottGu's post 上的 cmets(搜索线程一词)。因此,您应该以线程安全的方式将日志条目保存在 DbContext 之外的其他地方,并且仅在您想要访问 DB 时打开 DbContext。

+1 而不是InitializeLog() 方法,请查看System.Lazy&lt;T&gt;

【讨论】:

  • 我没有看到我在哪里违反了线程安全。 IoC 容器不会创建单例,因此它不会在应用程序的所有线程之间共享。 DBContext 是一个私有实例变量,在类的生命周期内保持打开状态。如msdn.microsoft.com/en-us/library/bb896325.aspx 中所述,上下文会自动打开和关闭与数据库的连接。我运行了一个查询,每个打开的 Web 服务器只有一个连接,这是所需的。我希望能够一次添加多条记录并将它们全部保存,因此我无法在每次调用时创建和销毁我的上下文。
  • @Josh Ok Ive missed the not singleton part. But the transient nature of the error - as you said: "is sometimes throwing this error" - still points to some concurency problem. Can you show us some sample code where you are using the LoggingService`?
  • @Josh 查看代码我现在没有其他想法。但是您仍然可以尝试将您的 Log 实体添加到 LoggigService 中的私有 List 实例中,并且仅在调用 SaveChanges 之前将它们添加到上下文中(当然在保存清除日志“缓冲区”之后)。
【解决方案2】:

可能是 EF 中的错误,也可能是您的代码中的错误,很难说。如果您发布实体类的定义,也许我们可以重现该问题,否则很难看到它。您也可以通过隔离您的代码并使用伪随机数据在无限循环中运行日志记录,直到它再次发生。

不过,如果可以的话。您对在日志服务中初始化的上下文有点偏执。如果您在构造函数中对其进行初始化,则以后无法对其进行初始化。由于您已经在使用容器,为什么不将上下文作为构造函数的参数并让容器为您注入它。您的课程基本上是 Control-Freak(反模式)。

    public class LoggingService : ILoggingService
    {
        Logging.Model.MyLogging logging;

        public LoggingService(MyLogging logging)
        {
            // check for null here
            this.logging = logging; 
            //no further null checks 
        }
        ....
     }

进一步,当您向容器注册组件时,将它们的生命周期设置为 http 请求范围。

For<ILoggingService>().HttpContextScoped().Use<LoggingService>();

如果您这样做,请不要忘记在请求结束时销毁对象:

protected void Application_EndRequest()
{
    ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
}

这样您就可以为每个请求获得一组新的对象,这些对象也会在最后得到正确处理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-30
    • 1970-01-01
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多