I currently have a project that uses Entity Framework 4.1 for logging to a database so we can track the web application which is deployed on more than one web server. I built it using Scott Gu's Code First solution.
So, I have code like this:
logging.Logs.Add(newLog);
which is sometimes throwing this error:
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.InternalSet1.Add(Object entity) at System.Data.Entity.DbSet
1.Add(TEntity entity)
Most of the time it works fine, but it will hiccup now and then. Is there a best practice I should be aware of when I have more than one server using this code to access/write to the same database?
The approach used right now is that each request causes the system to add several new log objects into the collection and then save a grouping of them, rather than save each individual log record. Here's an outline of my class.
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
}
I am using IoC with StructureMap, and I did not initialize this class as a singleton.
For<ILoggingService>().Use<LoggingService>();
And my context class looks like this:
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);
}
}
}
I'm probably doing something obviously wrong, but I just don't see it.
EDIT: As requested, here is a sample of how I call the logging service code. This particular method is logging information related to the HTTP Request. I append the the log items in one try catch and save in a separate try catch so if there is an issue it will at least save the additions that took. The handler is another service injected into this class through IoC that sends the details of the error to me via e-mail.
A single post to the server could log as many as 50-70 separate details separated into chunks of 10-15 (http request, data sent to a web service, result of the web service call, response back to the client), which is why I want to add a grouping and then save the grouping, rather than open and close a connection with each individual item.
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);
}
}