EnVisageOnline/Main/Source/EnVisage/EnVisageEntities.cs

607 lines
28 KiB
C#

namespace EnVisage
{
using Code;
using Code.Integration;
using Code.ThreadedProcessing;
using Code.BLL;
using Models.Entities;
using NLog;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using static Code.AuditProxy;
public partial class EnVisageEntities //: DbContext
{
#region Private Variables
private readonly Logger _Logger = LogManager.GetCurrentClassLogger();
#endregion
#region Private Classes
private class TransactionInformation
{
public bool IsLocalTransaction { get; set; }
public string TransactionId { get; set; }
public string ExecutorId { get; set; }
}
#endregion
#region Constructors
public EnVisageEntities(string sConnectionString) : base(sConnectionString)
{
}
#endregion
public static EnVisageEntities PrevuEntity(string sConnectionString, bool isAdo)
{
if (isAdo)
{
var entityBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
ProviderConnectionString = sConnectionString,
Metadata = @"res://*/DataModel.csdl|res://*/DataModel.ssdl|res://*/DataModel.msl"
};
sConnectionString = entityBuilder.ConnectionString;
}
return new EnVisageEntities(sConnectionString);
}
public ObjectContext ObjectContext()
{
return (this as IObjectContextAdapter).ObjectContext;
}
public override int SaveChanges()
{
var trackChangesTransaction = TrackChanges();
var result = base.SaveChanges();
if (trackChangesTransaction.IsLocalTransaction)
Task.Run(() => CommitHistoryChanges(trackChangesTransaction.TransactionId, trackChangesTransaction.ExecutorId));
return result;
}
/// <summary>Performs bulk save changes with history tracking</summary>
public void ExecuteBulkSaveChanges()
{
var trackChangesTransaction = TrackChanges();
this.BulkSaveChanges();
if (trackChangesTransaction.IsLocalTransaction)
Task.Run(() => CommitHistoryChanges(trackChangesTransaction.TransactionId, trackChangesTransaction.ExecutorId));
}
#region Private Methods
/// <summary>Sets timestamps for created and changed entities as well as for their parent container entities</summary>
private void SetTimestamps()
{
var stateManager = ObjectContext().ObjectStateManager;
var addedEntities = stateManager.GetObjectStateEntries(EntityState.Added);
var modifiedEntities = stateManager.GetObjectStateEntries(EntityState.Modified);
var removedEntities = stateManager.GetObjectStateEntries(EntityState.Deleted);
addedEntities.Where(x => x.Entity is ITimestampEntity).ToList().ForEach(t =>
{
var timestampEntity = t.Entity as ITimestampEntity;
timestampEntity?.SetCreatedTimestamp();
});
modifiedEntities.Where(x => x.Entity is ITimestampEntity).ToList().ForEach(t => (t.Entity as ITimestampEntity).SetUpdatedTimestamp());
#region Update scenario timestamps by relations
int modifiedCount = modifiedEntities.Count();
int addedCount = addedEntities.Count();
int removedCount = removedEntities.Count();
if ((modifiedCount > 0) || (addedCount > 0) || (removedCount > 0))
{
List<ObjectStateEntry> allChangedEntities = new List<ObjectStateEntry>(modifiedCount + addedCount + removedCount);
allChangedEntities.AddRange(addedEntities);
allChangedEntities.AddRange(modifiedEntities);
allChangedEntities.AddRange(removedEntities);
List<Guid> changedScenarios = GetScenarios(allChangedEntities);
if (changedScenarios.Count > 0)
{
// Exclude recently created scenarios
List<Guid> excludableScenarios =
addedEntities.Where(x => x.Entity is Scenario).Select(x => (x.Entity as Scenario).Id).ToList();
Scenarios.Where(x => !excludableScenarios.Contains(x.Id) && changedScenarios.Contains(x.Id))
.ToList()
.ForEach(s => s.SetUpdatedTimestamp());
}
allChangedEntities = null;
addedEntities = null;
modifiedEntities = null;
removedEntities = null;
}
#endregion
}
private List<Guid> GetScenarios(List<ObjectStateEntry> changedOrCreatedItems)
{
List<Guid> scenarios = new List<Guid>();
if ((changedOrCreatedItems == null) || (changedOrCreatedItems.Count < 1))
return scenarios;
scenarios.AddRange(
changedOrCreatedItems.Where(x =>
(x.Entity is ScenarioDetail) && (x.Entity as ScenarioDetail).ParentID.HasValue &&
!(x.Entity as ScenarioDetail).ParentID.Value.Equals(Guid.Empty))
.Select(t => (t.Entity as ScenarioDetail).ParentID.Value));
scenarios.AddRange(
changedOrCreatedItems.Where(x =>
(x.Entity is TeamAllocation) && !(x.Entity as TeamAllocation).ScenarioId.Equals(Guid.Empty))
.Select(t => (t.Entity as TeamAllocation).ScenarioId));
scenarios.AddRange(
changedOrCreatedItems.Where(x =>
(x.Entity is PeopleResourceAllocation) &&
!(x.Entity as PeopleResourceAllocation).ScenarioId.Equals(Guid.Empty))
.Select(t => (t.Entity as PeopleResourceAllocation).ScenarioId));
scenarios.AddRange(
changedOrCreatedItems.Where(x =>
(x.Entity is CostSaving) &&
!(x.Entity as CostSaving).ScenarioId.Equals(Guid.Empty))
.Select(t => (t.Entity as CostSaving).ScenarioId));
scenarios.AddRange(
changedOrCreatedItems.Where(x =>
(x.Entity is Rate) && (x.Entity as Rate).ParentId.HasValue &&
!(x.Entity as Rate).ParentId.Value.Equals(Guid.Empty) &&
!(x.Entity as Rate).ParentId.Value.Equals((x.Entity as Rate).ExpenditureCategoryId))
.Select(t => (t.Entity as Rate).ParentId.Value));
// For rates now return all the parents (not only scenarios)
scenarios.AddRange(
changedOrCreatedItems.Where(x =>
(x.Entity is Note) && (x.Entity as Note).ParentId.HasValue &&
!(x.Entity as Note).ParentId.Value.Equals(Guid.Empty))
.Select(t => (t.Entity as Note).ParentId.Value));
return scenarios.Distinct().ToList();
}
private void PushUpdates(IEnumerable<ObjectStateEntry> entries)
{
if (entries == null || !entries.Any())
return;
var trackingEntries = entries.Where(x => !x.IsRelationship && x.Entity != null &&
(x.Entity is Scenario || x.Entity is ScenarioDetail || x.Entity is Project))
.ToArray();
var changedScenarios = trackingEntries.Where(x => x.Entity is Scenario)
.Select(x => (x.Entity as Scenario)).ToList();
var changedScenariosIds = changedScenarios.Select(x => x.Id);
var changedScenariosViaDetailsIds = trackingEntries.Where(x => x.Entity is ScenarioDetail)
.Where(x => (x.Entity as ScenarioDetail).ParentID.HasValue)
.Select(x => (x.Entity as ScenarioDetail).ParentID.Value)
.Distinct();
var changedScenariosViaDetails = changedScenariosViaDetailsIds.Any() ? Scenarios.AsNoTracking()
.Where(x => x.Status == (int)ScenarioStatus.Active &&
!changedScenariosIds.Contains(x.Id) &&
changedScenariosViaDetailsIds.Contains(x.Id))
.ToList() : new List<Scenario>();
if (changedScenariosViaDetails.Any())
changedScenarios.AddRange(changedScenariosViaDetails);
var changedProjects = trackingEntries.Where(x => x.Entity is Project)
.Select(x => (x.Entity as Project)).ToList();
var changedProjectsIds = changedProjects.Select(x => x.Id);
var changedProjectsViaScenariosIds = changedScenarios.Where(x => x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct();
var changedProjectsViaScenarios = changedProjectsViaScenariosIds.Any() ? Projects.AsNoTracking()
.Where(x => !changedProjectsIds.Contains(x.Id) &&
changedProjectsViaScenariosIds.Contains(x.Id))
.ToList() : new List<Project>();
if (changedProjectsViaScenarios.Any())
changedProjects.AddRange(changedProjectsViaScenarios);
var parentProjectsIds = changedProjects.Where(x => x.ParentProjectId.HasValue)
.Select(x => x.ParentProjectId.Value)
.Distinct();
var parentProjects = parentProjectsIds.Any() ? Projects.AsNoTracking()
.Where(x => !changedProjectsIds.Contains(x.Id) &&
!changedProjectsViaScenariosIds.Contains(x.Id) &&
parentProjectsIds.Contains(x.Id))
.ToList() : new List<Project>();
if (parentProjects.Any())
changedProjects.AddRange(parentProjects);
var requiredStatuses = changedProjects.Select(x => x.StatusId).Distinct();
var statusesDict = requiredStatuses.Any() ? Status.Where(x => requiredStatuses.Contains(x.Id)).ToArray().ToDictionary(x => x.Id) : new Dictionary<Guid, Status>();
var requiredTypes = changedProjects.Select(x => x.TypeId).Distinct();
var typesDict = requiredTypes.Any() ? Types.Where(x => requiredTypes.Contains(x.Id)).ToArray().ToDictionary(x => x.Id) : new Dictionary<Guid, Type>();
var changedScenariosDict = changedScenarios.ToDictionary(x => x.Id);
var changedProjectsDict = changedProjects.ToDictionary(x => x.Id);
foreach (var entry in trackingEntries)
{
var captureForNotification = false;
Project project = null;
Scenario scenario = null;
Status _status = null;
Type _type = null;
if (entry.Entity is ScenarioDetail)
{
var scenarioDetail = entry.Entity as ScenarioDetail;
if (scenarioDetail.ParentID.HasValue && changedScenariosDict.ContainsKey(scenarioDetail.ParentID.Value))
scenario = changedScenariosDict[scenarioDetail.ParentID.Value];
if (scenario == null)
return;
if (scenario.Type == (int)ScenarioType.Portfolio && scenario.ParentId.HasValue)
if (changedProjectsDict.ContainsKey(scenario.ParentId.Value))
project = changedProjectsDict[scenario.ParentId.Value];
}
else if (entry.Entity is Scenario)
{
scenario = entry.Entity as Scenario;
if (scenario.Type == (int)ScenarioType.Portfolio && scenario.ParentId.HasValue)
if (changedProjectsDict.ContainsKey(scenario.ParentId.Value))
project = changedProjectsDict[scenario.ParentId.Value];
}
else if (entry.Entity is Project)
{
var projectId = (entry.Entity as Project).Id;
if (changedProjectsDict.ContainsKey(projectId))
project = changedProjectsDict[projectId];
}
if (project != null)
{
_status = statusesDict.ContainsKey(project.StatusId) ? statusesDict[project.StatusId] : null;
_type = typesDict.ContainsKey(project.TypeId) ? typesDict[project.TypeId] : null;
if (_status != null && _status.Id != Guid.Empty && _type != null && _type.Id != Guid.Empty)
{
if (_status.NotifyOnProjectDelete.HasValue)
if (_status.NotifyOnProjectDelete.Value)
captureForNotification = true;
if (_status.NotifyOnProjectCreate.HasValue)
if (_status.NotifyOnProjectCreate.Value)
captureForNotification = true;
if (_status.NotifyOnProjectChange.HasValue)
if (_status.NotifyOnProjectChange.Value)
captureForNotification = true;
if (_type.NotifyOnProjectDelete.HasValue)
if (_type.NotifyOnProjectDelete.Value)
captureForNotification = true;
if (_type.NotifyOnProjectCreate.HasValue)
if (_type.NotifyOnProjectCreate.Value)
captureForNotification = true;
if (_type.NotifyOnProjectChange.HasValue)
if (_type.NotifyOnProjectChange.Value)
captureForNotification = true;
}
}
// if we are not capturing data, and the scenario is not active we do not
// do any push changes.
if (entry.Entity is Scenario || entry.Entity is ScenarioDetail)
if (!captureForNotification)
return;
string action = entry.State == EntityState.Added ? "Add" : "Update";
_Logger.Log(LogLevel.Debug, "CRM " + action + " for " + (entry.Entity is Scenario ? "Scenario" : "Project"));
var crmDal = IntergrationHelper.GetIntergrationClass(IntergrationAccessType.ProjectExport, null);
Dictionary<string, Dictionary<string, string>> pushToCrmCollection = new Dictionary<string, Dictionary<string, string>>();
string tableName = entry.Entity.GetType().Name.Split('_')[0];
Dictionary<string, string> colcollection = new Dictionary<string, string>();
pushToCrmCollection.Add(tableName, colcollection);
// TODO: do we realy need to track each property or just changed ones?
var props = entry.Entity.GetType().GetProperties();
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
foreach (var elem in props)
{
string fieldName = elem.Name;
try
{
var val = Convert.ToString(elem.GetValue(entry.Entity));
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
_Logger.Log(LogLevel.Debug, "CRM Field Update for " + (entry.Entity is Scenario ? "Scenario" : "Project") + fieldName + "," + val);
colcollection.Add(fieldName, val);
}
}
catch
{
}
}
}
if (pushToCrmCollection.Any())
{
Guid parentKey = Guid.Empty;
var entity = entry.Entity as Scenario;
if (entity != null)
{
Scenario s = entity;
try
{
parentKey = Guid.Parse(s.Project.ProjectNumber);
}
catch
{
}
}
else if (entry.Entity is Project)
{
Project p = (Project)entry.Entity;
try
{
parentKey = Guid.Parse(p.ProjectNumber);
}
catch
{
}
}
if (parentKey != Guid.Empty)
{
BackgroundProcessManager bpm = new BackgroundProcessManager();
bpm.UpdateCRMAsync(pushToCrmCollection, parentKey, crmDal, action);
}
}
try
{
if (captureForNotification)
{
if (entry.Entity is Project)
{
if (project.ParentProjectId.HasValue)
if (changedProjectsDict.ContainsKey(project.ParentProjectId.Value))
project = changedProjectsDict[project.ParentProjectId.Value];
string url = Code.Session.AbsoluteUrl.EditProjectUrl(project.Id, this);
(new NotificationManager(null)).SendProjectChangeNotifications(project, _type, _status, entry.State, url);
}
if (entry.Entity is Scenario || entry.Entity is ScenarioDetail)
{
if (scenario.Type == (int)ScenarioType.Portfolio)
{
string url = Code.Session.AbsoluteUrl.EditScenarioUrl(scenario.Id, this);
(new NotificationManager(null)).SendScenarioChangeNotifications(scenario, _type, _status, entry.State, url);
}
}
}
}
catch (Exception dds)
{
_Logger.Error(dds, "error in Capture change notifications!!!");
}
}
}
private List<HistorySaveItemPropertyModel> ResolveChanges(ObjectStateEntry stateEntry)
{
#region Arguments Validation
if (stateEntry == null)
throw new ArgumentNullException(nameof(stateEntry));
#endregion
if (stateEntry.State == EntityState.Deleted)
return new List<HistorySaveItemPropertyModel>();
var properties = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(x => x.FieldType.Name).ToList();
var changedProperties = new List<HistorySaveItemPropertyModel>(properties.Count);
foreach (var propertyName in properties)
{
var oldValue = stateEntry.State == EntityState.Added ? DBNull.Value : stateEntry.OriginalValues[propertyName];
var newValue = stateEntry.CurrentValues[propertyName];
// we may not check for null value because if field has null in the database it will be presented as DBNull value here
if (!oldValue.Equals(newValue))
{
changedProperties.Add(new HistorySaveItemPropertyModel
{
Name = propertyName,
OldValue = oldValue.ToString(),
NewValue = newValue.ToString(),
});
}
}
return changedProperties;
}
private string ResolveGroupKey(object entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
if (entity is CostSaving)
return ((CostSaving)entity).ScenarioId.ToString();
if (entity is HolidayAllocation)
return ((HolidayAllocation)entity).HolidayId.ToString();
if (entity is NonProjectTimeResourceAllocation)
return ((NonProjectTimeResourceAllocation)entity).NonProjectTime2ResourceId.ToString();
if (entity is NonProjectTimeTeamAllocation)
return ((NonProjectTimeTeamAllocation)entity).NonProjectTime2TeamId.ToString();
if (entity is PeopleResourceActual)
return ((PeopleResourceActual)entity).PeopleResourceId.ToString();
if (entity is PeopleResourceAllocation)
return ((PeopleResourceAllocation)entity).ScenarioId.ToString();
if (entity is PeopleResourceExpCatChange)
return ((PeopleResourceExpCatChange)entity).PeopleResourceId.ToString();
if (entity is PeopleResource2Team)
return ((PeopleResource2Team)entity).PeopleResourceId.ToString();
if (entity is ScenarioDetail)
return ((ScenarioDetail)entity).ParentID?.ToString();
if (entity is TeamAllocation)
return ((TeamAllocation)entity).ScenarioId.ToString();
if (entity is Security)
return ((Security)entity).PrincipalId.ToString();
if (entity is ProjectAccess)
return ((ProjectAccess)entity).PrincipalId.ToString();
if (entity is Holiday2ExpenditureCategory)
return ((Holiday2ExpenditureCategory)entity).HolidayId.ToString();
if (entity is Holiday2PeopleResource)
return ((Holiday2PeopleResource)entity).HolidayId.ToString();
if (entity is Holiday2Team)
return ((Holiday2Team)entity).HolidayId.ToString();
if (entity is Team2Project)
return (entity as Team2Project).ProjectId.ToString();
if (entity is TagLink)
return (entity as TagLink).ParentID.ToString();
if (entity is StrategicGoal2Project)
return (entity as StrategicGoal2Project).ProjectId.ToString();
return null;
}
private string ResolveEntityId(ObjectStateEntry stateEntryEntity, List<HistorySaveItemPropertyModel> properties)
{
if (stateEntryEntity == null)
throw new ArgumentNullException(nameof(stateEntryEntity));
if (stateEntryEntity.EntityKey != null)
if (stateEntryEntity.EntityKey.EntityKeyValues != null)
if (stateEntryEntity.EntityKey.EntityKeyValues.Any())
return stateEntryEntity.EntityKey.EntityKeyValues[0].Value.ToString();
if (properties == null || !properties.Any())
return null;
var propertyKey = properties.FirstOrDefault(x => x.Name.ToLower() == "id");
return propertyKey?.NewValue;
}
private string ResolveEntityType(ObjectStateEntry stateEntryEntity)
{
if (stateEntryEntity == null)
throw new ArgumentNullException(nameof(stateEntryEntity));
if (stateEntryEntity.EntitySet != null)
{
if (stateEntryEntity.EntitySet.ElementType != null)
return stateEntryEntity.EntitySet.ElementType.Name;
return stateEntryEntity.EntitySet.Name;
}
return null;
}
private TransactionInformation TrackChanges()
{
#region Refresh Changes
// DetectChanges is called as part of the implementation of the SaveChanges.
// This means that if you override SaveChanges in your context, then DetectChanges will not have been called before your SaveChanges method is called.
// This can sometimes catch people out, especially when checking if an entity has been modified or not since its state may not be set to Modified until DetectChanges is called.
// So we need to call it right before we'll try to get changed entities to not skip some changes
ChangeTracker.DetectChanges();
#endregion
#region Automatic changes TimeStamping
SetTimestamps();
#endregion
// TODO: AK, review why there is no user
var executorId = Utils.CurrentUserId();
var stateManager = ObjectContext().ObjectStateManager;
var changes = stateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added | EntityState.Deleted)
.Where(x => !x.IsRelationship && x.Entity != null &&
!(x.Entity is History) &&
!(x.Entity is supt_ImportMessages) &&
!(x.Entity is supt_tbl_MongoDBBackup) &&
!(x.Entity is supt_tbl_ProjectIds) &&
!(x.Entity is supt_tbl_RecParser) &&
!(x.Entity is FiscalCalendar));
var isLocalTransaction = false;
var transactionId = this.GetClientConnectionId();
if (transactionId == null)
{
transactionId = Guid.NewGuid().ToString();
isLocalTransaction = true;
}
foreach (var stateEntryEntity in changes)
{
if (stateEntryEntity.Entity == null)
continue;
#region Prepare History Items and Log History
var groupKey = ResolveGroupKey(stateEntryEntity.Entity);
var properties = ResolveChanges(stateEntryEntity);
var entityId = ResolveEntityId(stateEntryEntity, properties);
var entityType = ResolveEntityType(stateEntryEntity);
var modificationType = stateEntryEntity.State.ToString();
// we do not need to save entity if no properties were changed
if (stateEntryEntity.State == EntityState.Deleted || properties.Any())
LogHistory(transactionId, groupKey, entityId, entityType, modificationType, properties);
#endregion
}
#region Capture updates for CRM/3rd party systems
//capture changes to push to crm
PushUpdates(changes);
#endregion
var transactionInfo = new TransactionInformation
{
IsLocalTransaction = isLocalTransaction,
TransactionId = transactionId,
ExecutorId = executorId
};
return transactionInfo;
}
#endregion
}
}