Esempio n. 1
0
        /// <summary>
        /// Update building financials
        /// </summary>
        /// <param name="context"></param>
        /// <param name="building"></param>
        /// <param name="buildingEvaluations"></param>
        /// <param name="buildingFiscals"></param>
        public static void UpdateBuildingFinancials(this PimsContext context, Entity.Building building,
                                                    ICollection <Entity.BuildingEvaluation> buildingEvaluations, ICollection <Entity.BuildingFiscal> buildingFiscals)
        {
            foreach (var buildingEvaluation in buildingEvaluations)
            {
                var existingEvaluation = building.Evaluations
                                         .FirstOrDefault(e => e.Date == buildingEvaluation.Date && e.Key == buildingEvaluation.Key);
                var updateEvaluation = existingEvaluation?.Value != buildingEvaluation.Value;

                if (existingEvaluation == null)
                {
                    building.Evaluations.Add(buildingEvaluation);
                }
                else if (updateEvaluation)
                {
                    context.Entry(existingEvaluation).CurrentValues.SetValues(buildingEvaluation);
                }
            }
            foreach (var buildingFiscal in buildingFiscals)
            {
                var originalBuildingFiscal = building.Fiscals
                                             .FirstOrDefault(e => e.FiscalYear == buildingFiscal.FiscalYear && e.Key == buildingFiscal.Key);

                var updateFiscal = originalBuildingFiscal?.Value != buildingFiscal.Value || originalBuildingFiscal?.EffectiveDate != buildingFiscal.EffectiveDate;
                if (originalBuildingFiscal == null)
                {
                    building.Fiscals.Add(buildingFiscal);
                }
                else if (updateFiscal)
                {
                    context.Entry(originalBuildingFiscal).CurrentValues.SetValues(buildingFiscal);
                }
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Update a single child navigation property on a parent entity specified by T and parentId.
        /// Expects to be passed a complete list of child entities for the targeted navigation property.
        /// This method will update the database such that the navigation property for the parent contains the exact list of children passed to this method.
        /// </summary>
        /// <typeparam name="T">The parent entity type</typeparam>
        /// <typeparam name="I">The type of the id property</typeparam>
        /// <typeparam name="C">The type of the child navigation property being targeted for updates.</typeparam>
        /// <param name="context"></param>
        /// <param name="childNavigation"></param>
        /// <param name="parentId"></param>
        /// <param name="children"></param>
        public static void UpdateChild <T, I, C>(this PimsContext context, Expression <Func <T, object> > childNavigation, I parentId, C[] children) where T : IdentityBaseAppEntity <I> where C : IdentityBaseAppEntity <I>
        {
            var dbEntity = context.Find <T>(parentId);

            var dbEntry = context.Entry(dbEntity);

            var propertyName = childNavigation.GetPropertyAccess().Name;
            var dbItemsEntry = dbEntry.Collection(propertyName);
            var accessor     = dbItemsEntry.Metadata.GetCollectionAccessor();

            dbItemsEntry.Load();
            var dbItemsMap = dbItemsEntry.CurrentValue.Cast <IdentityBaseAppEntity <I> >()
                             .ToDictionary(e => e.Id);

            foreach (var item in children)
            {
                if (!dbItemsMap.TryGetValue(item.Id, out var oldItem))
                {
                    accessor.Add(dbEntity, item, false);
                }
                else
                {
                    context.Entry(oldItem).CurrentValues.SetValues(item);
                    dbItemsMap.Remove(item.Id);
                }
            }

            foreach (var oldItem in dbItemsMap.Values)
            {
                accessor.Remove(dbEntity, oldItem);
                context.Remove(oldItem);
            }
        }
Esempio n. 3
0
        /// <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 metadata = originalProject.Metadata;

            context.Entry(originalProject).CurrentValues.SetValues(updatedProject);
            originalProject.Agency   = agency; // TODO: this should not be necessary.
            originalProject.Metadata = metadata;
            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);
                            if (existingParcel.ProjectNumber != null)
                            {
                                throw new InvalidOperationException("Parcels in a Project cannot be added to another Project.");
                            }
                            existingParcel.ProjectNumber = 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);
                            if (existingBuilding.ProjectNumber != null)
                            {
                                throw new InvalidOperationException("Buildings in a Project cannot be added to another Project.");
                            }
                            existingBuilding.ProjectNumber = updatedProject.ProjectNumber;
                        }
                        originalProject.AddProperty(eproperty);
                    }
                }
                else
                {
                    if (property.PropertyType == Entity.PropertyTypes.Land)
                    {
                        // Only allow editing the classification and evaluations/fiscals for now
                        existingProperty.Parcel.ProjectNumber = 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, originalProject.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.Building.ProjectNumber = 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, originalProject.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.ProjectNumber = null;
                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.ProjectNumber = null;
                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));
            }

            // Update project financials if the project is still active.
            if (!updatedProject.IsProjectClosed())
            {
                originalProject.UpdateProjectFinancials();
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Update a single grandchild navigation property on a parent entity specified by T and parentId.
        /// Expects to be passed a complete list of child and grandchild entities for the targeted navigation property.
        /// This method will update the database such that the navigation property for the parent contains the exact list of children and grandchildren passed to this method.
        /// </summary>
        /// <typeparam name="T">The parent entity type</typeparam>
        /// <typeparam name="I">The type of the id property</typeparam>
        /// <typeparam name="C">The type of the child navigation property being targeted for updates.</typeparam>
        /// <param name="context"></param>
        /// <param name="childNavigation"></param>
        /// <param name="grandchildNavigation"></param>
        /// <param name="parentId"></param>
        /// <param name="childrenWithGrandchildren">The collection of children (and grandchildren) to update. Assumes grandchildren can be accessed via grandchildNavigation</param>
        /// <param name="canDeleteGrandchild">A callback to determine whether is safe to remove a grandchild entity</param>
        public static void UpdateGrandchild <T, I, C>(
            this PimsContext context,
            Expression <Func <T, object> > childNavigation,
            Expression <Func <C, object> > grandchildNavigation,
            I parentId,
            C[] childrenWithGrandchildren,
            Func <PimsContext, C, bool> canDeleteGrandchild) where T : IdentityBaseAppEntity <I> where C : IdentityBaseAppEntity <I>
        {
            var dbEntity = context.Find <T>(parentId);
            var dbEntry  = context.Entry(dbEntity);

            var childPropertyName = childNavigation.GetPropertyAccess().Name;
            var childCollection   = dbEntry.Collection(childPropertyName);
            var childAccessor     = childCollection.Metadata.GetCollectionAccessor();

            childCollection.Load();
            var existingChildren = childCollection.CurrentValue.Cast <IdentityBaseAppEntity <I> >().ToDictionary(e => e.Id);

            // Compile the grandchildNavigation lambda expression so we can extract the value from the passed in array of children
            var grandchildPropertyName = grandchildNavigation.GetPropertyAccess().Name;
            var grandchildFunc         = grandchildNavigation.Compile();

            foreach (var child in childrenWithGrandchildren)
            {
                if (!existingChildren.TryGetValue(child.Id, out var existingChild))
                {
                    childAccessor.Add(dbEntity, child, false);
                }
                else
                {
                    var dbChildEntry = context.Entry(existingChild);
                    dbChildEntry.CurrentValues.SetValues(child);

                    // load grandchild navigation property
                    var grandchildReference = dbChildEntry.Reference(grandchildPropertyName);
                    grandchildReference.Load();

                    // Update grandchild navigation with values passed in the array
                    var grandchildValue = grandchildFunc(child);
                    grandchildReference.TargetEntry.CurrentValues.SetValues(grandchildValue);

                    existingChildren.Remove(child.Id);
                }
            }

            foreach (var existingChild in existingChildren.Values)
            {
                var dbChildEntry = context.Entry(existingChild);

                childAccessor.Remove(dbEntity, existingChild);
                context.Remove(existingChild);

                // Also remove the grandchild referenced by the child being removed
                if (canDeleteGrandchild(context, existingChild as C))
                {
                    // load grandchild navigation property
                    var grandchildReference = dbChildEntry.Reference(grandchildPropertyName);
                    grandchildReference.Load();

                    var dbGrandchild = grandchildReference.TargetEntry?.Entity;
                    if (dbGrandchild != null)
                    {
                        context.Remove(dbGrandchild);
                    }
                }
            }
        }