/// <summary> /// Merge the specified 'updatedProject' changes into the specified 'originalProject'. /// </summary> /// <param name="originalProject"></param> /// <param name="updatedProject"></param> /// <param name="context"></param> public static void Merge(this Entity.Project originalProject, Entity.Project updatedProject, PimsContext context) { // Update a project var agency = originalProject.Agency; var originalMetadata = context.Deserialize <DisposalProjectMetadata>(originalProject.Metadata ?? "{}"); // TODO: Need to test whether automatically overwriting the metadata is correct. var createdById = originalProject.CreatedById; var updatedById = originalProject.UpdatedById; context.Entry(originalProject).CurrentValues.SetValues(updatedProject); originalProject.Agency = agency; // Don't want to allow agency to change through this method. originalProject.CreatedById = updatedById; // Don't want these updated externally. originalProject.UpdatedById = updatedById; // Don't want these updated externally. context.SetOriginalRowVersion(originalProject); var agencies = originalProject.Agency.ParentId.HasValue ? new[] { originalProject.AgencyId } : context.Agencies.Where(a => a.ParentId == originalProject.AgencyId || a.Id == originalProject.AgencyId).Select(a => a.Id).ToArray(); // Update all properties foreach (var property in updatedProject.Properties) { var existingProperty = originalProject.Properties .FirstOrDefault(b => b.PropertyType == Entity.PropertyTypes.Land && b.ParcelId == property.ParcelId && b.ProjectId == updatedProject.Id || b.PropertyType == Entity.PropertyTypes.Building && b.ProjectId == updatedProject.Id && b.BuildingId == property.BuildingId); if (existingProperty == null) { //Todo: Navigation properties on project object were causing concurrency exceptions. var eproperty = property.PropertyType == Entity.PropertyTypes.Land ? context.Parcels.Find(property.ParcelId) : context.Buildings.Find(property.BuildingId) as Entity.Property; // Ignore properties that don't exist. if (eproperty != null) { if (property.PropertyType == Entity.PropertyTypes.Land) { var existingParcel = context.Parcels .Include(p => p.Agency) .Include(p => p.Evaluations) .Include(p => p.Fiscals) .FirstOrDefault(p => p.Id == property.ParcelId); existingParcel.ThrowIfPropertyNotInProjectAgency(agencies); existingParcel.UpdateProjectNumbers(updatedProject.ProjectNumber); } else { var existingBuilding = context.Buildings .Include(b => b.Agency) .Include(p => p.Evaluations) .Include(p => p.Fiscals) .FirstOrDefault(p => p.Id == property.BuildingId); existingBuilding.ThrowIfPropertyNotInProjectAgency(agencies); existingBuilding.UpdateProjectNumbers(updatedProject.ProjectNumber); } originalProject.AddProperty(eproperty); } } else { if (property.PropertyType == Entity.PropertyTypes.Land) { // Only allow editing the classification and evaluations/fiscals for now existingProperty.UpdateProjectNumbers(updatedProject.ProjectNumber); if (property.Parcel != null) { context.Entry(existingProperty.Parcel).Collection(p => p.Evaluations).Load(); existingProperty.Parcel.ClassificationId = property.Parcel.ClassificationId; existingProperty.Parcel.Zoning = property.Parcel.Zoning; existingProperty.Parcel.ZoningPotential = property.Parcel.ZoningPotential; foreach (var evaluation in property.Parcel.Evaluations) { var existingEvaluation = existingProperty.Parcel.Evaluations .FirstOrDefault(e => e.Date == evaluation.Date && e.Key == evaluation.Key); if (existingEvaluation == null) { existingProperty.Parcel.Evaluations.Add(evaluation); } else { context.Entry(existingEvaluation).CurrentValues.SetValues(evaluation); } } existingProperty.Parcel.RemoveEvaluationsWithinOneYear(property.Parcel, originalMetadata.DisposedOn); context.Entry(existingProperty.Parcel).Collection(p => p.Fiscals).Load(); foreach (var fiscal in property.Parcel.Fiscals) { var existingFiscal = existingProperty.Parcel.Fiscals .FirstOrDefault(e => e.FiscalYear == fiscal.FiscalYear && e.Key == fiscal.Key); if (existingFiscal == null) { existingProperty.Parcel.Fiscals.Add(fiscal); } else { context.Entry(existingFiscal).CurrentValues.SetValues(fiscal); } } } } else if (property.PropertyType == Entity.PropertyTypes.Building) { // Only allow editing the classification and evaluations/fiscals for now context.Entry(existingProperty.Building).Collection(p => p.Evaluations).Load(); existingProperty.UpdateProjectNumbers(updatedProject.ProjectNumber); if (property.Building != null) { existingProperty.Building.ClassificationId = property.Building.ClassificationId; foreach (var evaluation in property.Building.Evaluations) { var existingEvaluation = existingProperty.Building.Evaluations .FirstOrDefault(e => e.Date == evaluation.Date && e.Key == evaluation.Key); if (existingEvaluation == null) { existingProperty.Building.Evaluations.Add(evaluation); } else { context.Entry(existingEvaluation).CurrentValues.SetValues(evaluation); } } existingProperty.Building.RemoveEvaluationsWithinOneYear(property.Building, originalMetadata.DisposedOn); context.Entry(existingProperty.Building).Collection(p => p.Fiscals).Load(); foreach (var fiscal in property.Building.Fiscals) { var existingFiscal = existingProperty.Building.Fiscals .FirstOrDefault(e => e.FiscalYear == fiscal.FiscalYear && e.Key == fiscal.Key); if (existingFiscal == null) { existingProperty.Building.Fiscals.Add(fiscal); } else { context.Entry(existingFiscal).CurrentValues.SetValues(fiscal); } } } } } } // Remove any properties from this project that are no longer associated. var removePropertyIds = originalProject.Properties.Where(p => p.Id != 0).Select(p => p.Id).Except(updatedProject.Properties.Where(p => p.Id != 0).Select(p => p.Id)); var removeProperties = originalProject.Properties.Where(p => removePropertyIds.Contains(p.Id)); var removeParcelIds = removeProperties.Where(p => p.ParcelId.HasValue).Select(p => p.ParcelId.Value).ToArray(); var removeParcels = context.Parcels.Where(p => removeParcelIds.Contains(p.Id)); removeParcels.ForEach(p => { p.RemoveProjectNumber(updatedProject.ProjectNumber); context.Parcels.Update(p); }); var removeBuildingIds = removeProperties.Where(b => b.BuildingId.HasValue).Select(p => p.BuildingId.Value).ToArray(); var removeBuildings = context.Buildings.Where(p => removeBuildingIds.Contains(p.Id)); removeBuildings.ForEach(b => { b.RemoveProjectNumber(updatedProject.ProjectNumber); context.Buildings.Update(b); }); originalProject.Properties.RemoveAll(p => removePropertyIds.Contains(p.Id)); // Update tasks foreach (var task in updatedProject.Tasks) { var originalProjectTask = originalProject.Tasks.FirstOrDefault(t => t.TaskId == task.TaskId); if (originalProjectTask == null) { originalProject.Tasks.Add(task); } else { context.Entry(originalProjectTask).CurrentValues.SetValues(task); } } // Update responses foreach (var response in updatedProject.Responses) { var originalProjectResponse = originalProject.Responses.FirstOrDefault(r => r.AgencyId == response.AgencyId); if (originalProjectResponse == null) { originalProject.Responses.Add(response); } else { context.Entry(originalProjectResponse).CurrentValues.SetValues(response); } } // Update notes foreach (var note in updatedProject.Notes) { var originalNote = originalProject.Notes.FirstOrDefault(r => r.Id == note.Id && note.Id > 0); if (originalNote == null) { originalProject.Notes.Add(note); } else { context.Entry(originalNote).CurrentValues.SetValues(note); } } var toStatus = context.ProjectStatus .Include(s => s.Tasks) .FirstOrDefault(s => s.Id == updatedProject.StatusId); updatedProject.Status = toStatus; // If the tasks haven't been specified, generate them. var taskIds = updatedProject.Tasks.Select(t => t.TaskId).ToArray(); // Add the tasks for project status if they are not already added. foreach (var task in toStatus.Tasks.Where(t => !taskIds.Contains(t.Id))) { originalProject.Tasks.Add(new Entity.ProjectTask(updatedProject, task)); } }