2011-10-17 72 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(); 
} 

這樣,你得到也得到正確設置在每個請求新的一組對象結束。