合并多个DbQuery结果的最佳方法是什么?

问题描述 投票:0回答:1

我正在创建一个巨大的网格,该网格显示数据库中4个表中的所有不同记录类型。我创建了一个视图模型来保存每种记录类型的每个字段,然后进行查询以获取每个记录类型,并为每个记录创建一个viewmodel对象。现在,我想将所有这些组合成一个很大的结果,然后返回到“视图”,以便可以在网格中显示所有内容。这是我获取所有日志类型的功能:

    public ActionResult GetAllLogs([DataSourceRequest]DataSourceRequest request)
    {
        //var result;
        var allAssetConfigLogs = DbContext.AssetConfigurations.Select(log => new Models.AllLogsViewModel()
        {
            ID = log.Asset_SK.Value,
            AssetConfigurationID = log.AssetConfiguration_SK,
            AssetID = log.Asset_SK.Value,
            AssetName = log.Asset.Name,
            RedConID = log.ReadinessCondition_SK,
            RedCon = log.ReadinessCondition.Name,
            EKVType = log.EKV_Type_SK,
            IsSSF = log.IsSSF,
            IsArmedPlug = log.IsArmedPlug,
            IsConnectedCommunications = log.IsConnectedCommunications,
            IsFutureCapable = log.IsFutureCapable,
            IsFutureCapableLongTerm = log.IsFutureCapableLongTerm,
            IsActive = log.IsActive,
            IsExternal = log.IsExternal,
            IsIsolationComponent = log.IsIsolationComponent,
            IsIsolationSerial = log.IsIsolationSerial,
            IsInMaintenanceMode = log.IsInMaintenanceMode,
            IsEWCS = log.IsEWCS,
            IsMBIT = log.IsMBIT,
            IsIcoBsc = log.IsIcoBsc,
            IsLocal = log.IsLocal,
            IsStop = log.IsStop,
            IsSurge = log.IsSurge,
            ACNotes = log.Notes,
            SoftwareVersion = log.SoftwareVersion,
            IsIsolationSwitchPort = log.IsIsolationSwitchPort,
            IsPortManagedDown = log.IsPortManagedDown,
            IsOsfLogical = log.IsOsfLogical,
            ShipName = log.ShipName,
            JUNumber = log.JU_Number,
            GMACKey = log.GMAC_Key,
            URN = log.URN,
            TimeStamp = log.TimeStamp,
        });

        var allMissionLogs = DbContext.MissionConfigurations.Select(log => new Models.AllLogsViewModel()
        {
            MissionConfiguration_SK = log.MissionConfiguration_SK,
            Asset_SK = log.Asset_SK,
            EventType_SK = log.EventType_SK,
            Environment_SK = log.Environment_SK,
            RedConID = log.ReadinessCondition_SK,
            RecallTime = log.RecallTime,
            Notes = log.Notes
        });

        var allPACLogs = DbContext.PAC_Logs.Select(log => new Models.AllLogsViewModel()
        {
            UserRole = String.Join(",", log.User.Roles),
            PacLogID = log.PAC_Log_SK,
            SiteID = log.Site_SK,
            PacLogStatusID = log.PAC_LogStatus_SK,
            Status = log.PAC_LogStatus.Description,
            StatusDate = log.StatusDate,
            ActivityDescription = log.ActivityDescription,
            ActivityDetailDescription = log.ActivityDetailDescription,
            TrackingNumber = log.TrackingNumber,
            QuickTrack = log.QuickTrack,
            WAM_Number = log.WAM_Number,
            WAM_Revision = log.WAM_Revision,
            RequestedBy = log.RequestedBy,
            RequestedByName = log.User1 != null ? log.User1.FirstName + " " + log.User1.LastName : "",
            Controller = log.Controller,
            ControllerName = log.User2 != null ? log.User2.FirstName + " " + log.User2.LastName : "",
            AuthorizationNumber = log.AuthorizationNumber,
            IsSafe = log.IsSafe,
            SafeComments = log.SafeComments,
            PlanStartZulu = log.PlanStartZulu,
            PlanFinishZulu = log.PlanFinishZulu,
            ActualStartZulu = log.ActualStartZulu,
            ActualFinishZulu = log.ActualFinishZulu,
            PlanStartLocal = log.PlanStartLocal,
            PlanFinishLocal = log.PlanFinishLocal,
            ActualStartLocal = log.ActualStartLocal,
            ActualFinishLocal = log.ActualFinishLocal,
            IsGovDirected = log.IsGovernmentDirected,
            POC = log.POC,
            POCPhone = log.POC_Phone,
            WorkOrder = log.WorkOrder,
            ApprovalAuthority = log.ApprovalAuthority,
            AuthorityDocument = log.AuthorityDocument,
            FinalVerification = log.FinalVerification,
            WindowActivity = log.WindowActivities,
            APAC = log.APAC,
            WAMStartZulu = log.WAMStartZulu,
            WAMFinishZulu = log.WAMFinishZulu,
            AssetAvailable = log.AssetAvailable,
            AssetUnavailable = log.AssetUnavailable,
            AssetStatus = log.MaintenanceStatus_SK,
            AssetStatusString = log.MaintenanceStatu.Name,
            ID_MaintenanceActivity = log.MaintenanceActivity_SK,
            ID_MaintenanceActivityString = log.MaintenanceActivity.Name,
            FourtyMinuteNotification = log.C40MinNotification,
            Comments = log.Comments,
            QAComments = log.QA_Comments,
            QACheck = log.QA_Check,
            Unscheduled = log.Unscheduled,
            SiteName = log.Site.FullName,
            Environment1 = log.Environment1,
            Environment2 = log.Environment2,
            Environment3 = log.Environment3,
            TimeZoneID = log.TimeZone_SK,
            TimeZone = log.TimeZone.Name,
            TimeZoneOffset = log.TimeZone_SK != null ? log.TimeZone.Offset.Value : 0,
            AssetCheck = log.AssetLogPAC_Logs.Count > 0 ? "yes" : "",
            EnvironmentCheck = log.Environment1 || log.Environment2 || log.Environment3 ? "yes" : ""
        });

        var allPRs = DbContext.ProblemReports.Select(log => new Models.AllLogsViewModel()
        {
            ProblemReportID = log.ProblemReport_SK,
            ProblemReportOwnerID = log.ProblemReportOwner_SK,
            EventID = log.Event_SK,
            EventString = log.Event.Name,
            SubAssembly = log.Subassembly_SK,
            SubAssemblyString = log.Subassembly.Name,
            ActualFailureTime = log.ActualFailureTime,
            ActualRestoreTime = log.ActualRestoreTime,
            CurrentDateTimeGroup = log.CurrentDateTimeGroup,
            FaultEventDescription = log.FaultEventDescription,
            FaultEventDetails = log.FaultEventDetails,
            CurrentDayActivities = log.CurrentDayActivities,
            PathForward = log.PathForward,
            EstimatedArrivalDate = log.EstimatedArrivalDate,
            WorkOrderNumber = log.WorkOrderNumber,
            NCR_Number = log.NCR_Number,
            MalfunctionNotes = log.MalfunctionNotes,
            RepairStatus = log.RepairStatus,
            NextUpdateDue = log.NextUpdateDue,
            OriginatedBy = log.OriginatedBy,
            OriginatedByString = log.User.FirstName + " " + log.User.LastName,
            ReleasedBy = log.ReleasedBy,
            ReleasedByString = log.ReleasedBy != null ? log.User1.FirstName + " " + log.User1.LastName : "",
            ClosedBy = log.ClosedBy,
            ClosedByString = log.ClosedBy != null ? log.User2.FirstName + " " + log.User2.LastName : "",
            IsClosed = log.IsClosed,
            IsNotesVisible = log.IsNotesVisible,
            TimeStamp = log.TimeStamp,
            RedConID = log.ReadinessCondition_SK,
            RedCon = log.ReadinessCondition.Name,
            AssetType = log.Asset.AssetType.Name,
            AssetSite = log.Asset.Site.Name,
            Rev = log.ProblemReportDetails.OrderByDescending(x => x.CreatedBy).FirstOrDefault().Revision.Name,
            ETRO = log.ProblemReportDetails.OrderByDescending(x => x.CreatedBy).FirstOrDefault().ETRO.ToString(),
            ETROChangeReason = log.ProblemReportDetails.OrderByDescending(x => x.CreatedBy).FirstOrDefault().ETRO_ChangeReasons.Name,
            NoteArchive = log.NotesArchive,
            currentDetail = log.ProblemReportDetails.OrderByDescending(y => y.CreatedOn).FirstOrDefault(),
            RelatedAssetEnv = DbContext.MissionConfigurations.Where(y => y.Asset_SK == log.Asset.Asset_SK).OrderByDescending(y => y.TimeStamp).Select(y => y.Environment.EnvironmentLogs.OrderByDescending(z => z.TimeStamp).FirstOrDefault().Designation).FirstOrDefault()
        });

        //return Json(result.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
    }

我的巨型ViewModel(将来自不同日志类型的不同字段组合成一个大模型:

`//This is a model of all log types (relating to assets) consolidated into one.
public class AllLogsViewModel
{
    //This region holds fields that are shared amongst multiple log types.
    #region SharedFields
    public int ID { get; set; }
    public string RedCon { get; set; }
    public int RedConID { get; set; }
    public DateTime TimeStamp { get; set; }
    public string TimeStampString
    {
        get { return TimeStamp.ToString(); }
    }
    public string TimeStampStringZulu
    {
        get { return TimeStamp.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public string CreatedBy { get; set; }
    public DateTime CreatedOn { get; set; }
    public string CreatedOnString
    {
        get { return CreatedOn.ToString(); }
    }
    public string CreatedOnStringZulu
    {
        get { return CreatedOn.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public string UpdatedBy { get; set; }
    public DateTime UpdatedOn { get; set; }
    public string UpdatedOnString
    {
        get { return UpdatedOn.ToString(); }
    }
    public string UpdatedOnStringZulu
    {
        get { return UpdatedOn.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    #endregion

    #region AssetConfigLogs
    public int AssetConfigurationID { get; set; }
    public int AssetID { get; set; }
    public string AssetName { get; set; }
    public int EKVType { get; set; }
    //Labeled as "OSF Physical" for some reason?
    public bool IsSSF { get; set; }
    public bool IsArmedPlug { get; set; }
    public bool IsArmedSDS { get; set; }
    public bool IsConnectedCommunications { get; set; }
    public bool IsFutureCapable { get; set; }
    public bool IsFutureCapableLongTerm { get; set; }
    public bool IsActive { get; set; }
    public bool IsExternal { get; set; }
    public bool IsIsolationComponent { get; set; }
    public bool IsIsolationSerial { get; set; }
    public bool IsInMaintenanceMode { get; set; }
    public bool IsEWCS { get; set; }
    public bool IsMBIT { get; set; }
    public bool IsIcoBsc { get; set; }
    public bool IsLocal { get; set; }
    public bool IsStop { get; set; }
    public bool IsSurge { get; set; }
    public string ACNotes { get; set; }
    public string SoftwareVersion { get; set; }
    public bool IsIsolationSwitchPort { get; set; }
    public bool IsPortManagedDown { get; set; }
    public bool IsOsfLogical { get; set; }
    public string ShipName { get; set; }
    public string JUNumber { get; set; }
    public string GMACKey { get; set; }
    public string URN { get; set; }
    //form logic controls?
    public bool GMMVisibility { get; set; }
    public bool CNEVisibility { get; set; }
    public bool SiloVisibility { get; set; }
    public bool IDTVisibility { get; set; }
    public bool AEGISVisibility { get; set; }
    public bool GMACVisibility { get; set; }
    public bool URNVisibility { get; set; }
    public bool ANTPY2Visibility { get; set; }
    public bool GBIVisibility { get; set; }
    public bool OSFVisibility { get; set; }
    public bool PortIsolationVisibility { get; set; }
    public int AssignedGBIID { get; set; }
    public string AssignedGBIName { get; set; }
    public int AssignedCNEID { get; set; }
    public string AssignedCNEName { get; set; }
    #endregion

    #region MissionLogs
    public int MissionConfiguration_SK { get; set; }
    public int Asset_SK { get; set; }
    public int EventType_SK { get; set; }
    public int Environment_SK { get; set; }
    public string Designation 
    {
        get
        {
            var designation = string.Empty;

            switch (EventType_SK)
            {
                case 5: designation = "COMM";
                    break;
                case 8: designation = "OPS";
                    break;
                case 10: designation = "TEST";
                    break;
            }
            return designation;
        }
    } //For EventType_SK
    public int RecallTime { get; set; }
    public string Notes { get; set; }
    #endregion

    #region PacLogs
    public string UserRole { get; set; }
    public string SubmitType { get; set; }
    public int PacLogID { get; set; }
    public int SiteID { get; set; }
    public int? PacLogStatusID { get; set; }
    public string Status { get; set; }
    public DateTime StatusDate { get; set; }
    public string StatusDateString
    {
        get { return StatusDate.ToString(); }
    }
    [DisplayName("Status Date")]
    public string StatusDateStringZulu
    {
        get { return StatusDate.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public string ActivityDescription { get; set; }
    public string ActivityDetailDescription { get; set; }
    public string TrackingNumber { get; set; }
    public string QuickTrack { get; set; }
    public string WAM_Number { get; set; }
    public string WAM_Revision { get; set; }
    public int? RequestedBy { get; set; }
    public string RequestedByName { get; set; }
    public int? Controller { get; set; }
    public string ControllerName { get; set; }
    public string AuthorizationNumber { get; set; }
    public bool IsSafe { get; set; }
    public string SafeComments { get; set; }
    public DateTime? PlanStartZulu { get; set; }
    [DisplayName("Planned Start")]
    public string PlanStartZuluString
    {
        get { return PlanStartZulu?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    [Required(ErrorMessage = "Planned Close is Required")]
    // changed to "Planned Close"
    public DateTime? PlanFinishZulu { get; set; }
    [DisplayName("Planned Close")]
    public string PlanFinishZuluString
    {
        get { return PlanFinishZulu?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? ActualStartZulu { get; set; }
    [DisplayName("Actual Start")]
    public string ActualStartZuluString
    {
        get { return ActualStartZulu?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? ActualFinishZulu { get; set; }
    [DisplayName("Actual Close")]
    public string ActualFinishZuluString
    {
        get { return ActualFinishZulu?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? PlanStartLocal { get; set; }
    [DisplayName("Planned Start(Local)")]
    public string PlanStartLocalString
    {
        get { return PlanStartLocal?.ToString(); }
    }
    public DateTime? PlanFinishLocal { get; set; }
    [DisplayName("Planned Close(Local)")]
    public string PlanFinishLocalString
    {
        get { return PlanFinishLocal?.ToString(); }
    }
    public DateTime? ActualStartLocal { get; set; }
    [DisplayName("Actual Start(Local)")]
    public string ActualStartLocalString
    {
        get { return ActualStartLocal?.ToString(); }
    }
    public DateTime? ActualFinishLocal { get; set; }
    [DisplayName("Actual Close(Local)")]
    public string ActualFinishLocalString
    {
        get { return ActualFinishLocal?.ToString(); }
    }
    public bool IsGovDirected { get; set; }
    public string POC { get; set; }
    public string POCPhone { get; set; }
    public string WorkOrder { get; set; }
    public string ApprovalAuthority { get; set; }
    public string AuthorityDocument { get; set; }
    public int? FinalVerification { get; set; }
    public string FinalVerificationString { get; set; }
    public bool WindowActivity { get; set; }
    public bool APAC { get; set; }
    public DateTime? WAMStartZulu { get; set; }
    [DisplayName("WAM Start")]
    public string WAMStartZuluString
    {
        get { return WAMStartZulu?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? WAMFinishZulu { get; set; }
    [DisplayName("WAM Finish")]
    public string WAMFinishZuluString
    {
        get { return WAMFinishZulu?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? AssetAvailable { get; set; }
    public string AssetAvailableString
    {
        get { return AssetAvailable?.ToString(); }
    }
    [DisplayName("Asset Available")]
    public string AssetAvailableStringZulu
    {
        get { return AssetAvailable?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? AssetUnavailable { get; set; }
    public string AssetUnavailableString
    {
        get { return AssetUnavailable?.ToString(); }
    }
    [DisplayName("Asset Unavailable")]
    public string AssetUnavailableStringZulu
    {
        get { return AssetUnavailable?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public int? AssetStatus { get; set; }
    public string AssetStatusString { get; set; }
    // renamed to Activity Category
    public int? ID_MaintenanceActivity { get; set; }
    // renamed to Activity Category
    public string ID_MaintenanceActivityString { get; set; }
    public string FourtyMinuteNotification { get; set; }
    public string Comments { get; set; }
    public string QAComments { get; set; }
    public bool QACheck { get; set; }
    public bool Unscheduled { get; set; }
    public string TemplateName { get; set; }
    public string SiteName { get; set; }
    public bool Environment1 { get; set; }
    public bool Environment2 { get; set; }
    public bool Environment3 { get; set; }
    public string Environments
    {
        get
        {
            var envs = "";
            if (Environment1)
            {
                envs += "1";
                if (Environment2 || Environment3)
                {
                    envs += ", ";
                }
            }
            if (Environment2)
            {
                envs += "2";
                if (Environment3)
                {
                    envs += ", ";
                }
            }
            if (Environment3)
            {
                envs += "3";
            }

            return envs;
        }
    }
    public int? TimeZoneID { get; set; }
    public string TimeZone { get; set; }
    public int TimeZoneOffset { get; set; }
    public string AssetCheck { get; set; }
    public string EnvironmentCheck { get; set; }
    #endregion

    #region ProblemReports
    public int ProblemReportID { get; set; }
    public string ProblemReportIDString { get; set; }
    public int? ProblemReportOwnerID { get; set; }
    public int EventID { get; set; }
    public string EventString { get; set; }
    public int? SubAssembly { get; set; }
    public string SubAssemblyString { get; set; }
    public DateTime? ActualFailureTime { get; set; }
    public string ActualFailureTimeString
    {
        get { return ActualFailureTime?.ToString(); }
    }
    [DisplayName("Actual Failure Time")]
    public string ActualFailureTimeStringZulu
    {
        get { return ActualFailureTime?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? ActualRestoreTime { get; set; }
    public string ActualRestoreTimeString
    {
        get { return ActualRestoreTime?.ToString(); }
    }
    [DisplayName("Actual Restore Time")]
    public string ActualRestoreTimeStringZulu 
    {
        get { return ActualRestoreTime?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public DateTime? CurrentDateTimeGroup { get; set; }
    public string CurrentDateTimeGroupString
    {
        get { return CurrentDateTimeGroup?.ToString(); }
    }
    [DisplayName("Current DateTime Group")]
    public string CurrentDateTimeGroupStringZulu
    {
        get { return CurrentDateTimeGroup?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public string FaultEventDescription { get; set; }
    public string FaultEventDetails { get; set; }
    public string CurrentDayActivities { get; set; }
    public string PathForward { get; set; }
    public string PartNumber { get; set; }
    public DateTime? EstimatedArrivalDate { get; set; }
    public string EstimatedArrivalDateString
    {
        get { return EstimatedArrivalDate?.ToString(); }
    }
    [DisplayName("Estimated Arrival Date")]
    public string EstimatedArrivalDateStringZulu
    {
        get { return EstimatedArrivalDate?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public string WorkOrderNumber { get; set; }
    public string NCR_Number { get; set; }
    public string MalfunctionNotes { get; set; }
    public string RepairStatus { get; set; }
    public DateTime? NextUpdateDue { get; set; }
    public string NextUpdateDueString
    {
        get { return NextUpdateDue?.ToString(); }
    }
    [DisplayName("Next Update Due")]
    public string NextUpdateDueStringZulu
    {
        get { return NextUpdateDue?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public int OriginatedBy { get; set; }
    public string OriginatedByString { get; set; }
    public int? ReleasedBy { get; set; }
    public string ReleasedByString { get; set; }
    public int? ClosedBy { get; set; }
    public string ClosedByString { get; set; }
    public bool IsClosed { get; set; }
    public bool IsNotesVisible { get; set; }
    public string TimeStampZuluString
    {
        get { return TimeStamp.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    [DisplayName("TimeStamp")]
    public string TimeStampZuluStringZulu
    {
        get { return TimeStamp.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }

    public string AssetType { get; set; }
    public string AssetSite { get; set; }
    public string Rev { get; set; }
    public string ETRO { get; set; }
    [DisplayName("ETRO")]
    public string ETROzulu
    {
        get
        {
            if (DateTime.TryParse(ETRO, out DateTime temp))
            {
                return DateTime.Parse(ETRO).ToString("MM'/'dd'/'yyyy' 'HHmm'z'");
            }
            else
            {
                return ETRO;
            }
        }
    }
    public string ETROChangeReason { get; set; }
    public string NoteArchive { get; set; }

    public DAL.ProblemReportDetail currentDetail { get; set; }
    public Models.ProblemReportDetails currentDetailModel { get; set; }
    [DisplayName("Owner")]
    public string ProblemReportOwnerString { get; set; }
    public List<Models.ProblemReportDetails> RelatedPRDetails { get; set; }
    public string RelatedAssetEnv { get; set; }
    #endregion

    #region ProblemReportDetails
    public int ProblemReportDetailID { get; set; }
    public int RevisionID { get; set; }
    public bool HasParent { get; set; }
    public DateTime? EventDate { get; set; }
    public string EventDateString
    {
        get { return EventDate?.ToString(); }
    }
    public string EventDateStringZulu
    {
        get { return EventDate?.ToString("MM'/'dd'/'yyyy' 'HHmm'z'"); }
    }
    public int ProblemReportTypeID { get; set; }
    public string ProblemReportType { get; set; }
    public int? MaintenancePriorityID { get; set; }
    public string MaintenancePriority { get; set; }
    public int MaintenanceStatusID { get; set; }
    public string MaintenanceStatus { get; set; }
    public int? ParentMaintenanceStatus { get; set; }
    public string ParentMaintenanceStatusString { get; set; }
    public int DetailRedConID { get; set; }
    public string DetailRedConString { get; set; }
    public string ETRO_String { get; set; }
    public int? ETROChangeReasonID { get; set; }
    public string ETROChangeReasonString { get; set; }
    #endregion
}

}`

我正在寻找实现此目的的最佳方法...是否可以使用Union组合它们并返回它们?我知道我可以将它们全部转换为带有ToList()的列表,然后以这种方式将它们组合起来。我当然在寻找速度,因为这将需要大量数据。我认为ToList()解决方案会使速度变慢。

c# asp.net-mvc linq kendo-asp.net-mvc
1个回答
0
投票

但是如果数据不相关并且您只想合并这些集合,则应该可以对它们进行.Concat()。

@@ David是正确的,因为Concat可能是最简单的方法。您可以在进行转换后添加类似这样的内容

var allLogs = allAssetConfigLogs
  .Concat(allMissionLogs)
  .Concat(allPACLogs)
  .Concat(allPRs);

这假设您希望每个日志都没有特定的顺序。如果要使用不同的日志,可以使用Union(),但需要提供一些与之比较的日志。

作为旁注,我确实会建议像AutoMapper这样的东西来处理您的数据库实体到视图模型的转换。

© www.soinside.com 2019 - 2024. All rights reserved.