using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Coralcode.Framework.Domains;
using Coralcode.Framework.Extensions;
using Coralcode.Framework.Log;
using Coralcode.Framework.Models;
using Coralcode.Framework.Mvc.Extensions;
using Coralcode.Framework.Services; namespace Coralcode.Framework.Mvc.ControlContent
{
public abstract class ImportExportCoralController<TModel, TSearch> : CoralController
where TModel : class, IViewModel, new()
where TSearch : SearchBase, new()
{ private readonly ICrudCoralService<TModel, TSearch> _service;
protected readonly ImportExportService<TModel, TSearch> ImportExportService; protected Action<TModel> CreateAction;
protected ImportExportCoralController(ICrudCoralService<TModel, TSearch> service)
{
_service = service;
ImportExportService = new ImportExportService<TModel, TSearch>(service);
if (CreateAction == null)
CreateAction = item => _service.Create(item);
} protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
var routeValues = Request.GetRouteValues();
ViewBag.ImportUrl = Url.Action("Import", routeValues);
ViewBag.ExportUrl = Url.Action("Export", routeValues);
ViewBag.ExportTemplateUrl = Url.Action("DownloadTemplate");
} protected virtual string FileName
{
get { return (typeof(TModel).GetDescription() ?? IdentityGenerator.NewGuidString()) + DateTime.Now.ToString("yyyyMMddHHmmss"); }
} /// <summary>
/// 下载模板
/// </summary>
/// <returns></returns>
public virtual ActionResult DownloadTemplate()
{ var stream = ImportExportService.ExportTemplate();
return File(stream.ToArray(), "application/zip-x-compressed", FileName); } /// <summary>
/// 导出查询结果集
/// </summary>
/// <returns></returns>
public virtual ActionResult Export(TSearch searchModel)
{
searchModel = searchModel ?? new TSearch();
var stream = ImportExportService.Export(_service.Search(searchModel));
return File(stream.ToArray(), "application/zip-x-compressed", FileName);
} public ActionResult Import()
{
return PartialView("Import");
} /// <summary>
/// 导入返回结果
/// </summary>
/// <param name="uploadFile"></param>
/// <returns></returns>
[HttpPost]
public virtual ActionResult Import(HttpPostedFileBase uploadFile)
{
if (uploadFile == null || uploadFile.ContentLength < 0)
return AjaxErrorResult(null, "请选择导入文件!"); List<ImportMessage<TModel>> results;
try
{
results = ImportExportService.Import(uploadFile.InputStream, ValidateAndPreProccess);
}
catch (Exception ex)
{
LoggerFactory.Instance.Error("导入产生错误;信息:{0}", ex.ToString());
return AjaxExceptionResult(ex);
}
if (results.All(item => item.State == ResultState.Fail))
{
string errorMessage = results.Aggregate("<br/>", (seed, item) => string.Format("行号:{0},错误信息:{1}", item.Index, item.ErrorMessage));
return AjaxErrorResult(null, errorMessage);
}
if (results.Any(item => item.State == ResultState.Fail))
{
var errorDatas = results.Where(item => item.State == ResultState.Fail).ToList();
string errorMessage = errorDatas.Aggregate("<br/>", (seed, item) => string.Format("行号:{0},错误信息:{1},操作对象:{2}", item.Index, item.ErrorMessage, item.Model.ToString()));
System.IO.File.WriteAllBytes(FileName, errorDatas.ExportWithDescription().Export().ToArray());
return AjaxPartSuccessResult(null, errorMessage);
}
return AjaxOkResult(null, "导入成功!"); } /// <summary>
/// 验证导入数据
/// </summary>
/// <param name="model"></param>
/// <param name="index"></param>
/// <returns></returns>
protected abstract ImportMessage<TModel> ValidateAndPreProccess(TModel model, int index);
}
}
组件
- office组件
- npoi
- epplus
office组件依赖于机器的office库文件,所以对于服务器来说不太友好。npoi是从java搬过来的,而且支持2003版本的excel,扩展比较丰富,唯一遗憾是不支持vba。
epplus支持vba可以说扩展性能最强,我这里由于用不到vba,而且一直都是用npoi对这个组件比较熟悉,所以使用一直沿用这今我这里就选用这个组件作为基础的excel和转换
通用类
无论是导入导出都要涉及到list,datatable,excel的转换,另外excel中。为了方便用户的编辑列头应该是使用中文,然后代码中的列是英文了,那么就有一个mapping的工作,在之前的文章中我们选用了dispalynameattribute作为界面lable的显示,我们这里也沿用这种方式,另外一种是采用desciptionattribute来标注中文。这里我全部采用扩展方法来实现,可以很好的结合链式编程,让语法更优美
List的扩展
主要包括四个扩展方法,其中在导入的时候有一个out 参数作为导入时候错误消息,通常我们在导入很大量数据的时候,作为一个事务要么全部成功要么全部错误,但是实际情况中很多用户会将数据编辑错,那么如果每次事务提交有可能需要来回修改很多次,所以我将导入分为成功,部分成功,和失败三种,这也是我们之前设计resultstate的时候有个部分成功的原因。如果用户数据错误则,收集所有的数据,可以根据你需求给出友好的提示。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlTypes;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Coralcode.Framework.Common;
using Coralcode.Framework.Models;
using Coralcode.Framework.Mvc.Models;
using Coralcode.Framework.Reflection;
using Coralcode.Framework.Services; namespace Coralcode.Framework.Extensions
{
public static class ListExtensions
{
/// <summary>
/// 把list转换成数据表,
/// todo 这里如果属性是类,需要从类里面取一个字段作为值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entities"></param>
/// <returns></returns>
public static DataTable Export<T>(this List<T> entities) where T : class
{
var dt = new DataTable(); var properties = typeof(T).GetProperties().ToList();
properties.ForEach(item => dt.Columns.Add(new DataColumn(item.Name) { DataType = item.PropertyType }));
entities.ToList().ForEach(item =>
{
var dr = dt.NewRow();
properties.ForEach(
property =>
{
var value = property.GetValue(item, null);
dr[property.Name] = value;
});
dt.Rows.Add(dr);
});
return dt;
} /// <summary>
/// 把数据表转换成List
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="dt"></param>
/// <returns></returns>
public static List<T> Import<T>(this List<T> list, DataTable dt, out List<ImportMessage> errorMessages) where T : class,new()
{
var plist = new List<PropertyInfo>(typeof(T).GetProperties());
errorMessages = new List<ImportMessage>(); for (int i = 0; i < dt.Rows.Count; i++)
{
DataRow item = dt.Rows[i];
var s = Activator.CreateInstance<T>();
foreach (DataColumn column in dt.Columns)
{
var info = plist.FirstOrDefault(p => p.Name == column.ColumnName);
if (info == null) continue;
if (item[column.ColumnName] == null)
continue; dynamic dest;
var isConvert = false;
try
{
isConvert = CoralConvert.Convert(item[column.ColumnName], info.PropertyType, out dest);
}
catch (Exception ex)
{
errorMessages.Add(new ImportMessage
{
Index = i,
State = ResultState.Fail,
ErrorMessage = string.Format("{0}的值:{1} 类型转换失败,{2}", column.ColumnName, item[column.ColumnName], ex.Message)
});
continue;
} if (!isConvert)
{
errorMessages.Add(new ImportMessage
{
Index = i,
State = ResultState.Fail,
ErrorMessage = string.Format("{0}的值:{1} 类型转换失败", column.ColumnName, item[column.ColumnName])
});
continue;
}
info.SetValue(s, dest, null); }
list.Add(s);
} return list;
} /// <summary>
/// 把list转换成数据表,
/// todo 这里如果属性是类,需要从类里面取一个字段作为值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entities"></param>
/// <returns></returns>
public static DataTable ExportWithDescription<T>(this List<T> entities) where T : class
{
var dt = new DataTable(); var properties = typeof(T).GetProperties().ToList();
properties.ForEach(item =>
{
var des = PropertyExtensions.GetDisplayName(item);
if (!string.IsNullOrEmpty(des))
dt.Columns.Add(new DataColumn(des) { DataType = item.PropertyType });
});
entities.ToList().ForEach(item =>
{
var dr = dt.NewRow();
properties.ForEach(
property =>
{
var des = PropertyExtensions.GetDisplayName(property);
if (string.IsNullOrEmpty(des))
return; var value = property.GetValue(item, null);
dr[des] = value;
});
dt.Rows.Add(dr);
});
return dt;
} /// <summary>
/// 把数据表转换成List
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="dt"></param>
/// <param name="errorMessages">错误信息</param>
/// <returns></returns>
public static List<T> ImportWithDescription<T>(this List<T> list, DataTable dt, out List<ImportMessage> errorMessages) where T : class,new()
{
var plist = new List<PropertyInfo>(typeof(T).GetProperties()); errorMessages = new List<ImportMessage>(); for(int i =0;i < dt.Rows.Count;i++)
{
DataRow item = dt.Rows[i];
var s = Activator.CreateInstance<T>();
foreach (DataColumn column in dt.Columns)
{
var info = plist.FirstOrDefault(p =>PropertyExtensions.GetDisplayName( p) == column.ColumnName);
if (info == null) continue;
if(item[column.ColumnName] == null)
continue; dynamic dest;
var isConvert = false;
try
{
isConvert = CoralConvert.Convert(item[column.ColumnName], info.PropertyType, out dest);
}
catch (Exception ex)
{
errorMessages.Add(new ImportMessage
{
Index = i,
State = ResultState.Fail,
ErrorMessage = string.Format("{0}的值:{1} 类型转换失败,{2}", column.ColumnName, item[column.ColumnName], ex.Message)
});
continue;
} if (!isConvert)
{
errorMessages.Add(new ImportMessage
{
Index = i,
State = ResultState.Fail,
ErrorMessage = string.Format("{0}的值:{1} 类型转换失败",column.ColumnName, item[column.ColumnName])
});
continue;
}
info.SetValue(s, dest, null); }
list.Add(s);
} return list;
} } }
DataTable的扩展
主要是excel和datatable的转换,这里注意是两阶段处理。在excel中没有列头的概念,但是在datatable中有,所以第一行一般是作为datatable的列来处理。然后导入导出这部分,我全部是采用stream来处理,在web应用这,可以直接从网络流转换成内存流,然后直接导入到excel,不需要写硬盘,如果写硬盘还要负责清理,稍显麻烦。
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using Coralcode.Framework.Mvc.Models.MiniUI;
using Coralcode.Framework.Mvc.Template;
using Coralcode.Framework.Utils;
using NPOI.HPSF;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;
using ServiceStack.Common.Extensions;
using ServiceStack.Messaging.Rcon; namespace Coralcode.Framework.Extensions
{
public static class DataSetExtensions
{
/// <summary>
/// 按照sheet的名称导入
/// </summary>
/// <param name="table"></param>
/// <param name="stream"></param>
/// <param name="sheetName"></param>
/// <param name="rowIndex"></param>
/// <returns></returns>
public static DataTable ImportBySheetName(this DataTable table, Stream stream, string sheetName = "Sheet1", int rowIndex = 1)
{
//根据路径通过已存在的excel来创建HSSFWorkbook,即整个excel文档
var workbook = WorkbookFactory.Create(stream); //获取excel的第一个sheet
var sheet = workbook.GetSheet(sheetName); //生成表头
sheet.GetRow(0).Cells.ForEach(item =>
{
var column = new DataColumn(item.StringCellValue);
table.Columns.Add(column);
}); //从第二行开始取数据
for (int i = (sheet.FirstRowNum + rowIndex); i <= sheet.LastRowNum; i++)
{
DataRow dataRow = table.NewRow();
sheet.GetRow(i)
.Cells.Where(item => item != null)
.ToList()
.ForEach(item => { dataRow[item.ColumnIndex] = item.ToString(); });
table.Rows.Add(dataRow);
}
return table;
} /// <summary>
/// 按照sheet的索引导入
/// </summary>
/// <param name="table"></param>
/// <param name="stream"></param>
/// <param name="sheetIndex"></param>
/// <param name="rowIndex"></param>
/// <returns></returns>
public static DataTable ImportBySheetIndex(this DataTable table, Stream stream, int sheetIndex = 0, int rowIndex = 1)
{ //根据路径通过已存在的excel来创建HSSFWorkbook,即整个excel文档
var workbook = WorkbookFactory.Create(stream); //获取excel的第一个sheet
var sheet = workbook.GetSheetAt(sheetIndex); //生成表头
sheet.GetRow(0).Cells.ForEach(item =>
{
if (string.IsNullOrEmpty(item.StringCellValue))
return;
if (string.IsNullOrWhiteSpace(item.StringCellValue))
return;
var column = new DataColumn(item.StringCellValue.Trim()); table.Columns.Add(column);
}); //从第三行开始取数据
for (int i = (sheet.FirstRowNum + rowIndex); i <= sheet.LastRowNum; i++)
{
DataRow dataRow = table.NewRow();
var row = sheet.GetRow(i);
if (row == null || row.FirstCellNum == -1 || string.IsNullOrEmpty(row.Cells[0].ToString()))
continue;
row.Cells.Where(item => item != null).ToList().ForEach(item =>
{
if (item.CellType == CellType.Numeric)
{
short format = item.CellStyle.DataFormat;
if (format == 14 || format == 31 || format == 57 || format == 58 || format == 176)
{
DateTime date = item.DateCellValue;
dataRow[item.ColumnIndex] = date.ToString("yyyy-MM-dd");
return;
}
} if (item.ColumnIndex < table.Columns.Count)
dataRow[item.ColumnIndex] = item.ToString().Trim().Trim('_');
});
table.Rows.Add(dataRow);
}
return table;
} /// <summary>
/// 导出
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static MemoryStream Export(this DataTable table)
{
var ms = new MemoryStream();
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet(); var headerRow = sheet.CreateRow(0);
// handling header.
foreach (DataColumn column in table.Columns)
headerRow.CreateCell(column.Ordinal).SetCellValue(column.Caption);
//If Caption not set, returns the ColumnName value
// handling value.
int rowIndex = 1;
foreach (DataRow row in table.Rows)
{
var dataRow = sheet.CreateRow(rowIndex); foreach (DataColumn column in table.Columns)
{
var columnValue = row[column].ToString();
if (column.DataType == typeof(Enum)
|| column.DataType.BaseType == typeof(Enum))
{
columnValue = EnumExtensions.GetDescriptionByInt(column.DataType, (int)row[column]);
} dataRow.CreateCell(column.Ordinal).SetCellValue(columnValue);
}
rowIndex++;
}
workbook.Write(ms);
return ms;
} /// <summary>
/// 按照列名导出
/// </summary>
/// <param name="table"></param>
/// <param name="header"></param>
/// <param name="focusHeader">是否只导出对应的列</param>
/// <returns></returns>
public static MemoryStream Export(this DataTable table, Dictionary<string, string> header, bool focusHeader=false)
{
var ms = new MemoryStream();
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet(); var headerRow = sheet.CreateRow(0); int columnIndex = 0;
// handling header.
foreach (DataColumn column in table.Columns)
{
if (header.ContainsKey(column.ColumnName))
{
headerRow.CreateCell(columnIndex).SetCellValue(header[column.ColumnName]);
columnIndex++;
}
else if (!focusHeader)
{
headerRow.CreateCell(columnIndex).SetCellValue(column.Caption);
columnIndex++;
}
} //If Caption not set, returns the ColumnName value
// handling value.
int rowIndex = 1;
foreach (DataRow row in table.Rows)
{
var dataRow = sheet.CreateRow(rowIndex);
columnIndex = 0;
foreach (DataColumn column in table.Columns)
{
if (focusHeader && !header.ContainsKey(column.ColumnName))
{
continue;
}
dataRow.CreateCell(columnIndex).SetCellValue(row[column].ToString());
columnIndex++;
}
rowIndex++;
}
workbook.Write(ms);
return ms;
} }
}
导入
有了上面两个扩展,那么实现起来就很方便了,步骤如下
- 接受网络流文件
- 将文件转换成DataTable
- 将DataTable转换成List
- 数据验证和预处理
- 导入数据库
- 返回导入的错误数据(包含错误数据)
但是要注意下面两个问题
分片导入
在遇到大量数据的时候,虽然ef自带了unityofwork,但是如果一次性导入 几万数据会非常慢,而且基于之前的用户体验,如果失败的话就会全部失败。 所以我们将数据分片,分片大小我一般采用100条,这个最好能写成 配置,根据情况调整。这样错误顶多是100条错误。并且分片之后可以结合TPL并行库,并行提交,不过要注意数据库链接和cpu压力
导入模板
导入模板可以可以采取动态生成,也可采取预先生成,也可以两者结合的方式 动态生成,跟导出一样,只是数据为空。预先生成就是直接人工编辑放到指定目录。两者结合的话可以采用如果没有静态文件则生成,如果有则直接下载。如果导入模板有变化,可以采取文件名的方式来区分。由于我这里性能影响基本可以忽略,这里采用的是动态生成的方式。
导出
基于之前的组件导出步骤分为以下几步
- 通过ef查询数据
- 导入到DataTable
- 转换成excel
- 直接通过网络流下载
这里要注意,由于查询和导出分离的原因最好是通过session,所见即所得的方式。先查询数据,然后把查询条件放到session,点击导出按钮的时候直接从session获取
查询条件然后走上述流程即可。
导入导出服务
这里使用一个委托作为验证,复用了之前controller的验证和预处理逻辑,做了维度的变化,和之前的服务层的设计类似
导入导出控制器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Coralcode.Framework.Domains;
using Coralcode.Framework.Extensions;
using Coralcode.Framework.Log;
using Coralcode.Framework.Models;
using Coralcode.Framework.Mvc.Extensions;
using Coralcode.Framework.Services; namespace Coralcode.Framework.Mvc.ControlContent
{
public abstract class ImportExportCoralController<TModel, TSearch> : CoralController
where TModel : class, IViewModel, new()
where TSearch : SearchBase, new()
{ private readonly ICrudCoralService<TModel, TSearch> _service;
protected readonly ImportExportService<TModel, TSearch> ImportExportService; protected Action<TModel> CreateAction;
protected ImportExportCoralController(ICrudCoralService<TModel, TSearch> service)
{
_service = service;
ImportExportService = new ImportExportService<TModel, TSearch>(service);
if (CreateAction == null)
CreateAction = item => _service.Create(item);
} protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
var routeValues = Request.GetRouteValues();
ViewBag.ImportUrl = Url.Action("Import", routeValues);
ViewBag.ExportUrl = Url.Action("Export", routeValues);
ViewBag.ExportTemplateUrl = Url.Action("DownloadTemplate");
} protected virtual string FileName
{
get { return (typeof(TModel).GetDescription() ?? IdentityGenerator.NewGuidString()) + DateTime.Now.ToString("yyyyMMddHHmmss"); }
} /// <summary>
/// 下载模板
/// </summary>
/// <returns></returns>
public virtual ActionResult DownloadTemplate()
{ var stream = ImportExportService.ExportTemplate();
return File(stream.ToArray(), "application/zip-x-compressed", FileName); } /// <summary>
/// 导出查询结果集
/// </summary>
/// <returns></returns>
public virtual ActionResult Export(TSearch searchModel)
{
searchModel = searchModel ?? new TSearch();
var stream = ImportExportService.Export(_service.Search(searchModel));
return File(stream.ToArray(), "application/zip-x-compressed", FileName);
} public ActionResult Import()
{
return PartialView("Import");
} /// <summary>
/// 导入返回结果
/// </summary>
/// <param name="uploadFile"></param>
/// <returns></returns>
[HttpPost]
public virtual ActionResult Import(HttpPostedFileBase uploadFile)
{
if (uploadFile == null || uploadFile.ContentLength < 0)
return AjaxErrorResult(null, "请选择导入文件!"); List<ImportMessage<TModel>> results;
try
{
results = ImportExportService.Import(uploadFile.InputStream, ValidateAndPreProccess);
}
catch (Exception ex)
{
LoggerFactory.Instance.Error("导入产生错误;信息:{0}", ex.ToString());
return AjaxExceptionResult(ex);
}
if (results.All(item => item.State == ResultState.Fail))
{
string errorMessage = results.Aggregate("<br/>", (seed, item) => string.Format("行号:{0},错误信息:{1}", item.Index, item.ErrorMessage));
return AjaxErrorResult(null, errorMessage);
}
if (results.Any(item => item.State == ResultState.Fail))
{
var errorDatas = results.Where(item => item.State == ResultState.Fail).ToList();
string errorMessage = errorDatas.Aggregate("<br/>", (seed, item) => string.Format("行号:{0},错误信息:{1},操作对象:{2}", item.Index, item.ErrorMessage, item.Model.ToString()));
System.IO.File.WriteAllBytes(FileName, errorDatas.ExportWithDescription().Export().ToArray());
return AjaxPartSuccessResult(null, errorMessage);
}
return AjaxOkResult(null, "导入成功!"); } /// <summary>
/// 验证导入数据
/// </summary>
/// <param name="model"></param>
/// <param name="index"></param>
/// <returns></returns>
protected abstract ImportMessage<TModel> ValidateAndPreProccess(TModel model, int index);
}
}
导入模板
@{
Layout = "~/Views/Shared/_EditLayout.cshtml";
}
<div style="padding-left: 11px; padding-bottom: 5px;">
<table style="table-layout: fixed; margin-left: 25px;">
<tr>
<td style="width: 75px;">导入文件:</td>
<td style="width: 360px;">
<input id="fileUpload" type="file" />
</tr>
</table> <div style="text-align: center; padding: 10px;">
<input type="button" onclick="onUpload()" value="上传" />
<input type="button" onclick="onCancel()" value="取消" />
</div>
</div>
@section Script {
<script type="text/javascript"> function onUpload(e) {
var url = "@Html.Raw(ViewBag.ImportAction)";
var data = new FormData();
var files = $("#fileUpload").get(0).files; // Add the uploaded image content to the form data collection
if (files.length < 0) {
mini.alert("请上传导入文件!");
}
data.append("uploadFile", files[0]);
// Make Ajax request with the contentType = false, and procesDate = false
$.ajax({
type: "POST",
url: url,
contentType: false,
processData: false,
data: data,
success: function (e) {
mini.showMessageBox({
title: "导入提示!",
iconCls: "mini-messagebox-question",
buttons: ["ok", "cancel"],
message: e.Message,
callback: function (action) {
if (e.State == 0 || e.State == 1) {
return;
}
if (action == "ok") {
//ajax post download file
$.dynamicSubmit = function (url) { var form = $('#dynamicForm'); if (form.length <= 0) {
form = $("<form>");
form.attr('id', 'dynamicForm');
form.attr('style', 'display:none');
form.attr('target', '');
form.attr('method', 'post'); var input = '';
$.each(e.Data, function (i, model) {
$.each(model, function (k, v) {
input += '<input type="hidden" name="viewModels[' + i + '].' + k + '" value="' + v + '" />';
}); }); $('body').append(form.append(input));
} form = $('#dynamicForm');
form.attr('action', url); form.submit();
}
$.dynamicSubmit("@ViewBag.ExportTemplateUrl");
} }
});
var file = $("#fileUpload");
file.after(file.clone().val(""));
file.remove();
}
});
}
</script>
}
大批量excel数据的处理
这里我们之前虽然做了分片,但是大量数据,几万几十万数据的时候肯定会很慢,我们知道sqlserver有一种bcp的导入方式,但是直接使用bcp的话数据更新又成了另外一个问题,这里我给出一个方案
- 定义表变量数据类型(列一般是原表列的一个子集)
- 编写存储过程逻辑(入参是之前定义表变量类型,然后用select into 来导入)
- 界面导入数据后显示在一个列表中
- 给出验证提示让用户需改excel数据,重复上一步和这一步,直到数据全部验证通过
- 调用存储过程提交DataTable到数据库
通过上述步骤就可以做到快速的导入数据了,这样就结合了之前逻辑和速度。这里表变量的使用请参考如下链接:http://blog.csdn.net/downmoon/article/details/7431881
其实百度的话有好多,具体自己参考下。
总结
这里我采用组合的逻辑组装的服务,考虑到不是所有增删该查的服务都需要导入导出,但是几乎所有的导入导出界面都需要增删改查的界面,所以controller采用继承来实现。
核心业务代码部分就贴了代码没有做讲解,基本上在前面几章都有介绍,所以省略掉了,有问题留言吧。
最近破事好多,代码直接从项目贴的也许无法使用,大家看看写法和实现即可。两周了写了一篇,下周争取这周末能把整个设计的代码整合成一个demo.
下一篇更精简的设计只会有设计思路,并不会有具体的实现,因为我并不推崇这种方式,只是展示一下,这个设计可以让代码精简到什么程度。
CRUD全栈式编程架构之导入导出的设计的更多相关文章
-
CRUD全栈式编程架构之更精简的设计
精简的程度 ViewModel精简 服务精简 控制器精简 Index.cshmtl精简 AddOrEdit.cshtml精简 效果:最精简的情况下,只需要写Entity这一个数据库实体然后加上一些简单 ...
-
CRUD全栈式编程架构之数据层的设计
CodeFirst 一直以来我们写应用的时候首先都是创建数据库 终于在orm支持codefirst之后,我们可以先建模. 通过模型去创建数据库,并且基于codefirst可以实现方便的 实现数据库迁移 ...
-
CRUD全栈式编程架构之MVC的扩展设计
MVC执行流程 路由的扩展 我理解的路由作用有以下几个 Seo优化,用“/”分开的url爬虫更爱吃 物理和逻辑文件分离,url不再按照文件路径映射 Controller,Action的选择 MVC路由 ...
-
CRUD全栈式编程架构之控制器的设计
页面 这里界面我采用jquery miniui来做的,当你完全了解了整个设计之后可以轻松切换到其他的js框架,个人认为类似muniui,easyui等等这类可以将web界面做得和winform类似的框 ...
-
CRUD全栈式编程架构之界面层的设计
Layout的设计 模板模式 mvc的模板特别类似设计模式中模板方法模式,结合Layout中RenderSection和RenderBody方法可以将部分html展现逻辑延迟到具体的视图页面去实现里面 ...
-
CRUD全栈式编程架构之服务层的设计
服务层代码 首先我先放出2个主要类的代码再分别讲解 接口 using System; using System.Collections.Generic; using System.Linq; usin ...
-
CRUD全栈式编程架构总结
这里放出实例代码 github.com/SkyvenXiong/HCC
-
CRUD全栈式编程概述
业务场景 CRUD,从数据驱动的角度几乎所有的的业务都是在做这样的事情. 几乎所有的操作都是在做对表的增删改查. 假设我们将数据库数据规个类: 分为基础/配置数据和业务/增长数据,或者说静态数据 ...
-
全栈式JavaScript
如今,在创建一个Web应用的过程中,你需要做出许多架构方面的决策.当然,你会希望做的每一个决定都是正确的:你想要使用能够快速开发的技术,支持持续的迭代,最高的工作效率,迅速,健壮性强.你想要精益求精并 ...
随机推荐
-
简单搭建 nuget 内部服务器
搭建 nuget 内部服务器,最好的方式是使用 ProGet,参考博文<用 ProGet 搭建内部的 NuGet 服务器>,好处非常多,但需要使用 SQL Server 数据库,如果不想使 ...
-
解决Android后台清理APP后,程序自动重启的问题
最近解决了一个Android APP的bug,发现APP在被后台清理后,会自动重启.现象很奇怪,有的手机(HTC)后台清理后,程序会再次重启,而有的手机(小米)则不会.猜想可能是小米手机内部做了处理, ...
-
BZOJ 1901: Zju2112 Dynamic Rankings[带修改的主席树]【学习笔记】
1901: Zju2112 Dynamic Rankings Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 7143 Solved: 2968[Su ...
-
C++指针详解(二)
指针是C/C++编程中的重要概念之一,也是最容易产生困惑并导致程序出错的问题之一.利用指针编程可以表示各种数据结构,通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯:指针能 ...
-
PHP Module中获取$_GET/$_POST/$_COOKIE的方法研究
假设要获取$_GET['c']; 首先,先介绍下http_globals; 1.http_globals,定义在php_globals.h中: zval * http_globals[6]; 其中的索 ...
-
【转】 CoreGraphics QuartzCore CGContextTranslateCTM 用法
原文:http://blog.csdn.net/sqc3375177/article/details/25708447 CoreGraphics.h 一些常用旋转常量 #define M_E 2.71 ...
-
NPOI:处理xls文件中的合并行
/// <summary> /// NPOI根据路径获取文件转换成DataTable /// </summary> /// <param name="FileP ...
-
用Python实现Zabbix-API 监控
做运维的朋友应该知道,公司IDC机房经常有上架.下架.报修和报废的服务器.如果服务器数量很多的时候很容易造成监控遗漏. 大的互联网公司把监控系统和CMDB(资产管理系统|配置管理数据库系统 ...
-
总目录(Catalog)
总目录(Catalog) C#高级编程(C# advanced programming) 1.并发编程(Concurrent programming)(8) ...... 数据结构与算法(Data s ...
-
[转] iOS 动画库 Pop 和 Canvas 各自的优势和劣势是什么?
iOS 动画库 Pop 和 Canvas 各自的优势和劣势是什么? http://www.zhihu.com/question/23654895/answer/25541037 拿 Canvas 来和 ...