/// <summary>
        /// 기간에 해당되는 실적 파일 데이터를 셀렉트한다.
        /// </summary>
        public IEnumerable <DataFileSource> SelectDataFileSourceListBySourceDateRange(Guid formId, Guid formSectionId, DateTimeOffset beginDate, DateTimeOffset endDate)
        {
            string procCommandName = "up_DataFileSource_SelectBySourceDateRange";

            try
            {
                var command = Connection.GetStoredProcCommand(procCommandName);
                Connection.AddInParameter(command, "FormID", DbType.Guid, formId);
                Connection.AddInParameter(command, "FormSectionID", DbType.Guid, formSectionId);
                Connection.AddInParameter(command, "BeginDate", DbType.DateTimeOffset, beginDate);
                Connection.AddInParameter(command, "EndDate", DbType.DateTimeOffset, endDate);

                using (DataSet ds = Connection.ExecuteDataSet(command))
                {
                    ValidateTableCount(ds, 1);

                    List <DataFileSource> dataFileSourceList = null;
                    if (ds.Tables[0].Rows.Count > 0)
                    {
                        dataFileSourceList = new List <DataFileSource>();
                        foreach (DataRow dr in ds.Tables[0].Rows)
                        {
                            dataFileSourceList.Add(DataFileSource.ParseFrom(dr));
                        }
                    }

                    return(dataFileSourceList);
                }
            }
            catch (Exception ex)
            {
                throw new DataException($"프로시져 실행 중 예기치 못한 에러가 발생했습니다.\r\n 프로시저: \"{procCommandName}\"", ex);
            }
        }
        /// <summary>
        /// 실적 파일 데이터를 셀렉트한다.
        /// </summary>
        public DataFileSource SelectDataFileSource(Guid formId, Guid formSectionId, Guid fileSourceId)
        {
            string procCommandName = "up_DataFileSource_Select";

            try
            {
                var command = Connection.GetStoredProcCommand(procCommandName);
                Connection.AddInParameter(command, "FormID", DbType.Guid, formId);
                Connection.AddInParameter(command, "FormSectionID", DbType.Guid, formSectionId);
                Connection.AddInParameter(command, "FileSourceID", DbType.Guid, fileSourceId);

                using (DataSet ds = Connection.ExecuteDataSet(command))
                {
                    ValidateTableCount(ds, 1);

                    if (ds.Tables[0].Rows.Count > 0)
                    {
                        return(DataFileSource.ParseFrom(ds.Tables[0].Rows[0]));
                    }

                    return(null);
                }
            }
            catch (Exception ex)
            {
                throw new DataException($"프로시져 실행 중 예기치 못한 에러가 발생했습니다.\r\n 프로시저: \"{procCommandName}\"", ex);
            }
        }
        /// <summary>
        /// 대시보드 테이블 업무 영역을 반환한다.
        /// </summary>
        public FormTableSection SelectFormTableSection(Guid formId, Guid formSectionId)
        {
            string procCommandName = "up_FormTableSection_Select";

            try
            {
                var command = Connection.GetStoredProcCommand(procCommandName);
                Connection.AddInParameter(command, "FormID", DbType.Guid, formId);
                Connection.AddInParameter(command, "FormSectionID", DbType.Guid, formSectionId);

                using (DataSet ds = Connection.ExecuteDataSet(command))
                {
                    ValidateTableCount(ds, 4);

                    // 1. 업무 영역
                    if (ds.Tables[0].Rows.Count > 0)
                    {
                        var formSection = FormTableSection.ParseFrom(ds.Tables[0].Rows[0]);

                        // 2. 파일 템플릿 정보
                        if (ds.Tables[1].Rows.Count > 0)
                        {
                            formSection.FileTemplate = DataFileTemplate.ParseFrom(ds.Tables[1].Rows[0]);
                        }

                        // 3. 파일 소스 정보
                        if (ds.Tables[2].Rows.Count > 0)
                        {
                            formSection.FileSource = DataFileSource.ParseFrom(ds.Tables[2].Rows[0]);
                        }

                        // 4. 데이터 업로더 정보
                        foreach (DataRow dr in ds.Tables[3].Rows)
                        {
                            var uploader = DataFileSourceUploader.ParseFrom(dr);
                            formSection.FileSourceUploaders[uploader.UserId] = uploader;
                        }

                        return(formSection);
                    }

                    return(null);
                }
            }
            catch (Exception ex)
            {
                throw new DataException($"프로시져 실행 중 예기치 못한 에러가 발생했습니다.\r\n 프로시저: \"{procCommandName}\"", ex);
            }
        }
        /// <summary>
        /// 페이징 처리된 실적 파일 데이터 리스트를 반환한다.
        /// </summary>
        /// <param name="queryUserId">값이 있는 경우, 해당 유저가 접근 가능한 데이터만 반환</param>
        public PagedModel <DataFileSource> SelectDataFileSourcePagedList(DataFileSourceSearchOption option, string queryUserId = null)
        {
            string procCommandName = "up_DataFileSource_SelectPagedList";

            try
            {
                var command = Connection.GetStoredProcCommand(procCommandName);
                Connection.AddInParameter(command, "PageNumber", DbType.Int32, option.PageNumber);
                Connection.AddInParameter(command, "PageCount", DbType.Int32, option.PageCount);
                Connection.AddInParameter(command, "SortBy", DbType.String, option.SortBy);
                Connection.AddInParameter(command, "OrderBy", DbType.String, option.OrderBy.ToEnumMemberString());

                if (option.FormId.HasValue)
                {
                    Connection.AddInParameter(command, "FormID", DbType.Guid, option.FormId.Value);
                }

                Connection.AddInParameter(command, "SearchKeyword", DbType.String, option.SearchKeyword);
                Connection.AddInParameter(command, "QueryUserID", DbType.String, queryUserId);

                using (DataSet ds = Connection.ExecuteDataSet(command))
                {
                    ValidateTableCount(ds, 2);

                    var result = new PagedModel <DataFileSource>(option)
                    {
                        PagingOption = option
                    };
                    int totalCount = 0;

                    // 1. 유저 정보 테이블
                    var userInfoMap = new Dictionary <string, UserInfo>(StringComparer.OrdinalIgnoreCase);
                    foreach (DataRow dr in ds.Tables[1].Rows)
                    {
                        var userInfo = UserInfo.ParseFrom(dr);
                        userInfoMap[userInfo.UserId] = userInfo;
                    }

                    // 2. 데이터 파일 리스트
                    var dataFileSourceList = new List <DataFileSource>();
                    foreach (DataRow dr in ds.Tables[0].Rows)
                    {
                        var dataFile = DataFileSource.ParseFrom(dr, out totalCount);

                        UserInfo userInfo = null;
                        if (userInfoMap.TryGetValue(dataFile.CreatorId, out userInfo))
                        {
                            dataFile.CreatorInfo = userInfo;
                        }
                        else
                        {
                            dataFile.CreatorInfo = new UserInfo(dataFile.CreatorId);
                        }

                        dataFileSourceList.Add(dataFile);
                    }

                    result.TotalCount = totalCount;
                    result.Items      = dataFileSourceList;

                    return(result);
                }
            }
            catch (Exception ex)
            {
                throw new DataException($"프로시져 실행 중 예기치 못한 에러가 발생했습니다.\r\n 프로시저: \"{procCommandName}\"", ex);
            }
        }
        /// <summary>
        /// 페이징 처리된 대시보드 테이블 업무 영역을 반환한다.
        /// </summary>
        public PagedModel <FormTableSection> SelectFormTableSectionPagedList(PagingOption option)
        {
            string procCommandName = "up_FormTableSection_SelectPagedList";

            try
            {
                var command = Connection.GetStoredProcCommand(procCommandName);
                Connection.AddInParameter(command, "PageNumber", DbType.Int32, option.PageNumber);
                Connection.AddInParameter(command, "PageCount", DbType.Int32, option.PageCount);
                Connection.AddInParameter(command, "SortBy", DbType.String, option.SortBy);
                Connection.AddInParameter(command, "OrderBy", DbType.String, option.OrderBy.ToEnumMemberString());

                using (DataSet ds = Connection.ExecuteDataSet(command))
                {
                    ValidateTableCount(ds, 4);

                    var result = new PagedModel <FormTableSection>(option)
                    {
                        PagingOption = option
                    };
                    int totalCount = 0;

                    // 1. 업무 영역
                    foreach (DataRow dr in ds.Tables[0].Rows)
                    {
                        var formSection = FormTableSection.ParseFrom(dr, out totalCount);
                        result.Items.Add(formSection);
                    }

                    // 2. 파일 템플릿 정보
                    foreach (DataRow dr in ds.Tables[1].Rows)
                    {
                        var fileTemplate = DataFileTemplate.ParseFrom(dr);
                        var formSections = result.Items.FindAll(o => o.FileTemplateId == fileTemplate.FileTemplateId);
                        if (formSections.Any())
                        {
                            formSections.ForEach(o =>
                            {
                                o.FileTemplate = fileTemplate;
                            });
                        }
                    }

                    // 3. 파일 소스 정보
                    foreach (DataRow dr in ds.Tables[2].Rows)
                    {
                        var fileSource   = DataFileSource.ParseFrom(dr);
                        var formSections = result.Items.FindAll(o => o.FormId == fileSource.FormId && o.FormSectionId == fileSource.FormSectionId);
                        if (formSections.Any())
                        {
                            formSections.ForEach(o =>
                            {
                                o.FileSource = fileSource;
                            });
                        }
                    }

                    // 4. 데이터 업로더 정보
                    foreach (DataRow dr in ds.Tables[3].Rows)
                    {
                        var uploader     = DataFileSourceUploader.ParseFrom(dr);
                        var formSections = result.Items.FindAll(o => o.FormId == uploader.FormId && o.FormSectionId == uploader.FormSectionId);
                        if (formSections.Any())
                        {
                            formSections.ForEach(o =>
                            {
                                o.FileSourceUploaders[uploader.UserId] = uploader;
                            });
                        }
                    }

                    result.TotalCount = totalCount;

                    return(result);
                }
            }
            catch (Exception ex)
            {
                throw new DataException($"프로시져 실행 중 예기치 못한 에러가 발생했습니다.\r\n 프로시저: \"{procCommandName}\"", ex);
            }
        }
        /// <summary>
        /// 대시보드 테이블 업무 영역 리스트를 반환한다.
        /// </summary>
        /// <param name="queryUserId">값이 있는 경우, 해당 유저가 접근 가능한 데이터만 반환</param>
        /// <param name="fileSourceDateRanges">값이 있는 경우, 해당 날짜의 데이터 파일 소스 정보를 반환</param>
        public IEnumerable <FormTableSection> SelectFormTableSectionList(Guid?formId, string queryUserId = null, List <DataDateRange> fileSourceDateRanges = null)
        {
            string procCommandName = "up_FormTableSection_SelectList";

            try
            {
                var command = Connection.GetStoredProcCommand(procCommandName);

                if (formId.HasValue)
                {
                    Connection.AddInParameter(command, "FormID", DbType.Guid, formId.Value);
                }

                Connection.AddInParameter(command, "QueryUserID", DbType.String, queryUserId);

                if (fileSourceDateRanges != null)
                {
                    // 특정한 날짜 범위 내의 데이터 소스 값만 반환하기 위해 날짜 범위를 DB에 전달한다.
                    using (var spParamDateRanges = new DataTable()) // type_DataSourceDateRanges
                    {
                        spParamDateRanges.Columns.Add("UploadInterval", typeof(string));
                        spParamDateRanges.Columns.Add("BeginDate", typeof(DateTimeOffset));
                        spParamDateRanges.Columns.Add("EndDate", typeof(DateTimeOffset));
                        spParamDateRanges.Columns.Add("IsCurrentData", typeof(bool));

                        foreach (var dateRange in fileSourceDateRanges)
                        {
                            spParamDateRanges.Rows.Add(new object[]
                            {
                                dateRange.UploadInterval,
                                dateRange.BeginDate,
                                dateRange.EndDate,
                                dateRange.IsCurrentData
                            });
                        }

                        var param = command.Parameters.AddWithValue("SourceDateRanges", spParamDateRanges);
                        param.SqlDbType = SqlDbType.Structured;
                    }
                }

                using (DataSet ds = Connection.ExecuteDataSet(command))
                {
                    ValidateTableCount(ds, 4);

                    var result = new List <FormTableSection>();

                    // 1. 업무 영역
                    foreach (DataRow dr in ds.Tables[0].Rows)
                    {
                        var formSection = FormTableSection.ParseFrom(dr);
                        result.Add(formSection);
                    }

                    // 2. 파일 템플릿 정보
                    foreach (DataRow dr in ds.Tables[1].Rows)
                    {
                        var fileTemplate = DataFileTemplate.ParseFrom(dr);
                        var formSections = result.FindAll(o => o.FileTemplateId == fileTemplate.FileTemplateId);
                        if (formSections.Any())
                        {
                            formSections.ForEach(o =>
                            {
                                o.FileTemplate = fileTemplate;
                            });
                        }
                    }

                    // 3. 파일 소스 정보
                    foreach (DataRow dr in ds.Tables[2].Rows)
                    {
                        var  fileSource    = DataFileSource.ParseFrom(dr);
                        bool isCurrentData = dr.Get <bool>("IsCurrentData");

                        var formSections = result.FindAll(o => o.FormId == fileSource.FormId && o.FormSectionId == fileSource.FormSectionId);
                        if (formSections.Any())
                        {
                            formSections.ForEach(o =>
                            {
                                if (isCurrentData)
                                {
                                    o.FileSource = fileSource;
                                }
                                else
                                {
                                    o.PrevFileSource = fileSource;
                                }
                            });
                        }
                    }

                    // 4. 데이터 업로더 정보
                    foreach (DataRow dr in ds.Tables[3].Rows)
                    {
                        var uploader     = DataFileSourceUploader.ParseFrom(ds.Tables[3].Rows[0]);
                        var formSections = result.FindAll(o => o.FormId == uploader.FormId && o.FormSectionId == uploader.FormSectionId);
                        if (formSections.Any())
                        {
                            formSections.ForEach(o =>
                            {
                                o.FileSourceUploaders[uploader.UserId] = uploader;
                            });
                        }
                    }

                    return(result);
                }
            }
            catch (Exception ex)
            {
                throw new DataException($"프로시져 실행 중 예기치 못한 에러가 발생했습니다.\r\n 프로시저: \"{procCommandName}\"", ex);
            }
        }