protected void PreProcessFeatures() { List <string> distinctMsgFeatures = (from s in message.Sessions from fu in s.FeatureUses select fu.FeatureName).Distinct().ToList(); if (distinctMsgFeatures.Count > 0) { List <string> knownFeatures = ImportCache.GetFeatureNames(repository); List <string> missing = distinctMsgFeatures.Except(knownFeatures).ToList(); if (missing.Count > 0) { foreach (string fn in missing) { Feature modelFeature = new Feature() { Name = fn }; repository.Context.Features.AddObject(modelFeature); } repository.IgnoreDuplicateKeysOnSaveChanges <Feature>(); ImportCache.InvalidateFeaturesCaches(); } } }
protected void PreProcessActivationMethods() { List <string> distinctMsgActivationMethods = (from s in message.Sessions from fu in s.FeatureUses select fu.ActivationMethod ?? string.Empty).Distinct().ToList(); if (distinctMsgActivationMethods.Count > 0) { List <string> knownActivationMethods = ImportCache.GetActivationMethodNames(repository); List <string> missing = distinctMsgActivationMethods.Except(knownActivationMethods).ToList(); if (missing.Count > 0) { foreach (string am in missing) { ActivationMethod modelAM = new ActivationMethod() { Name = am }; repository.Context.ActivationMethods.AddObject(modelAM); } repository.IgnoreDuplicateKeysOnSaveChanges <ActivationMethod>(); ImportCache.InvalidateActivationMethodsCaches(); } } }
protected void PreProcessEnvironmentDataValues() { List <string> distinctMsgEnvValues = (from s in message.Sessions from p in s.EnvironmentProperties select p.Value).Distinct().ToList(); if (distinctMsgEnvValues.Count > 0) { List <string> knownDataValueNames = ImportCache.GetEnvironmentDataValueNames(repository); List <string> missing = distinctMsgEnvValues.Except(knownDataValueNames).ToList(); if (missing.Count > 0) { foreach (string vn in missing) { EnvironmentDataValue modelValue = new EnvironmentDataValue() { Name = vn }; repository.Context.EnvironmentDataValues.AddObject(modelValue); } repository.IgnoreDuplicateKeysOnSaveChanges <EnvironmentDataValue>(); ImportCache.InvalidateEnvironmentDataValueCaches(); } } }
protected void PreProcessEnvironmentDataNames() { List <string> distinctMsgEnvProperties = (from s in message.Sessions from p in s.EnvironmentProperties select p.Name).Distinct().ToList(); // did we receive environment data at all? if (distinctMsgEnvProperties.Count > 0) { List <string> knownDataNames = ImportCache.GetEnvironmentDataNameNames(repository); List <string> missing = distinctMsgEnvProperties.Except(knownDataNames).ToList(); // this happens rarely for environment data names if (missing.Count > 0) { foreach (string envdn in missing) { EnvironmentDataName modelEdn = new EnvironmentDataName() { Name = envdn }; repository.Context.EnvironmentDataNames.AddObject(modelEdn); } repository.IgnoreDuplicateKeysOnSaveChanges <EnvironmentDataName>(); ImportCache.InvalidateEnvironmentDataNamesCaches(); } } }
protected void PreProcessExceptions() { List <ExceptionImport> exceptions = (from s in message.Sessions from e in s.Exceptions select new ExceptionImport(e) { ClientSessionId = s.SessionID, IsFirstInSession = false }).ToList(); if (0 == exceptions.Count) { return; // no exceptions reported, denormalisedExceptions remains null } // mark first exception in session - note that above LINQ query does not mix up order of items long clientSession = -1; exceptions.ForEach(ex => { if (ex.ClientSessionId != clientSession) { ex.IsFirstInSession = true; clientSession = ex.ClientSessionId; } }); List <string> distinctMsgExceptionGroups = (from e in exceptions select e.FingerprintHash).Distinct().ToList(); List <string> knownExceptionGroups = ImportCache.GetExceptionGroupFingerprintHashes(repository); List <string> missing = distinctMsgExceptionGroups.Except(knownExceptionGroups).ToList(); if (missing.Count > 0) { List <ExceptionImport> groupsToAdd = (from e in exceptions join m in missing on e.FingerprintHash equals m select e).Distinct(new ExceptionImportGroupEqualityComparer()).ToList(); foreach (ExceptionImport imp in groupsToAdd) { ExceptionGroup modelGroup = new ExceptionGroup() { ExceptionFingerprint = imp.Fingerprint, ExceptionLocation = imp.Location, ExceptionType = imp.Type, TypeFingerprintSha256Hash = imp.FingerprintHash }; repository.Context.ExceptionGroups.AddObject(modelGroup); } repository.IgnoreDuplicateKeysOnSaveChanges <ExceptionGroup>(); ImportCache.InvalidateExceptionGroupsCaches(); } this.denormalisedExceptions = exceptions; }
// we intentionally don't build the full model in memory first (user -> sessions -> data tables) // avoiding concurrency issues (eg type tables) is more important than fewer database writes public void ProcessMessage() { string userGuid = message.UserID.ToString(); if (String.IsNullOrEmpty(userGuid)) { return; } PreProcessTypes(); int userId = repository.FindOrInsertUserByGuid(userGuid); /* Session duplicate detection */ List <long> clientSessionIdList = message.Sessions.Select(s => s.SessionID).ToList(); if (clientSessionIdList.Count > 0) { // Match on Id only to execute a T-SQL IN statement var potentialDuplicates = (from s in repository.Context.Sessions where s.UserId == userId && clientSessionIdList.Contains(s.ClientSessionId) select new { s.ClientSessionId, s.StartTime }).ToList(); if (potentialDuplicates.Count > 0) { // Remove all duplicates that were detected from this message // SQL Server doesn't store times with the same accurracy as .NET; so we only look whether the times were close message.Sessions.RemoveAll(s => potentialDuplicates.Any(d => s.SessionID == d.ClientSessionId && Math.Abs((s.StartTime - d.StartTime).TotalSeconds) < 1)); } } List <Session> newSessions = new List <Session>(); foreach (UsageDataSession msgSession in message.Sessions) { UsageDataEnvironmentProperty appVersion = msgSession.EnvironmentProperties.FirstOrDefault(ep => ep.Name == "appVersion"); int appVersionMajor = 0, appVersionMinor = 0, appVersionBuild = 0, appVersionRevision = 0; if (null != appVersion && !String.IsNullOrEmpty(appVersion.Value)) { Version v; if (Version.TryParse(appVersion.Value, out v)) { appVersionMajor = v.Major; appVersionMinor = v.Minor; appVersionBuild = v.Build; appVersionRevision = v.Revision; } } // Set up the association with the commit; if that's already stored in the database. UsageDataEnvironmentProperty commitHash = msgSession.EnvironmentProperties.FirstOrDefault(ep => ep.Name == "commit"); int?commitId = null; if (commitHash != null) { Commit commit = repository.GetCommitByHash(commitHash.Value); if (commit != null) { commitId = commit.Id; } } Session modelSession = new Session() { ClientSessionId = msgSession.SessionID, StartTime = msgSession.StartTime, EndTime = msgSession.EndTime, UserId = userId, AppVersionMajor = appVersionMajor, AppVersionMinor = appVersionMinor, AppVersionBuild = appVersionBuild, AppVersionRevision = appVersionRevision, CommitId = commitId, IsDebug = msgSession.EnvironmentProperties.Any(ep => ep.Name == "debug"), FirstException = msgSession.Exceptions.Min(e => (DateTime?)e.Time), // cast time to nullable to that Min/Max() can return null LastFeatureUse = msgSession.FeatureUses.Max(f => (DateTime?)f.Time) // when there are no exceptions or feature uses }; newSessions.Add(modelSession); repository.Context.Sessions.AddObject(modelSession); } repository.Context.SaveChanges(); // Save #2 List <EnvironmentDataName> storedEnvNames = ImportCache.GetEnvironmentDataNames(repository); List <EnvironmentDataValue> storedEnvValues = ImportCache.GetEnvironmentDataValues(repository); var insertEnvProperties = (from s in message.Sessions from prop in s.EnvironmentProperties join envName in storedEnvNames on prop.Name equals envName.Name join envValue in storedEnvValues on prop.Value equals envValue.Name join storedSession in newSessions on s.SessionID equals storedSession.ClientSessionId select new EnvironmentData() { SessionId = storedSession.Id, EnvironmentDataNameId = envName.Id, EnvironmentDataValueId = envValue.Id }); foreach (var ede in insertEnvProperties) { repository.Context.EnvironmentDatas.AddObject(ede); } List <ActivationMethod> storedActivationMethods = ImportCache.GetActivationMethods(repository); List <Feature> storedFeatures = ImportCache.GetFeatures(repository); var insertFeatureUse = (from s in message.Sessions from fu in s.FeatureUses join f in storedFeatures on fu.FeatureName equals f.Name join am in storedActivationMethods on fu.ActivationMethod ?? string.Empty equals am.Name join storedSession in newSessions on s.SessionID equals storedSession.ClientSessionId select new ICSharpCode.UsageDataCollector.DataAccess.Collector.FeatureUse() { ActivationMethodId = am.Id, FeatureId = f.Id, SessionId = storedSession.Id, UseTime = fu.Time, EndTime = fu.EndTime }); foreach (var fue in insertFeatureUse) { repository.Context.FeatureUses.AddObject(fue); } if (null != denormalisedExceptions) { List <ExceptionGroup> storedExGroups = ImportCache.GetExceptionGroups(repository); var insertExceptions = (from e in denormalisedExceptions join g in storedExGroups on e.FingerprintHash equals g.TypeFingerprintSha256Hash join storedSession in newSessions on e.ClientSessionId equals storedSession.ClientSessionId select new ICSharpCode.UsageDataCollector.DataAccess.Collector.Exception() { ExceptionGroupId = g.Id, SessionId = storedSession.Id, Stacktrace = e.StackTrace, ThrownAt = e.Time, IsFirstInSession = e.IsFirstInSession }); foreach (var e in insertExceptions) { repository.Context.Exceptions.AddObject(e); } } repository.Context.SaveChanges(); // Save #3 }