2011-10-17 73 views
3

我目前有一个使用Entity Framework 4.1登录数据库的项目,因此我们可以跟踪部署在多个Web服务器上的Web应用程序。我使用Scott Gu's Code First解决方案来构建它。添加新记录时的实体框架空引用

所以,我有这样的代码:

logging.Logs.Add(newLog); 

这是有时引发此错误:

System.NullReferenceException : Object reference not set to an instance of an object. at System.Data.Objects.ObjectStateManager.DetectConflicts(IList 1 entries) at System.Data.Objects.ObjectStateManager.DetectChanges() at System.Data.Entity.Internal.Linq.InternalSet 1.ActOnSet(Action action, EntityState newState, Object entity, String methodName) at System.Data.Entity.Internal.Linq.InternalSet 1.Add(Object entity) at System.Data.Entity.DbSet 1.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 

} 

我在StructureMap中使用IoC,并没有将此类初始化为单例。

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请求相关的信息。我在一个try catch中追加日志项,并保存在一个单独的try catch中,所以如果有问题,它至少会保存所添加的内容。处理程序是通过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); 
     } 
    } 
+0

SQL Server?或者其他数据库和提供商?为什么所有这些'InitializeLog'调用都在这个地方?你在构造函数中初始化上下文,这不够吗?如果您真的会在SaveChanges方法中创建新的上下文,然后立即调用该上下文的SaveChanges,则由于上下文为空,因此不会发生任何事情。如果上下文为'null',我宁愿让应用程序崩溃,因为那可能是错误的。如果你默默地创造一个新的环境,你错过了一个重要的错误。 – Slauma

+0

Sql Server 2008 R2。感谢指针。我会清理干净的。 – Josh

回答

1

第一个问题是您在应用程序期间保持打开您的DbContext。最佳做法是仅在需要时将其实例化,然后进行处理。但更大的问题在于,您在多线程环境中使用LoggingService(如果我正确地理解了它),但DbContext不是线程安全的。看到这个SO question以及ScottGu's post(搜索单词线程)上的意见。所以你应该保持你的日志条目在一个线程安全的地方,而不是在DbContext中,并且当你想访问数据库时只打开一个DbContext。

+1取而代之的是InitializeLog()方法的检出System.Lazy<T>

+0

我看不到我违反了线程安全。 IoC容器不会创建单例,因此它不会在应用程序的所有线程中共享。 DBContext是一个私有实例变量,在类的整个生命周期中保持打开状态。正如http://msdn.microsoft.com/en-us/library/bb896325.aspx中所述,上下文自动打开并关闭与数据库的连接。我运行了一个查询,并且每个Web服务器只打开一个连接,这是所需的。我希望能够添加多个记录并将它们一次全部保存,所以我无法在每次调用时创建和销毁我的上下文。 – Josh

+0

@Josh好吧我错过了没有单身的部分。但是,错误的暂时性 - 如你所说:“有时会抛出这个错误” - 仍然指向一些协调问题。你可以向我们展示一些你使用LoggingService的示例代码吗? – nemesv

+0

我添加了你想看的代码。 – Josh

1

可能是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(); 
} 

这样,你得到也得到正确设置在每个请求新的一组对象结束。