背景介绍:php
为了平衡社区成员的贡献和索取,一块儿帮引入了帮帮币。当用户积分(帮帮点)达到必定数额以后,就会“掉落”必定数量的“帮帮币”。为了增长趣味性,帮帮币“掉落”以后全部用户均可以“捡取”,谁先捡到归谁。css
但这样就产生了一个问题,由于这个“帮帮币”是能够买卖有价值的,因此不免会有恶意用户用爬虫不断的扫描,致使这样的状况出现:html
注:经核实,乔布斯的同窗 其实没有用爬虫,就是手工点,点出来的!还能说什么呢?只能表示佩服啊佩服……jquery
因此咱们须要一种机制,阻止这种爬虫的行为。c++
大体思路:git
这个问题咱们有一个很便利的前提:只有注册用户才可以“捡起”帮帮币。因此,咱们不须要经过“封IP”(需获取真实IP)这种方式来阻断爬虫爬行,而是直接封注册用户,很是方便。程序员
那么如何判断一个请求是真实用户,仍是爬虫呢?咱们决定使用最简单的方法:记录访问频次。当某一个用户的访问频次高于设定值时(好比:5分钟10次),就断定该用户“有爬虫嫌疑”。github
此外,为了防止误判(确实有用户手快),咱们还应该给用户一个“解锁”的功能:经过输入验证码来肯定不是爬虫。ajax
细节设计:redis
一个最核心的问题是:用什么来记录用户的访问频次?
数据库?感受不必,这个数据又不须要长期保留,访问一次就作一次I/O操做在性能上接受不了,因此咱们决定使用内存。
可是,具体须要记录那些数据,又用什么样的数据结构呢?
最后咱们选择使用缓存,记录最简单的“用户ID -> 访问次数”键值对,来解决这个问题,由于:
- 利用缓存的自动清除(expire)特性,清除过时数据,保证记录的访问次数始终是在必定时间内的。
- 缓存的读写速度很快,性能上没有压力
固然,这里其实仍是有那么点问题的。好比,假设缓存时间是5分钟,最多访问次数是10次。0:10,开始缓存访问次数,一直累加,到0:14,共记录访问次数7次,没有问题;然而,一过0:15,缓存被清空,0:16的时候,缓存里只有0:15到0:16这一分钟的数据,没有过去5分钟(从0:11到0:16)的数据。因此用户能够控制一直爬虫,访问9次,而后就歇着,5分钟事后,再继续访问9次,而后再歇5分钟……
唉~~真这么拼,我还真没什么办法?但若是这么一个频次他能接受的话,我其实也无所谓,你就慢慢爬呗。或者,咱们后台作更大的监控,把每一个用户的每次访问都记录下来,进行统计,找出异常。那时候可能就真的须要数据库了(为了提升性能能够内存里放一个DataTable,定时同步到Database)。但暂时来讲,没有这个必要。
此外,还有一个问题,是否是只须要记录用户访问频次?
若是按上述方案,在缓存里记录访问频次,经过缓存数据来判断是否容许继续访问,会有一个问题:缓存到期失效以后,这个用户就又能够自由访问目标页面了!至关于到期自动解锁。
我以为这仍是不科学,若是认定是爬虫,只能是人工解锁(识别码验证)。因此在数据库用户表里添加一个“已锁定”(Locked)字段,若是用户被锁定,Update其为当前时间;未锁定时(解锁后)为NULL。
具体实现:
为了重用,咱们须要利用 Authorize Fitler,在它的OnAuthorization()方法里面进行检查和记录。
代码自己应该比较简单,if...else...的逻辑:
///1. 先根据数据库捡查当前用户是否被锁定 ///2. 若是被锁定,直接拦截。不然: ///3. 在缓存中检查有无当前用户的访问次数记录 /// 3.1 没有,新建一条他的缓存。不然: /// 3.2 检查该用户已访问次数 /// 3.2.1 若是已到达访问次数限制,拦截并在数据库中锁定该用户。不然 /// 3.2.2 累加用户的访问次数
精简注释代码以下:
public class NeedLogOn : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { HttpContextBase context = filterContext.HttpContext; ///Autofac相关操做,获取正取的ISharedService实例 ISharedService service = AutofacConfig.Container.Resolve<ISharedService>(); _NavigatorModel model = service.Get(); //从数据库获取当前User的信息 ///截断式编程,减小if...else的{}嵌套 if (model.Locked.HasValue) { ///model.Locked 来自数据库,用户已经被锁定,拦截 visitTooMuch(filterContext); return; } string cacheKey = CacheKey.MAX_VISIT + model.Id; ///很是有意思,不能直接使用int值类型,必须使用引用类型的 VisitCounter amount; if (context.Cache[cacheKey] == null) { amount = new VisitCounter { Value = 1 }; ///新创建一条Cache context.Cache.Add(cacheKey, amount, null, DateTime.Now.AddSeconds(Config.Seconds), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } else { amount = context.Cache[cacheKey] as VisitCounter; if (amount.Value >= Config.MaxVisit) { ///在数据库中锁定该用户 service.LockCurrentUser(); BaseService.Commit(); ///当即清除Cache context.Cache.Remove(cacheKey); visitTooMuch(filterContext); return;} else { ///不能使用:currentVisitAmount++; ///context.Cache[cacheKey] = currentVisitAmount; ///见:https://stackoverflow.com/questions/2118067/cached-item-never-expiring amount.Value++; } } } } public class VisitCounter { public int Value { get; set; } }
仔细观察代码,你会发现两个问题。这就是飞哥我曾经掉的坑啊!o(╥﹏╥)o
一、为何要引入VisitCounter类?
缓存里就存放着这个类的实例,而这个类其实就包裹一个int Value;干吗呢,这是?为何不直接用int呢?直接把int存到Cache里不行吗?
不行啊!艹。
存进去,没问题;取出来,也没问题;但更新(累加)的时候有问题啊。你怎么更新?
//取出缓存 currentVisitAmount = Convert.ToInt32(context.Cache[cacheKey]); //累加 currentVisitAmount++; //再存进去 context.Cache[cacheKey] = currentVisitAmount;
这样不行的,具体的解释看这里:Cached item never expiring。
简单的说,context.Cache[cacheKey] = currentVisitAmount; 这一句,等于从新插入了一条永不过时的缓存。万万没想到啊!这个bug把飞哥都差点搞疯了,原本cache的调试都很是麻烦,还搞个这种幺蛾子。
因此解决的办法是什么呢?在Cache里存一个引用类型值,而后不改Cache,只改引用类实例里的值就OK了。代码就不重复了。
二、在锁定用户的同时,清除该用户的cache
这里啊,曾经走了点弯路。
我最开始是在解锁用户的时候清除该用户的Cache。
[NeedLogOn] public ActionResult Unlock() { string userId = getCurrentUserId(); string cacheKey = CacheKey.MAX_VISIT + userId; HttpContext.Cache.Remove(cacheKey); return View(new ImageCodeModel()); }
结果不知道咋回事,时灵时不灵。我把本地代码,链接服务器数据库,开着Debug模式,一步一会的进去看,OK,没问题;但把本地代码发布到服务器,duang,不行了?!无法调试,只有写log啥的,坑得我不要不要的……
后来忽然发现,这里有“坏代码的味道”:重复。你看这个cacheKey的构建,是否是在 NeedLogOn.OnAuthorization()里构建过一次?重复使用的代码是否是就应该封装?因此呢,开始呢,是想弄一个方法出来得到cacheKey,好比striing GetVisitLimitCacheKey()啥的,但这个方法要让Controller里的UnLock()和Filter里的OnAuthorization()都能调用,放在哪里呢?
忽然灵光一闪:为何 Cache.Remove 要写在UnLock()里面呢?
其实只要用户被锁定,他的缓存信息就没用了。由于咱们已经在数据库中标明了他被Locked,因此NeedLogOn.OnAuthorization()拦截住他,不须要Cache呀!尽早的清除这个Cache,还能提升那么一点点的性能。
最关键的是,这样代码更紧凑了:cacheKe在同一个方法里被使用,cache操做在同一个方法类完成,避免了代码分散耦合,优雅多了!
C#中缓存的使用
缓存的概念及优缺点在这里就很少作介绍,主要介绍一下使用的方法。
1.在ASP.NET中页面缓存的使用方法简单,只须要在aspx页的顶部加上一句声明便可:
<%@ OutputCache Duration="100" VaryByParam="none" %>
Duration:缓存时间(秒为单位),必填属性
2.使用微软自带的类库System.Web.Caching
新手接触的话不建议直接使用微软提供的类库,由于这样对理解不够深入。因此在这里我带你们本身写一套缓存操做方法,这样理解得更加清晰。
话很少说,代码开敲。
1、首先,先模拟数据来源。新建一个类,写一个数据操做方法(该方法耗时、耗资源)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading;
using
System.Threading.Tasks;
namespace
Cache
{
public
class
DataSource
{
/// <summary>
/// 模拟从数据库读取数据
/// 耗时、耗CPU
/// </summary>
/// <param name="count"></param>
public
static
int
GetDataByDB(
int
count)
{
Console.WriteLine(
"-------GetDataByDB-------"
);
int
result = 0;
for
(
int
i = count; i < 99999999; i++)
{
result += i;
}
Thread.Sleep(2000);
return
result;
}
}
}
|
2、编写一个缓存操做类
2.1 构造一个字典型容器,用于存放缓存数据,权限设为private ,防止随意访问形成数据不安全性
//缓存容器 private static Dictionary<string, object> CacheDictionary = new Dictionary<string, object>();
2.2 构造三个方法(添加数据至缓存容器、从缓存容器获取数据、判断缓存是否存在)
/// <summary> /// 添加缓存 /// </summary> public static void Add(string key, object value) { CacheDictionary.Add(key, value); } /// <summary> /// 获取缓存 /// </summary> public static T Get<T>(string key) { return (T)CacheDictionary[key]; } /// <summary> /// 判断缓存是否存在 /// </summary> /// <param name="key"></param> /// <returns></returns> public static bool Exsits(string key) { return CacheDictionary.ContainsKey(key); }
3、程序入口编写测试方法
3.1 先看一下普通状况不适用缓存,它的执行效率有多慢
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Cache { class Program { static void Main(string[] args) { for (int i = 1; i < 6; i++) { Console.WriteLine($"------第{i}次请求------"); int result = DataSource.GetDataByDB(666); Console.WriteLine($"第{i}次请求得到的数据为:{result}"); } } } }
3.2 接下来,咱们编写缓存试用方法。概念无非就是根据key前往字典容器里查找是否有相对应缓存数据,有则直接调用,没有则生成并存入字典容器里。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Cache { class Program { static void Main(string[] args) { for (int i = 1; i < 6; i++) { Console.WriteLine($"------第{i}次请求------"); //int result = DataSource.GetDataByDB(666); int result = 0; //key的名字必定要确保请求的准确性 DataSource GetDataByDB 666缺一不可 string key = "DataSource_GetDataByDB_666"; if (CacheHelper.Exsits(key)) { //缓存存在,直接获取原数据 result = CacheHelper.Get<int>(key); } else { //缓存不存在,去生成缓存,并加入容器 result = DataSource.GetDataByDB(666); CacheHelper.Add(key, result); } Console.WriteLine($"第{i}次请求得到的数据为:{result}"); } } } }
3.3 咱们看看加入缓存以后的效率如何
4、能够看到,瞬间完成。事已至此,缓存的使用基本是完成了。可是回过头来咱们想一想看。一个系统成百上千个地方使用缓存的话,那岂不是要写成百上千个if else判断缓存是否存在,而后获取?
答案显而易见,确定不合理的。因此咱们要对代码进行优化。
4.1 缓存操做类(CacheHelper)编写一个通用的获取方法
/// <summary> /// 缓存获取方法 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key">缓存字典容器对应key</param> /// <param name="func">委托方法 传入操做对象</param> /// <returns></returns> public static T GetCache<T>(string key, Func<T> func) { T t = default(T); if (CacheHelper.Exsits(key)) { //缓存存在,直接获取原数据 t = CacheHelper.Get<T>(key); } else { //缓存不存在,去生成缓存,并加入容器 t = func.Invoke(); CacheHelper.Add(key, t); } return t; }
4.2 程序入口进行调用,传入的委托参数为lamad表达式优化后的代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Cache { class Program { static void Main(string[] args) { for (int i = 1; i < 6; i++) { Console.WriteLine($"------第{i}次请求------"); int result = 0; //key的名字必定要确保请求的准确性 DataSource GetDataByDB 666缺一不可 string key = "DataSource_GetDataByDB_666"; //将须要执行的获取数据操做编写成委托传入方法(重点) //Func<int> func = new Func<int>(() => { return DataSource.GetDataByDB(666); }); result = CacheHelper.GetCache(key, () => DataSource.GetDataByDB(666)); Console.WriteLine($"第{i}次请求得到的数据为:{result}"); } } } }
到这里,缓存的使用基本结束了。最好值得一提的是,缓存尽可能在数据量小、重复查询量大的状况下使用。由于缓存也是要耗内存的,服务器内存是有限的!
C#操做redis
Redis 是一个非关系型高性能的key-value数据库。在部分场合能够对关系数据库起到很好的补充做用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
redis提供五种数据类型:string,hash,list,set及zset(sorted set)。
好了,话很少说,先安装redis吧。我这里提供的版本是64位的3.2.1.00 https://files.cnblogs.com/files/wangjifeng23/Redis-x64-3.2.100.zip ,其他版本可前往官网进行下载 http://download.redis.io/releases/ 。
下载好以后,新建文件夹,将文件解压。
解压完以后,开始进行redis安装。
1.键入cmd
2.指向redis安装路径 f: --> cd redis
3.redis安装指令 redis-server redis.windows.conf,出现如下图标即安装成功
打开redis客户端工具(redis-cli.exe)
使用set get设置获取值,以下所示即便用成功
好了,为了使用方便,咱们能够把redis部署到服务上面自启动,而后使用第三方客户端软件RedisDesktopManager(下载连接: https://pan.baidu.com/s/1DAWFwlZQK0AJphOQEHQaXA 密码: jr5r)进行管理,让开发更加便捷。
如上所示使用cmd键入命令: redis-server --service-install redis.windows.conf
打开客户端,建立链接,输入localhost(本机服务),链接前确保redis服务已开启,端口为6379(主服务器)
以下图所示证实咱们已经链接成功啦,左边就是我存储的4个键值对数据。
好了,接下来咱们要在代码里实现对他的存储以及获取。
使用NuGet安装ServiceStack.Redis,这是微软提供已经封装好的对redis操做类。包含4个dll
链接redis服务器,读取以及存储
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace redis { public partial class Login : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } public static ServiceStack.Redis.RedisClient client = new ServiceStack.Redis.RedisClient("127.0.0.1", 6379); public void login(object sender, EventArgs e) { //读取 string name = client.Get<string>("name"); string pwd = client.Get<string>("password"); //存储 client.Set<string>("name1", username.Value); client.Set<string>("password1", userpwd.Value); } } }
总结:
1 、Redis不只仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,能够将内存中的数据保持在磁盘中,重启的时候能够再次加载进行使用。
四、Redis能够实现主从复制,实现故障恢复。
五、Redis的Sharding技术: 很容易将数据分布到多个Redis实例中
转载请注明出处
WPF 控件库——可拖动选项卡的TabControl
1、先看看效果
2、原理
一、选项卡大小和位置
此次给你们介绍的控件是比较经常使用的TabControl,网上常见的TabControl样式有不少,其中一部分也支持拖动选项卡,可是带动画效果的不多见。这也是有缘由的,由于想要作一个不失原有功能,还须要添加动画效果的控件可不是一行代码的事。要作成上图中的效果,咱们不能一蹴而就,最忌讳的是一上来就想实现全部效果。
一开始,咱们最好先用Blend看看原生的TabControl样式模板部分是如何实现的,这样咱们也好有个参考。咱们先从资产面板中拖一个TabControl放到窗体中,调整好合适的大小:
而后在它上面右键,编辑模板->编辑副本->肯定,在自动生成的xaml代码中关键部分是这里:
能够看到,全部的选项卡(也就是TabItem)其实都是放在TabControl内部维护的一个TabPanel中,知道这些就够了,咱们彻底能够作一个定制的TabPanel来替换它: public class TabPanel : Panel 。既然这个TabPanel是一个容器,因此它必须负责计算TabItem的大小还要安排它的位置,咱们能够重载父类Panel的 MeasureOverride 方法来处理这些逻辑: protected override Size MeasureOverride(Size constraint) 。在这个方法中咱们经过 InternalChildren 这个只读属性来获取选项卡,选项卡的高度咱们由 TabItemHeight 属性指定,因为TabPanel对用户是透明的,因此咱们还要定制一个TabControl,里面加上 TabItemHeight 属性,让它和TabPanel的绑定。以后的 TabItemWidth 和 IsEnableTabFill 也同理。而选项卡的宽度则要分状况讨论了,若是 IsEnableTabFill = true 咱们则要平分宽度,例如容器宽度为100,选项卡有10个,那么每一个选项卡的宽度就是10。在这里要注意的是,选项卡的宽度最好不要有小数点,虽然有诸如 UseLayoutRounding 这种特性的帮助能够必定程度去除模糊,但在一个个连续排列的选项卡上反而会拔苗助长,你会发现两两之间的分割线宽度是不一致的,最好的办法就是“不公平的平分”,贴上一段代码来解释:
public static int[] DivideInt2Arr(int num, int count) { var arr = new int[count]; var div = num / count; var rest = num % count; for (int i = 0; i < count; i++) { arr[i] = div; } for (int i = 0; i < rest; i++) { arr[i] += 1; } return arr;
}
假设如今的容器宽度是108,选项卡仍是10个,经过 MeasureOverride 方法处理后,前八个的宽度则是11,后两个是10。若是 IsEnableTabFill = false 则不要平分了,直接放入容器便可。
如今选项卡大小搞定了,位置呢?太简单了,一个for循环不断叠加每一个选项卡的宽度就能够了: size.Width += tabItem.ItemWidth; 。最后经过调用 Element.Arrange 便可排布选项卡的位置:
var rect = new Rect { X = size.Width - tabItem.BorderThickness.Left, Width = itemWidth, Height = TabItemHeight }; tabItem.Arrange(rect);
由于选项卡左右都有边距,减去一个左边距,二者间的间隔就是一个边距了。
选项卡大小和位置的逻辑处理大体是上述的过程,因为篇幅有限,加之我不喜欢一贴一大段代码,因此只挑重点来讨论,完整的代码还要考虑各类状况,这里就再也不赘述了。
二、动画处理
这一部分咱们的关注点就是鼠标了,对选项卡而言,鼠标按下、鼠标移动、鼠标抬起,这些咱们都要关注,因此分别给它们订阅一下事件。与之对应的,咱们还要给选项卡添加几个标私有字段,用以记录状态,好比 _isDragging 、 _isDragged 、 _dragPoint 、 _isWaiting ,前两个我就不说了,都是字面意思,第三个则用来暂存鼠标移动时的位置,每次进入选项卡的 OnMouseMove 事件,都要将 _isDragged 和其旧值做差,以求得当前选项卡应该移动的距离。 _isWaiting 用途比较特殊,在用户拖动选项卡时,咱们最好等待一个粘滞距离,好比20个单位宽度,也就是说,在水平方向鼠标移动了超过20个像素无关单位后,选项卡才开始被拖动。
在一开始的gif中能够看到,被拖动的选项卡改变位置时,其他的选项卡也会动态改变位置,那么位置改变的时机是如何肯定的呢?很简单,只要将被拖动的选项卡到容器(TabPanel)左边界的这个距离除以 ItemWidth ,结果四舍五入就是这个选项卡当前应该所处的位置,紧接着下一步就是要把这个位置上的选项卡和当前被拖动的换个位置。此刻咱们终于能够用动画来实现了,因为这个系列的文章屡次讲过动画的代码了,因此就再也不赘述。
上面一段讲的是换位置,那么添加选项卡、删除选项卡呢?其实有个捷径能够走,就是使用 FluidMoveBehavior ,把他往样式里一塞,好了,效果出来了!
可是这里有一个坑要注意, FluidMoveBehavior 虽然能够化简一部分动画逻辑,可是它有点越权了,它把你位置移动的逻辑也给作了,你会发现,若是不加处理,在你本身的动画结束后它还会再来一遍它的动画。能够将 FluidMoveBehavior 的 Duration 属性暂时归零来解决这个问题: FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); 。
这篇文章只是大体介绍一下实现的过程和思路,感兴趣的能够下载源码,多多交流,共同提升。
3、源码
本文所讨论的控件源码已经在github开源:https://github.com/NaBian/HandyControl
【Bootstrap系列】详解Bootstrap-table
本篇文章将与你们分享bootstrap-table插件,借助于它实现基本的增删改查,导入导出,分页,父子表等。
至于其余技术,如冻结表头,列排列,行拖动,列拖动等,会在后续文章中与你们分享。
一 效果图
(一)页面初始化
下图是页面首次加载结束后的效果,主要完成如下功能:
1.日期部分,开始时间:当前月第一天对应的8位日期,结束时间:当前月最后一天对应的8位日期,时间格式为:yyyy-mm-dd
2.bootstrap-table加载的数据为日期部分所对应的时间,且按照时间递减展现
(二)查询
1.支持日期查询和订单编号查询
2.当日期和订单编号都存在时,忽略日期条件(由于订单编号是惟一的)
以下为查询结果:
(三)添加
1.利用dialog模态框加载AddForm页面;
2.实现可拖拽
(四)编辑
1.利用dialog模态框加载EditForm页面
2.根据订单编号选择编辑
(五)删除
1.选中删除
(六)导入
1.下载导入模板
2.按照模板格式导入数据
(七)导出
1.选中导出
2.导出支持多种格式
(八)父子表
1.订单表做为父表,产品表做为子表
2.父表和字表经过产品编号来关联
二 Bootstrap-table讲解
关于bootstrap-table参数,须要掌握以下几大类:表格参数,列参数,事件,方法和多语言,
详情能够参考bootstrap-table官网:http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/
三 本Demo技术讲解
(一)Demo架构图
本Demo采用UI+BLL+DAL+Model三层架构。
(二)核心代码
1.Bootstrap-table JS结构定义

1 //初始化 2 var InitTable = function (url) { 3 //先销毁表格 4 $('#tb_SaleOrder').bootstrapTable("destroy"); 5 //加载表格 6 $('#tb_SaleOrder').bootstrapTable({ 7 rowStyle: function (row, index) {//row 表示行数据,object,index为行索引,从0开始 8 var style = ""; 9 if (row.SignInTime == '' || row.SignOutTime=='') { 10 style = { css: { 'color': 'red' } }; 11 } 12 return style; 13 }, 14 //searchAlign: 'left', 15 //search: true, //显示隐藏搜索框 16 showHeader: true, //是否显示列头 17 //classes: 'table-no-bordered', 18 showLoading: true, 19 undefinedText: '', 20 showFullscreen: true, 21 toolbarAlign: 'left', 22 paginationHAlign: 'right', 23 silent: true, 24 url: url, 25 method: 'get', //请求方式(*) 26 toolbar: '#toolbar', //工具按钮用哪一个容器 27 striped: true, //是否显示行间隔色 28 cache: false, //是否使用缓存,默认为true,因此通常状况下须要设置一下这个属性(*) 29 pagination: true, //是否显示分页(*) 30 sortable: false, //是否启用排序 31 sortOrder: "asc", //排序方式 32 //queryParams: InitTable.queryParams, //传递参数(*) 33 sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) 34 pageNumber: 1, //初始化加载第一页,默认第一页 35 pageSize: 10, //每页的记录行数(*) 36 pageList: [2, 5, 10, 15], //可供选择的每页的行数(*) 37 search: false, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,因此,我的感受意义不大 38 strictSearch: true, 39 showColumns: true, //是否显示全部的列 40 showRefresh: true, //是否显示刷新按钮 41 minimumCountColumns: 2, //最少容许的列数 42 clickToSelect: true, //是否启用点击选中行 43 //height: 680, //行高,若是没有设置height属性,表格自动根据记录条数以为表格高度 44 uniqueId: "ID", //每一行的惟一标识,通常为主键列 45 showToggle: true, //是否显示详细视图和列表视图的切换按钮 46 cardView: false, //是否显示详细视图 47 detailView: false, //是否显示父子表 48 showExport: true, 49 //exportDataType: 'all', 50 exportDataType: "selected", //导出checkbox选中的行数 51 paginationLoop: false, //是否无限循环 52 columns: [{ 53 checkbox: true 54 }, { 55 field: 'OrderNO', 56 title: '订单编号' 57 }, { 58 field: 'ProductNo', 59 title: '产品编号' 60 }, { 61 field: 'CustName', 62 title: '客户姓名' 63 }, { 64 field: 'CustAddress', 65 title: '客户地址', 66 }, { 67 field: 'CustPhone', 68 title: '客户电话', 69 }, { 70 field: 'CustCompany', 71 title: '客户公司', 72 }, { 73 field: 'CreateDateTime', 74 title: '订单建立时间', 75 }, { 76 field: 'UpdateDateTime', 77 title: '订单更新时间', 78 }] 79 }); 80 return InitTable; 81 };
2.订单表增删改查

1 $(function () { 2 //初始时间控件 3 var frstDayDate = GetLocalMonFrstDayDate(); 4 var lastDayDate = GetLocalMonLastDayDate(); 5 $("#startDate").val(frstDayDate); 6 $("#endDate").val(lastDayDate); 7 8 //初始化bootstrap-table参数 9 var filterParam = ""; 10 var startDate = $("#startDate").val(); 11 var endDate = $("#endDate").val(); 12 url = "/SaleManage/GetOrderList?startDate=" + startDate + "&endDate=" + endDate + "&orderNO=" + filterParam + ""; 13 InitTable(url); 14 15 //查询数据 16 $("#btn_query").click(function () { 17 var filterParam = $("#queryKey").val(); 18 var startDate = $("#startDate").val(); 19 var endDate = $("#endDate").val(); 20 var url = "/SaleManage/GetOrderList?startDate="+ startDate + "&endDate=" +endDate + "&orderNO=" + filterParam + ""; 21 InitTable(url); 22 }) 23 24 //添加 25 $("#btn_add").click(function () { 26 var url = "/SaleManage/AddForm"; 27 openDialog(url, "AddForm", "添加订单", 645, 470, function (iframe) { 28 top.frames[iframe].AcceptClick() 29 }); 30 }) 31 32 //编辑 33 $("#btn_edit").click(function () { 34 //获取当前选择行id 35 var selectedRows = $("#tb_SaleOrder").bootstrapTable('getSelections'); 36 if (selectedRows.length <= 0) { 37 alert('请选择要编辑的数据'); 38 } else if (selectedRows.length > 1) { 39 alert('一次只能选择一行数据进行编辑'); 40 } else { 41 var KeyValue = selectedRows[0].OrderNO; 42 var url = "/SaleManage/EditForm?KeyValue=" + KeyValue; 43 openDialog(url, "EditForm", "编辑邮件", 645, 470, function (iframe) { 44 top.frames[iframe].AcceptClick() 45 }); 46 } 47 }) 48 //删除数据 49 $("#btn_delete").click(function () { 50 //获取当前选择行id 51 var selectedRows = $("#tb_SaleOrder").bootstrapTable('getSelections'); 52 if (selectedRows.length <= 0) { 53 alert('请选择要删除的数据'); 54 return; 55 } 56 if (selectedRows.length > 1) { 57 alert('一次只能选择一行删除'); 58 return; 59 } 60 var orderNo = selectedRows[0].OrderNO; 61 //aja异步请求 62 $.ajax({ 63 url: '/SaleManage/DelOrder', 64 type: 'get', 65 contentType: 'application/json;charset=utf-8', 66 data: { orderNo: orderNo }, 67 success: function (data) { 68 //刷新bootstrap-table 69 $("#tb_SaleOrder").bootstrapTable('refresh'); 70 }, 71 error: function (data) { 72 alert('数据删除失败' + data); 73 } 74 }) 75 }) 76 77 //回车键 78 document.onkeydown = function (e) { 79 if (!e) e = window.event; //火狐中是 window.event 80 if ((e.keyCode || e.which) == 13) { 81 var query = document.getElementById("btn_query"); 82 query.focus(); 83 query.click(); 84 } 85 } 86 });
3.日期初始化

1 //当月第一天所对应的日期 yyyy-mm-dd 2 function GetLocalMonFrstDayDate() { 3 var now = new Date(); 4 var year = now.getFullYear();//年 5 var mon = now.getMonth() + 1;//月 6 if (mon < 10) { 7 mon = '-0' + mon; 8 } 9 var frstDay = "-01"; //日 10 return year + mon + frstDay; 11 } 12 //当月最后一天所对应的日期 yyyy-mm-dd 13 function GetLocalMonLastDayDate() { 14 var now = new Date(); 15 var year = now.getFullYear();//年 16 var mon = now.getMonth() + 1;//月 17 if (mon < 10) { 18 mon = '-0' + mon; 19 } 20 var LastDay = "-" + GetDayCountInMon(year + mon); 21 return year + mon + LastDay; 22 } 23 //计算当月对应的最大天数 24 function GetDayCountInMon(YearMon) { 25 var arr = YearMon.split("-"); 26 var localYear = parseInt(arr[0]); 27 var localMon = parseInt(arr[1]); 28 var localDate = new Date(localYear, localMon, 0); 29 return localDate.getDate(); 30 }
4.Index.cshtml

1 @{ 2 Layout = "~/Views/Shared/_LayoutBTSTable.cshtml"; 3 } 4 5 <!--查询条件--> 6 <div class="panel-body" style="padding-bottom:0px;width:104%;margin-left:-15px"> 7 <div class="panel panel-default"> 8 <div class="panel-heading"> 9 订单管理 10 </div> 11 <div style="margin-top:-30px;text-align:right"> 12 <a href="~/Files/ImportTemple.xlsx" style="margin-right:20px">下载导入模板 </a> 13 </div> 14 <div class="panel-body"> 15 <div style="margin-top:10px;"> 16 日期: 17 <input type="text" id="startDate" style="height:35px;width:100px;margin-left:5px;margin-top:-32px;border-radius: 6px;border: 1px #cccccc solid; outline: none" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd'})"> 18 — 19 <input type="text" id="endDate" style="height:35px;width:100px;margin-left:8px;margin-top:-34px;border-radius: 6px;border: 1px #cccccc solid; outline: none" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd'})"> 20 订单编号:<input type="text" id="queryKey" placeholder="请输入订单编号进行查询" style="height:35px;width:170px;margin-left:10px;margin-top:-34px;border-radius: 6px;border: 1px #cccccc solid; outline: none"> 21 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_query" class="btn btn-success">查询</button> 22 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_add" class="btn btn-info">添加</button> 23 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_edit" class="btn btn-warning">编辑</button> 24 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_delete" class="btn btn-danger">删除</button> 25 </div> 26 </div> 27 </div> 28 </div> 29 <!--初始化bootstrap-table--> 30 <div style="margin-bottom:-40px;color:red">注释:订单数据</div> 31 <table id="tb_SaleOrder" class="table"></table> 32 33 <style> 34 #tb_SaleOrder tbody > tr:hover { 35 background-color: #449d44; 36 } 37 38 #tb_SaleOrder > thead th { 39 padding: 0; 40 margin: 0; 41 background-color: #d9edf7; 42 } 43 </style> 44 <script> 45 //刷新bootstrap-table 46 function refleshBootStrapTable() { 47 $("#tb_SaleOrder").bootstrapTable('refresh'); 48 } 49 </script> 50 51 <script src="~/CustomUI/TableJS/SaleOrder.js"></script>
5.AddForm.cshtml

1 @{ 2 ViewBag.Title = "AddForm"; 3 Layout = "~/Views/Shared/_LayoutBTSTable.cshtml"; 4 } 5 6 <script> 7 //添加数据 8 function AcceptClick() { 9 var OrderNO = $("#OrderNO").val(); 10 var ProductNo = $("#ProductNo").val(); 11 var CustName = $("#CustName").val(); 12 var CustAddress = $("#CustAddress").val(); 13 var CustPhone = $("#CustPhone").val(); 14 var CustCompany = $("#CustCompany").val(); 15 var CreateDateTime = $("#CreateDateTime").val(); 16 var UpdateDateTime = $("#UpdateDateTime").val(); 17 $.ajax({ 18 url: '/SaleManage/AddDataToDB', 19 type: 'get', 20 contentType: 'application/json;charset=utf-8', 21 data: { 22 'OrderNO': OrderNO, 'ProductNo': ProductNo, 'CustName': CustName, 23 'CustAddress': CustAddress, 'CustPhone': CustPhone, 'CustCompany': CustCompany, 24 'CreateDateTime': CreateDateTime, 'UpdateDateTime': UpdateDateTime 25 }, 26 success: function (data) { 27 reflesh(); 28 //关闭对话框 29 closeDialog(); 30 }, 31 error: function (data) { 32 alert('添加数据失败' + data); 33 } 34 }) 35 } 36 //刷新 37 function reflesh() { 38 window.parent.refleshBootStrapTable(); 39 } 40 </script> 41 42 43 <div class="table" style="width:100%;margin-top:10px"> 44 <table id="tb_SaleOrder_Add" class="table text-nowrap" style="text-align:right;"> 45 <tr> 46 <td style="height:35px;line-height:35px">订单编号 :</td> 47 <td><input type="text" id="OrderNO" style="width:500px;" /></td> 48 </tr> 49 <tr> 50 <td style="height:35px;line-height:35px">产品名称 :</td> 51 <td><input type="text" id="ProductNo" style="width:500px;" /></td> 52 </tr> 53 <tr> 54 <td style="height:35px;line-height:35px">客户姓名 :</td> 55 <td><input type="text" id="CustName" style="width:500px;" /></td> 56 </tr> 57 <tr> 58 <td style="height:35px;line-height:35px">客户地址 :</td> 59 <td><input type="text" id="CustAddress" style="width:500px;" /></td> 60 </tr> 61 <tr> 62 <td style="height:35px;line-height:35px">客户电话 :</td> 63 <td><input type="text" id="CustPhone" style="width:500px;" /></td> 64 </tr> 65 <tr> 66 <td style="height:35px;line-height:35px">客户公司 :</td> 67 <td><input type="text" id="CustCompany" style="width:500px;" /></td> 68 </tr> 69 <tr> 70 <td style="height:35px;line-height:35px">订单建立时间 :</td> 71 <td><input type="text" id="CreateDateTime" style="width:500px;" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" class="Wdate"/></td> 72 </tr> 73 <tr> 74 <td style="height:35px;line-height:35px">订单更新时间 :</td> 75 <td><input type="text" id="UpdateDateTime" style="width:500px;" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" class="Wdate"/></td> 76 </tr> 77 </table> 78 </div> 79 80 <style> 81 #tb_SaleOrder_Add td input[type=text] { 82 height: 35px; 83 border-radius: 6px; 84 border: 1px #cccccc solid; 85 outline: none 86 } 87 </style>
6.EditForm.cshtml

@{ ViewBag.Title = "EditForm"; Layout = "~/Views/Shared/_LayoutBTSTable.cshtml"; } <script> $(function () { //初始化页面控件 $.ajax({ url: "/SaleManage/InitModifySheet", type: 'get', contentType: 'application/json;charset=utf-8', data: { orderNO: GetQuery('KeyValue') }, success: function (data) { //将回调数据转化为json对象 var jsonData = eval(data); //遍历,为表单赋值 $("#OrderNO").val(jsonData[0].OrderNO); $("#ProductNo").val(jsonData[0].ProductNo); $("#CustName").val(jsonData[0].CustName); $("#CustAddress").val(jsonData[0].CustAddress); $("#CustPhone").val(jsonData[0].CustPhone); $("#CustCompany").val(jsonData[0].CustCompany); $("#CreateDateTime").val(jsonData[0].CreateDateTime); $("#UpdateDateTime").val(jsonData[0].UpdateDateTime); }, error: function (data) { alert('编辑数据失败' + data); } }) }) //添加数据 function AcceptClick() { var OrderNO = $("#OrderNO").val(); var ProductNo = $("#ProductNo").val(); var CustName = $("#CustName").val(); var CustAddress = $("#CustAddress").val(); var CustPhone = $("#CustPhone").val(); var CustCompany = $("#CustCompany").val(); var CreateDateTime = $("#CreateDateTime").val(); var UpdateDateTime = $("#UpdateDateTime").val(); $.ajax({ url: '/SaleManage/ModifyDataToDB', type: 'get', contentType: 'application/json;charset=utf-8', data: { 'OrderNO': OrderNO, 'ProductNo': ProductNo, 'CustName': CustName, 'CustAddress': CustAddress, 'CustPhone': CustPhone, 'CustCompany': CustCompany, 'CreateDateTime': CreateDateTime, 'UpdateDateTime': UpdateDateTime }, success: function (data) { reflesh(); //关闭对话框 closeDialog(); }, error: function (data) { alert('添加数据失败' + data); } }) } //刷新 function reflesh() { window.parent.refleshBootStrapTable(); } </script> <div class="table" style="width:100%;margin-top:10px"> <table id="tb_SaleOrder_Add" class="table text-nowrap" style="text-align:right;"> <tr> <td style="height:35px;line-height:35px">订单编号 :</td> <td><input type="text" id="OrderNO" style="width:500px;" disabled/></td> </tr> <tr> <td style="height:35px;line-height:35px">产品名称 :</td> <td><input type="text" id="ProductNo" style="width:500px;" /></td> </tr> <tr> <td style="height:35px;line-height:35px">客户姓名 :</td> <td><input type="text" id="CustName" style="width:500px;" /></td> </tr> <tr> <td style="height:35px;line-height:35px">客户地址 :</td> <td><input type="text" id="CustAddress" style="width:500px;" /></td> </tr> <tr> <td style="height:35px;line-height:35px">客户电话 :</td> <td><input type="text" id="CustPhone" style="width:500px;" /></td> </tr> <tr> <td style="height:35px;line-height:35px">客户公司 :</td> <td><input type="text" id="CustCompany" style="width:500px;" /></td> </tr> <tr> <td style="height:35px;line-height:35px">订单建立时间 :</td> <td><input type="text" id="CreateDateTime" style="width:500px;" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" class="Wdate" /></td> </tr> <tr> <td style="height:35px;line-height:35px">订单更新时间 :</td> <td><input type="text" id="UpdateDateTime" style="width:500px;" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" class="Wdate" /></td> </tr> </table> </div> <style> #tb_SaleOrder_Add td input[type=text] { height: 35px; border-radius: 6px; border: 1px #cccccc solid; outline: none } </style>
7.Import.cshtml

1 @{ 2 ViewBag.Title = "EditForm"; 3 Layout = "~/Views/Shared/_LayoutBTSTable.cshtml"; 4 } 5 6 <script> 7 $(function () { 8 //初始化页面控件 9 $.ajax({ 10 url: "/SaleManage/InitModifySheet", 11 type: 'get', 12 contentType: 'application/json;charset=utf-8', 13 data: { 14 orderNO: GetQuery('KeyValue') 15 }, 16 success: function (data) { 17 //将回调数据转化为json对象 18 var jsonData = eval(data); 19 //遍历,为表单赋值 20 $("#OrderNO").val(jsonData[0].OrderNO); 21 $("#ProductNo").val(jsonData[0].ProductNo); 22 $("#CustName").val(jsonData[0].CustName); 23 $("#CustAddress").val(jsonData[0].CustAddress); 24 $("#CustPhone").val(jsonData[0].CustPhone); 25 $("#CustCompany").val(jsonData[0].CustCompany); 26 $("#CreateDateTime").val(jsonData[0].CreateDateTime); 27 $("#UpdateDateTime").val(jsonData[0].UpdateDateTime); 28 }, 29 error: function (data) { 30 alert('编辑数据失败' + data); 31 } 32 }) 33 }) 34 35 //添加数据 36 function AcceptClick() { 37 var OrderNO = $("#OrderNO").val(); 38 var ProductNo = $("#ProductNo").val(); 39 var CustName = $("#CustName").val(); 40 var CustAddress = $("#CustAddress").val(); 41 var CustPhone = $("#CustPhone").val(); 42 var CustCompany = $("#CustCompany").val(); 43 var CreateDateTime = $("#CreateDateTime").val(); 44 var UpdateDateTime = $("#UpdateDateTime").val(); 45 $.ajax({ 46 url: '/SaleManage/ModifyDataToDB', 47 type: 'get', 48 contentType: 'application/json;charset=utf-8', 49 data: { 50 'OrderNO': OrderNO, 'ProductNo': ProductNo, 'CustName': CustName, 51 'CustAddress': CustAddress, 'CustPhone': CustPhone, 'CustCompany': CustCompany, 52 'CreateDateTime': CreateDateTime, 'UpdateDateTime': UpdateDateTime 53 }, 54 success: function (data) { 55 reflesh(); 56 //关闭对话框 57 closeDialog(); 58 }, 59 error: function (data) { 60 alert('添加数据失败' + data); 61 } 62 }) 63 } 64 //刷新 65 function reflesh() { 66 window.parent.refleshBootStrapTable(); 67 } 68 </script> 69 70 71 72 <div class="table" style="width:100%;margin-top:10px"> 73 <table id="tb_SaleOrder_Add" class="table text-nowrap" style="text-align:right;"> 74 <tr> 75 <td style="height:35px;line-height:35px">订单编号 :</td> 76 <td><input type="text" id="OrderNO" style="width:500px;" disabled/></td> 77 </tr> 78 <tr> 79 <td style="height:35px;line-height:35px">产品名称 :</td> 80 <td><input type="text" id="ProductNo" style="width:500px;" /></td> 81 </tr> 82 <tr> 83 <td style="height:35px;line-height:35px">客户姓名 :</td> 84 <td><input type="text" id="CustName" style="width:500px;" /></td> 85 </tr> 86 <tr> 87 <td style="height:35px;line-height:35px">客户地址 :</td> 88 <td><input type="text" id="CustAddress" style="width:500px;" /></td> 89 </tr> 90 <tr> 91 <td style="height:35px;line-height:35px">客户电话 :</td> 92 <td><input type="text" id="CustPhone" style="width:500px;" /></td> 93 </tr> 94 <tr> 95 <td style="height:35px;line-height:35px">客户公司 :</td> 96 <td><input type="text" id="CustCompany" style="width:500px;" /></td> 97 </tr> 98 <tr> 99 <td style="height:35px;line-height:35px">订单建立时间 :</td> 100 <td><input type="text" id="CreateDateTime" style="width:500px;" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" class="Wdate" /></td> 101 </tr> 102 <tr> 103 <td style="height:35px;line-height:35px">订单更新时间 :</td> 104 <td><input type="text" id="UpdateDateTime" style="width:500px;" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" class="Wdate" /></td> 105 </tr> 106 </table> 107 </div> 108 109 <style> 110 #tb_SaleOrder_Add td input[type=text] { 111 height: 35px; 112 border-radius: 6px; 113 border: 1px #cccccc solid; 114 outline: none 115 } 116 </style>
8.ParentAndChild.cshtml

1 @{ 2 Layout = "~/Views/Shared/_LayoutBTSTable.cshtml"; 3 } 4 5 <!--查询条件--> 6 <div class="panel-body" style="padding-bottom:0px;width:104%;margin-left:-15px"> 7 <div class="panel panel-default"> 8 <div class="panel-heading"> 9 订单管理 10 </div> 11 <div style="margin-top:-30px;text-align:right"> 12 <a href="~/Files/ImportTemple.xlsx" style="margin-right:20px">下载导入模板 </a> 13 </div> 14 <div class="panel-body"> 15 <div style="margin-top:10px;"> 16 日期: 17 <input type="text" id="startDate" style="height:35px;width:100px;margin-left:5px;margin-top:-32px;border-radius: 6px;border: 1px #cccccc solid; outline: none" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd'})"> 18 — 19 <input type="text" id="endDate" style="height:35px;width:100px;margin-left:8px;margin-top:-34px;border-radius: 6px;border: 1px #cccccc solid; outline: none" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd'})"> 20 订单编号:<input type="text" id="queryKey" placeholder="请输入订单编号进行查询" style="height:35px;width:170px;margin-left:10px;margin-top:-34px;border-radius: 6px;border: 1px #cccccc solid; outline: none"> 21 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_query" class="btn btn-success">查询</button> 22 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_add" class="btn btn-info">添加</button> 23 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_edit" class="btn btn-warning">编辑</button> 24 <button type="button" style="width:70px;height:35px;margin-left:20px;margin-top:-3px" id="btn_delete" class="btn btn-danger">删除</button> 25 </div> 26 </div> 27 </div> 28 </div> 29 <!--初始化bootstrap-table--> 30 <div style="margin-bottom:-40px;color:red">注释:父子表</div> 31 <table id="tb_SaleOrder" class="table"></table> 32 33 <style> 34 #tb_SaleOrder > thead th { 35 padding: 0; 36 margin: 0; 37 background-color: #d9edf7; 38 } 39 </style> 40 <script> 41 //刷新bootstrap-table 42 function refleshBootStrapTable() { 43 $("#tb_SaleOrder").bootstrapTable('refresh'); 44 } 45 </script> 46 47 <script src="~/CustomUI/TableJS/ParentChild.js"></script>
9.布局页 _LayoutBTSTable.cshtml

1 <!DOCTYPE html> 2 3 <html> 4 <head> 5 <meta name="viewport" content="width=device-width" /> 6 <link href="~/CustomUI/bootstrap/css/bootstrap.css" rel="stylesheet" /> 7 <link href="~/CustomUI/bootstrapTable/bootstrap-table.css" rel="stylesheet" /> 8 <link href="~/CustomUI/skin/WdatePicker.css" rel="stylesheet" /> 9 <script src="~/CustomUI/jquery/jquery-3.3.1.js"></script> 10 <script src="~/CustomUI/lhgdialog/lhgdialog.min.js"></script> 11 <script src="~/CustomUI/bootstrap/js/bootstrap.js"></script> 12 <script src="~/CustomUI/bootstrapTable/bootstrap-table.js"></script> 13 <script src="~/CustomUI/bootstrapTable/tableExport.js"></script> 14 <script src="~/CustomUI/bootstrapTable/bootstrap-table-export.js"></script> 15 <script src="~/CustomUI/bootstrapTable/bootstrap-table-zh-CN.js"></script> 16 <script src="~/CustomUI/datepicker/WdatePicker.js"></script> 17 </head> 18 <body> 19 <div> 20 @RenderBody() 21 </div> 22 </body> 23 </html> 24 25 <script src="~/CustomUI/TableJS/DialogTemple.js"></script>
10.ImportExcelToDB.cs

1 using System; 2 using System.Collections.Generic; 3 using System.Configuration; 4 using System.Data; 5 using System.Data.OleDb; 6 using System.Data.SqlClient; 7 using System.Linq; 8 using System.Text; 9 using System.Web; 10 11 namespace BTStrapTB.Common 12 { 13 public class ImportExcelToDB 14 { 15 //全局数据库链接字符串 16 private readonly string strConnection = ConfigurationManager.ConnectionStrings["conStr"].ConnectionString; 17 18 //从Excel读取数据 19 public static DataSet ReadExcelDataToTable(string filepath) 20 { 21 try 22 { 23 int index1 = filepath.LastIndexOf("\\"); 24 int index2 = filepath.LastIndexOf("."); 25 string fileName ="["+filepath.Substring(index1+1,index2-index1-1)+"$]"; 26 string strConn = string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 8.0;HDR=Yes;IMEX=1;'", filepath); 27 using (OleDbConnection oleConn = new OleDbConnection(strConn)) 28 { 29 oleConn.Open(); 30 string sql = "select * from "+fileName+ ""; 31 OleDbDataAdapter oleDaExcel = new OleDbDataAdapter(sql, oleConn); 32 DataSet oleDsExcel = new DataSet(); 33 oleDaExcel.Fill(oleDsExcel, "table1"); 34 return oleDsExcel; 35 } 36 } 37 catch (Exception ex) 38 { 39 throw ex; 40 } 41 } 42 public void InsertExcelDataToDB(string fileName) 43 { 44 //导入表格格式化SQL 45 string sqlText = @"INSERT INTO [dbo].[SaleOrder] 46 ([OrderNO] 47 ,[ProductNo] 48 ,[CustName] 49 ,[CustAddress] 50 ,[CustPhone] 51 ,[CustCompany] 52 ,[CreateDateTime] 53 ,[UpDateDateTime]) 54 VALUES 55 ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}')"; 56 57 if (!System.IO.File.Exists(fileName)) 58 { 59 throw new Exception("指定路径的Excel文件不存在!"); 60 } 61 DataSet ds = ReadExcelDataToTable(fileName); 62 DataTable dt = ds.Tables[0]; 63 //将excel数据插入到DB以前,先判断DB中是否存在数据 64 DelDBRepeatData(dt); 65 List<string> list = (from DataRow row in dt.Rows 66 select String.Format(sqlText, row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])).ToList(); 67 OperateDB(list); 68 } 69 70 /* 71 将excel数据插入到DB以前, 72 先判断DB中是否存在同一天同一员工记录 73 */ 74 public int DelDBRepeatData(DataTable dt) 75 { 76 //sql脚本 77 string delSqlText = @"DELETE FROM [dbo].[SaleOrder] 78 WHERE OrderNO IN ('{0}') 79 "; 80 81 //取excel中的员工号和打卡日期 82 StringBuilder strBld = new StringBuilder(); 83 84 for (int i = 0; i < dt.Rows.Count; i++) 85 { 86 strBld.Append(dt.Rows[i][0]); 87 88 } 89 90 List<string> list = (from DataRow row in dt.Rows 91 select String.Format(delSqlText, row[0])).ToList(); 92 93 OperateDB(list); 94 return 0; 95 } 96 97 //DB操做 98 public int OperateDB(List<string> list) 99 { 100 int result = 0; 101 using (SqlConnection conn = new SqlConnection(strConnection)) 102 { 103 if (conn.State==ConnectionState.Closed) 104 { 105 conn.Open(); 106 } 107 foreach (string item in list) 108 { 109 SqlCommand cmd = new SqlCommand(item, conn); 110 result=cmd.ExecuteNonQuery(); 111 } 112 } 113 return result; 114 } 115 } 116 }
12.ConvertHelpers.cs

1 using Newtonsoft.Json; 2 using System; 3 using System.Collections.Generic; 4 using System.Data; 5 using System.Linq; 6 using System.Reflection; 7 using System.Web; 8 9 namespace BTStrapTB.Common 10 { 11 /// <summary> 12 /// 转换Json格式帮助类 13 /// </summary> 14 public static class JsonHelper 15 { 16 public static object ToJson(this string Json) 17 { 18 return JsonConvert.DeserializeObject(Json); 19 } 20 public static string ToJson(this object obj) 21 { 22 return JsonConvert.SerializeObject(obj); 23 } 24 public static List<T> JonsToList<T>(this string Json) 25 { 26 return JsonConvert.DeserializeObject<List<T>>(Json); 27 } 28 public static T JsonToEntity<T>(this string Json) 29 { 30 return JsonConvert.DeserializeObject<T>(Json); 31 } 32 } 33 }
13.SaleManageController

1 using BTStrapTB.BLL; 2 using BTStrapTB.Common; 3 using BTStrapTB.Models; 4 using System; 5 using System.Collections.Generic; 6 using System.IO; 7 using System.Linq; 8 using System.Web; 9 using System.Web.Mvc; 10 11 namespace BTStrapTB.Controllers 12 { 13 //销售管理 14 public class SaleManageController : BaseManageController 15 { 16 ImportExcelToDB ImportToExcl = new ImportExcelToDB(); 17 SaleOrderBLL SOBLL = new SaleOrderBLL(); 18 SaleProductBLL SPBLL = new SaleProductBLL(); 19 public override ActionResult Index() 20 { 21 return View(); 22 } 23 //导入页面 24 public ActionResult Import() 25 { 26 return View(); 27 } 28 29 //将Excel订单数据导入 30 [HttpPost] 31 public ActionResult ImportExclToDB(HttpPostedFileBase file) 32 { 33 var severPath = this.Server.MapPath("/Files"); //获取当前虚拟文件路径 34 var savePath = Path.Combine(severPath, file.FileName); //拼接保存文件路径 35 file.SaveAs(savePath); 36 try 37 { 38 ImportToExcl.InsertExcelDataToDB(savePath); 39 return Content("<script>alert('上传成功!!')</script>"); 40 } 41 catch (Exception ex) 42 { 43 throw new Exception(ex.Message); 44 } 45 46 //Response.Redirect("/PunchCardRecord/Index"); 47 } 48 49 //父子页面 50 public ActionResult ParentAndChild() 51 { 52 return View(); 53 } 54 55 //获取子表数据 56 public ActionResult GetChildDataList(int limit, int offset,string productNo) 57 { 58 List<SaleProduct> list = SPBLL.GetProductOrderList(productNo); 59 int total = list.Count; 60 var rows = list.Skip(offset).Take(limit).ToList(); 61 return Json(new { total, rows }, JsonRequestBehavior.AllowGet); 62 } 63 64 //获取订单列表 65 public ActionResult GetOrderList(int limit, int offset,string startDate,string endDate,string orderNO) 66 { 67 List<SaleOrder> list = SOBLL.GetSaleOrderList(startDate,endDate, orderNO); 68 int total = list.Count; 69 var rows = list.Skip(offset).Take(limit).ToList(); 70 return Json(new { total, rows }, JsonRequestBehavior.AllowGet); 71 } 72 //删除数据 73 public void DelOrder(string orderNo) 74 { 75 SOBLL.DelDataToDB(orderNo); 76 } 77 //添加数据 78 public void AddDataToDB(SaleOrder saleOrder) 79 { 80 SOBLL.AddDataToDB(saleOrder); 81 } 82 //初始化修改表单 83 public ActionResult InitModifySheet(string orderNO) 84 { 85 List<SaleOrder> list = SOBLL.GetSaleOrderList("", "", orderNO); 86 return Content(list.ToJson()); 87 } 88 //修改数据 89 public void ModifyDataToDB(SaleOrder saleOrder) 90 { 91 SOBLL.ModifyDataToDB(saleOrder); 92 } 93 } 94 }
14.父子表JS

1 //初始化 2 var InitTable = function (url) { 3 //先销毁表格 4 $('#tb_SaleOrder').bootstrapTable("destroy"); 5 //加载表格 6 $('#tb_SaleOrder').bootstrapTable({ 7 rowStyle: function (row, index) {//row 表示行数据,object,index为行索引,从0开始 8 var style = ""; 9 if (row.SignInTime == '' || row.SignOutTime == '') { 10 style = { css: { 'color': 'red' } }; 11 } 12 return style; 13 }, 14 //searchAlign: 'left', 15 //search: true, //显示隐藏搜索框 16 showHeader: true, //是否显示列头 17 //classes: 'table-no-bordered', 18 showLoading: true, 19 undefinedText: '', 20 showFullscreen: true, 21 toolbarAlign: 'left', 22 paginationHAlign: 'right', 23 silent: true, 24 url: url, 25 method: 'get', //请求方式(*) 26 toolbar: '#toolbar', //工具按钮用哪一个容器 27 striped: true, //是否显示行间隔色 28 cache: false, //是否使用缓存,默认为true,因此通常状况下须要设置一下这个属性(*) 29 pagination: true, //是否显示分页(*) 30 sortable: false, //是否启用排序 31 sortOrder: "asc", //排序方式 32 //queryParams: InitTable.queryParams, //传递参数(*) 33 sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) 34 pageNumber: 1, //初始化加载第一页,默认第一页 35 pageSize: 10, //每页的记录行数(*) 36 pageList: [2, 5, 10, 15], //可供选择的每页的行数(*) 37 search: false, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,因此,我的感受意义不大 38 strictSearch: true, 39 showColumns: true, //是否显示全部的列 40 showRefresh: true, //是否显示刷新按钮 41 minimumCountColumns: 2, //最少容许的列数 42 clickToSelect: true, //是否启用点击选中行 43 //height: 680, //行高,若是没有设置height属性,表格自动根据记录条数以为表格高度 44 uniqueId: "ID", //每一行的惟一标识,通常为主键列 45 showToggle: true, //是否显示详细视图和列表视图的切换按钮 46 cardView: false, //是否显示详细视图 47 detailView: true, //是否显示父子表 48 showExport: true, 49 //exportDataType: 'all', 50 exportDataType: "selected", //导出checkbox选中的行数 51 paginationLoop: false, //是否无限循环 52 columns: [{ 53 checkbox: true 54 }, { 55 field: 'OrderNO', 56 title: '订单编号' 57 }, { 58 field: 'ProductNo', 59 title: '产品编号' 60 }, { 61 field: 'CustName', 62 title: '客户姓名' 63 }, { 64 field: 'CustAddress', 65 title: '客户地址', 66 }, { 67 field: 'CustPhone', 68 title: '客户电话', 69 }, { 70 field: 'CustCompany', 71 title: '客户公司', 72 }, { 73 field: 'CreateDateTime', 74 title: '订单建立时间', 75 }, { 76 field: 'UpdateDateTime', 77 title: '订单更新时间', 78 }], 79 80 //无限循环取子表,直到子表里面没有记录 81 onExpandRow: function (index, row, $Subdetail) { 82 InitSubTable(index, row, $Subdetail); 83 } 84 }); 85 return InitTable; 86 87 88 }; 89 90 //初始化子表格(无线循环) 91 InitSubTable = function (index, row, $detail) { 92 var parentid = row.ProductNo; 93 var cur_table = $detail.html('<table></table>').find('table'); 94 $(cur_table).bootstrapTable({ 95 url: "/SaleManage/GetChildDataList", 96 method: 'get', 97 queryParams: { 'limit':10000,'offset':0,'productNo':parentid}, 98 clickToSelect: true, 99 detailView: false,//父子表 100 sidePagination: "server", 101 uniqueId: "ProductNo", 102 pageSize: 10, 103 pageList: [10, 25], 104 columns: [{ 105 field: 'ProductNo', 106 title: '产品编号' 107 }, 108 { 109 field: 'ProductName', 110 title: '产品名称' 111 }, { 112 field: 'ProductType', 113 title: '产品类型' 114 }, { 115 field: 'ProductCount', 116 title: '产品数量' 117 }, 118 { 119 field: 'ProductPrice', 120 title: '产品单价' 121 }], 122 //无限循环取子表,直到子表里面没有记录 123 onExpandRow: function (index, row, $Subdetail) { 124 InitSubTable(index, row, $Subdetail); 125 } 126 }); 127 };
(三)其余技术点
1.改变bootstrap-table表头颜色
1 #tb_SaleOrder > thead th { 2 padding: 0; 3 margin: 0; 4 background-color: #d9edf7; 5 }
2.改变bootstrap-table 光标悬停颜色
1 #tb_SaleOrder tbody > tr:hover { 2 background-color: #449d44; 3 }
3.刷新bootstrap-table
1 //刷新bootstrap-table 2 function refleshBootStrapTable() { 3 $("#tb_SaleOrder").bootstrapTable('refresh'); 4 }
4.弹窗
1 /* 2 弹出对话框(带:确认按钮、取消按钮) 3 */ 4 function openDialog(url, _id, _title, _width, _height, callBack) { 5 Loading(true); 6 top.$.dialog({ 7 id: _id, 8 width: _width, 9 height: _height, 10 max: false, 11 lock: true, 12 title: _title, 13 resize: false, 14 extendDrag: true, 15 content: 'url:' + RootPath() + url, 16 ok: function () { 17 callBack(_id); 18 return false; 19 }, 20 cancel: true 21 }); 22 }
5.Bootstrap-table核心技术点,再次强调
1 var InitTable = function (url) { 2 //先销毁表格 3 $('#tb_SaleOrder').bootstrapTable("destroy"); 4 //加载表格 5 $('#tb_SaleOrder').bootstrapTable({ 6 rowStyle: function (row, index) {//row 表示行数据,object,index为行索引,从0开始 7 var style = ""; 8 if (row.SignInTime == '' || row.SignOutTime=='') { 9 style = { css: { 'color': 'red' } }; 10 } 11 return style; 12 }, 13 //searchAlign: 'left', 14 //search: true, //显示隐藏搜索框 15 showHeader: true, //是否显示列头 16 //classes: 'table-no-bordered', 17 showLoading: true, 18 undefinedText: '', 19 showFullscreen: true, 20 toolbarAlign: 'left', 21 paginationHAlign: 'right', 22 silent: true, 23 url: url, 24 method: 'get', //请求方式(*) 25 toolbar: '#toolbar', //工具按钮用哪一个容器 26 striped: true, //是否显示行间隔色 27 cache: false, //是否使用缓存,默认为true,因此通常状况下须要设置一下这个属性(*) 28 pagination: true, //是否显示分页(*) 29 sortable: false, //是否启用排序 30 sortOrder: "asc", //排序方式 31 //queryParams: InitTable.queryParams, //传递参数(*) 32 sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) 33 pageNumber: 1, //初始化加载第一页,默认第一页 34 pageSize: 10, //每页的记录行数(*) 35 pageList: [2, 5, 10, 15], //可供选择的每页的行数(*) 36 search: false, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,因此,我的感受意义不大 37 strictSearch: true, 38 showColumns: true, //是否显示全部的列 39 showRefresh: true, //是否显示刷新按钮 40 minimumCountColumns: 2, //最少容许的列数 41 clickToSelect: true, //是否启用点击选中行 42 //height: 680, //行高,若是没有设置height属性,表格自动根据记录条数以为表格高度 43 uniqueId: "ID", //每一行的惟一标识,通常为主键列 44 showToggle: true, //是否显示详细视图和列表视图的切换按钮 45 cardView: false, //是否显示详细视图 46 detailView: false, //是否显示父子表 47 showExport: true, 48 //exportDataType: 'all', 49 exportDataType: "selected", //导出checkbox选中的行数 50 paginationLoop: false, //是否无限循环 51 columns: [{ 52 checkbox: true 53 }, { 54 field: 'OrderNO', 55 title: '订单编号' 56 }, { 57 field: 'ProductNo', 58 title: '产品编号' 59 }, { 60 field: 'CustName', 61 title: '客户姓名' 62 }, { 63 field: 'CustAddress', 64 title: '客户地址', 65 }, { 66 field: 'CustPhone', 67 title: '客户电话', 68 }, { 69 field: 'CustCompany', 70 title: '客户公司', 71 }, { 72 field: 'CreateDateTime', 73 title: '订单建立时间', 74 }, { 75 field: 'UpdateDateTime', 76 title: '订单更新时间', 77 }] 78 }); 79 return InitTable; 80 };
四 写在最后
本片文章借助于bootstrap-table插件,实现了基本的增删改查,导入导出,分页,父子表等。至于其余技术,如冻结表头,列排列,行拖动,列拖动等,会在后续文章中与你们分享。
AutoFac
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace FB.CMS.MvcSite.App_Start { using Autofac; using Autofac.Integration.Mvc; using System.Reflection; using System.Web.Mvc; /// <summary> /// 这个类是我本身定义的一个类,主要用初始化AutoFac容器的相关数据 /// </summary> public class AutoFacConfig { public static void Register() { //初始化AutoFac的相关功能 /* 1.0 告诉AutoFac初始化数据仓储层FB.CMS.Repository.dll中全部类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中 2.0 告诉AutoFac初始化业务逻辑层FB.CMS.Services.dll中全部类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中 3.0 将MVC默认的控制器工厂替换成AutoFac的工厂 */ //第一步: 构造一个AutoFac的builder容器 ContainerBuilder builder = new Autofac.ContainerBuilder(); //第二步:告诉AutoFac控制器工厂,控制器类的建立去哪些程序集中查找(默认控制器工厂是去扫描bin目录下的全部程序集) //2.1 从当前运行的bin目录下加载FB.CMS.MvcSite.dll程序集 Assembly controllerAss = Assembly.Load("FB.CMS.MvcSite"); //2.2 告诉AutoFac控制器工厂,控制器的建立从controllerAss中查找(注意:RegisterControllers()方法是一个可变参数,若是你的控制器类的建立须要去多个程序集中查找的话,
那么咱们就再用Assembly controllerBss=Assembly.Load("须要的程序集名")加载须要的程序集,而后与controllerAss组成数组,而后将这个数组传递到RegisterControllers()方法中) builder.RegisterControllers(controllerAss); //第三步:告诉AutoFac容器,建立项目中的指定类的对象实例,以接口的形式存储(其实就是建立数据仓储层与业务逻辑层这两个程序集中全部类的对象实例,而后以其接口的形式保存到AutoFac容器内存中,
固然若是有须要也能够建立其余程序集的全部类的对象实例,这个只须要咱们指定就能够了) //3.1 加载数据仓储层FB.CMS.Repository这个程序集。 Assembly repositoryAss = Assembly.Load("FB.CMS.Repository"); //3.2 反射扫描这个FB.CMS.Repository.dll程序集中全部的类,获得这个程序集中全部类的集合。 Type[] rtypes = repositoryAss.GetTypes(); //3.3 告诉AutoFac容器,建立rtypes这个集合中全部类的对象实例 builder.RegisterTypes(rtypes) .AsImplementedInterfaces(); //指明建立的rtypes这个集合中全部类的对象实例,以其接口的形式保存 //3.4 加载业务逻辑层FB.CMS.Services这个程序集。 Assembly servicesAss = Assembly.Load("FB.CMS.Services"); //3.5 反射扫描这个FB.CMS.Services.dll程序集中全部的类,获得这个程序集中全部类的集合。 Type[] stypes = servicesAss.GetTypes(); //3.6 告诉AutoFac容器,建立stypes这个集合中全部类的对象实例 builder.RegisterTypes(stypes) .AsImplementedInterfaces(); //指明建立的stypes这个集合中全部类的对象实例,以其接口的形式保存 //第四步:建立一个真正的AutoFac的工做容器 var container = builder.Build(); //咱们已经建立了指定程序集的全部类的对象实例,并以其接口的形式保存在AutoFac容器内存中了。那么咱们怎么去拿它呢? //从AutoFac容器内部根据指定的接口获取其实现类的对象实例 //假设我要拿到IsysFunctionServices这个接口的实现类的对象实例,怎么拿呢? //var obj = container.Resolve<IsysFunctionServices>(); //只有有特殊需求的时候能够经过这样的形式来拿。通常状况下没有必要这样来拿,由于AutoFac会自动工做
(即:会自动去类的带参数的构造函数中找与容器中key一致的参数类型,并将对象注入到类中,其实就是将对象赋值给构造函数的参数) //第五步:将当前容器中的控制器工厂替换掉MVC默认的控制器工厂。(即:不要MVC默认的控制器工厂了,用AutoFac容器中的控制器工厂替代)此处使用的是将AutoFac工做容器交给MVC底层 (须要using System.Web.Mvc;) DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); //咱们知道控制器的建立是调用MVC默认的控制器工厂,默认的控制器工厂是调用控制器类的无参构造函数 //但是咱们若是要使用AutoFac自动工厂,将对象经过构造函数注入类中,那么这个构造函数就须要带参数 //若是咱们将控制器的无参构造函数删除,保留带参数的构造函数,MVC默认的控制器工厂来建立控制的时候 //就会去调用无参的构造函数,但是这时候发现没有无参的构造函数因而就报“没有为该对象定义无参数的构造函数”错误 //既然报错,那咱们若是保留无参的构造函数,同时在声明一个带参数的构造函数是否可行呢? //答案;行是行,可是建立控制器的时候,MVC默认的控制器工厂调用的是无参构造函数,它并不会去调用有参的构造函数 //这时候,咱们就只能将AutoFac它的控制器工厂替换调用MVC默认的控制器工厂(控制器由AutoFac的控制器工厂来建立) //而AutoFac控制器工厂在建立控制的时候只会扫描带参数的构造函数,并将对象注入到带参数的构造函数中 //AutofacDependencyResolver这个控制器工厂是继承了 IDependencyResolver接口的,而IDependencyResolver接口是MVC的东西 //MVC默认的控制器工厂名字叫:DefaultControllerFactory //具体参考:http://www.cnblogs.com/artech/archive/2012/04/01/controller-activation-032.html } } }
using FB.CMS.MvcSite.App_Start; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace FB.CMS.MvcSite { // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, // 请访问 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //第一: 在网站一启动的时候就初始化AutoFac的相关功能 /* 1.0 告诉AutoFac初始化数据仓储层FB.CMS.Repository.dll中全部类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中 2.0 告诉AutoFac初始化业务逻辑层FB.CMS.Services.dll中全部类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中 3.0 将MVC默认的控制器工厂替换成AutoFac的工厂 */ //具体作法就是咱们去App_Start文件夹下建立一个AutoFacConfig类,具体实现什么功能去这个类中实现。而后再这里调用下这个类 AutoFacConfig.Register(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace FB.CMS.MvcSite.Controllers { using FB.CMS.IServices; public class HomeController : Controller { IsysFunctionServices dal; public HomeController(IsysFunctionServices dal) //依赖构造函数进行对象注入 { this.dal = dal; //在构造函数中初始化HomeController控制器类的dal属性 (这个dal属性的类型是IsysFunctionServices) } public ActionResult Index() { var a = dal.QueryWhere(r => r.fID > 20).ToList(); //查询 return View(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FB.CMS.Repository { using FB.CMS.IRepository; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq.Expressions; using System.Runtime.Remoting.Messaging; using System.Threading; public class BaseDal<TEntity> : IBaseDal<TEntity> where TEntity : class { //BaseDbContext db = new BaseDbContext(); //对建立上下文容器类对象进行优化(原理:一个线程下咱们只建立一个上下文容器类对象,而后保存到线程缓存当中去,当同一个线程过来的时候,就从线程缓存当中取上下文容器类对象) public BaseDbContext db { get { //获取BaseDbContext的彻底限定名,其实这个名字没什么特别的意义,仅仅是一个名字而已,也能够取别的名字的 string threadName = typeof(BaseDbContext).FullName; //获取key为threadName的这个线程缓存(CallContext就是线程缓存容器类) object dbObj = CallContext.GetData(threadName); //若是key为threadName的线程缓存不存在 if (dbObj == null) { //建立BaseDbContext类的对象实例 dbObj = new BaseDbContext(); //将这个BaseDbContext类的对象实例保存到线程缓存当中(以键值对的形式进行保存的,我这就将key设为当前线程的彻底限定名了) CallContext.SetData(threadName, dbObj); return dbObj as BaseDbContext; } return dbObj as BaseDbContext; } } DbSet<TEntity> _dbset; public BaseDal() { this._dbset = db.Set<TEntity>(); //初始化 } #region 增长 public void AddEnity(TEntity model) { if (model == null) { throw new Exception("moddel不能为null"); } this._dbset.Add(model); } #endregion #region 物理删除 /// <summary> /// 删除 /// </summary> /// <param name="model">实体类</param> /// <param name="isaddedContext">是否物理删除</param> public void DeleteEntity(TEntity model, bool isaddedContext) { if (model == null) { throw new Exception("DeleteEntity方法中的model不能为null"); } //若是仅仅是逻辑删除的话,那咱们只要调用编辑方法将标识为逻辑删除的那个字段修改成true就能够了。 if (isaddedContext == true) { this._dbset.Attach(model); } this._dbset.Remove(model); } #endregion #region 查寻 /// <summary> /// 普通带条件查询 /// </summary> /// <param name="where"></param> /// <returns></returns> public IQueryable<TEntity> QueryWhere(Expression<Func<TEntity, bool>> where) { return this._dbset.Where(where); } /// <summary> /// 连表查询 /// </summary> /// <param name="where">连表查询的条件筛选查询</param> /// <param name="tablesName">要作连表查询的全部表名集合</param> /// <returns></returns> public IQueryable<TEntity> QueryJoin(Expression<Func<TEntity, bool>> where, string[] tablesName) { if (tablesName == null || tablesName.Any() == false) { throw new Exception("连表查询最少也要一个表,全部QueryJoin方法中tablesName中最少也须要有一个表名"); } DbQuery<TEntity> query = this._dbset; foreach (string tableName in tablesName) { //不断的连表,直到把tablesName里的全部表都连完 query = query.Include(tableName); } return query.Where(where); //而后对连表进行条件筛选查询 } /// <summary> /// 带条件的分页查询 /// </summary> /// <typeparam name="TKey">按哪一个字段进行排序</typeparam> /// <param name="pageindex">当前页</param> /// <param name="pagesize">页大小</param> /// <param name="rowCount">数据总条数</param> /// <param name="order">排序</param> /// <param name="where">筛选条件</param> /// <returns></returns> public IQueryable<TEntity> QueryByPage<TKey>(int pageindex, int pagesize, out int rowCount, Expression<Func<TEntity, TKey>> order, Expression<Func<TEntity, bool>> where) { //获取总条数 rowCount = this._dbset.Count(where); //建议将这个Where条件语句放在前面,若是你放到后面,分页的时候可能存在问题。 return this._dbset.Where(where).OrderByDescending(order).Skip((pageindex - 1) * pagesize).Take(pagesize); } /// <summary> /// 调用存储过程或执行SQL语句(可是咱们不推荐执行sql语句) /// </summary> /// <typeparam name="TElement"> /// 由于存储过程返回的数据不必定就是TEntity这个实体,由于存储过返回的结果集有多是本身拼接出来的,因此这个方法的返回结果 /// 为Lsit<TEntity>就不合适了。 这个 TElement是在调用的存储过程的时候传入的一个实体,此实体必须和调用的存储过程的返回结集 /// 中的字段名称保存一致(你这个存储过程返回有多个字段,那么你这个实体中就应该有多少个属性) /// </typeparam> /// <param name="sql"> /// 假设我建立了这么一个存储过程: /// create proc proc_T_UserInfo_Paging2(@pageSize int,@currentPage int,@CountData ) /// 那如今咱们调用这个存储过程,那么这个SQL语句的写法就是: /// proc_T_UserInfo_Paging2 @pageSize int,@currentPage int,@CountData /// /// </param> /// <param name="prms">参数化查询的参数数组</param> /// <returns></returns> public List<TElement> RunProc<TElement>(string sql, params object[] prms) { return db.Database.SqlQuery<TElement>(sql, prms).ToList(); } #endregion #region 编辑 /// <summary> /// 编辑 /// </summary> /// <param name="model">实体</param> /// <param name="propertyNames">要编辑的的全部属性名称序列</param> public void EditEntity(TEntity model, string[] propertyNames) { if (model == null) { throw new Exception("EditEntity方法中的参数model不能为null"); } if (propertyNames.Any() == false || propertyNames == null) { throw new Exception("EditEntity方法中的参数propertyNames最少须要一个属性"); } System.Data.Entity.Infrastructure.DbEntityEntry entry = db.Entry(model); entry.State = System.Data.EntityState.Unchanged; foreach (string item in propertyNames) { entry.Property(item).IsModified = true; } db.Configuration.ValidateOnSaveEnabled = false; } #endregion #region 统一保存 public int SaveChanges() { return db.SaveChanges(); } #endregion } }
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace FB.CMS.Site.Controllers { using FB.CMS.IRepository; public class HomeController : Controller { IsysFunctionRepository fundal; IsysKeyValueRepository kvdal; //咱们在经过AutoFac将IsysFunctionRepository和IsysKeyValueRepository的两个实现类的对象实例注入到当前构造函数中 //那么在注入IsysFunctionRepository这实现类的对象的时候就会执行到sysFunctionRepository类,而这个sysFunctionRepository类 //又是继承了BaseDal类,而后就会去执行BaseDal类的构造函数,而在BaseDal这个类的构造函数中咱们执行了 //this._dbset = db.Set<TEntity>()这段代码,这段代码中使用了db这个上下文容器类对象。既然使用了这个对象,因此就会首先初始化这个对象 //按照日常的写法,通常都是直接new一个上下文容器对象,即:BaseDbContext db = new BaseDbContext(); //而后当咱们再对构造函数的参数IsysKeyValueRepository kvdal进行对象注入的时候,一样的原理又会执行一次 //BaseDbContext db = new BaseDbContext();即又建立了一个上下文容器类对象。因此说当咱们经过构造函数一次性注入多个对象的时候 //会建立多个上下文容器类对象。因此这样的作法是很是很差的。由于咱们若是在同一个线程里面用两个上下文容器类对象分别去对数据增删改 //的后,【最后统一执行db.SaveChanges()】;这时候会发现只有后面的那个上下文容器类对象对数据的增删改操做成功,而前面的那个上下文容器类对象对数据的增删 public HomeController(IsysFunctionRepository fundal, IsysKeyValueRepository kvdal) { this.fundal = fundal; this.kvdal = kvdal; } public ActionResult Index() { //var model = fundal.QueryWhere(r => r.fID == 20).FirstOrDefault(); //model.fName = "默认888"; //fundal.SaveChanges(); //执行保存修改其实就已经操做一次数据库了 //var model2 = kvdal.QueryWhere(r => r.KID == 3).FirstOrDefault(); //model2.KName = "公司88"; //fundal.SaveChanges(); //这里又执行一次保存修改操做,又执行了一个数据库, //那咱们就但愿在同一个控制器中(同一个控制中意味着在同一个线程当中)无论操做多少个上下文容器类对象对数据的操做 //咱们只但愿最后只执行已给SaveChanges(),这样就能保障最少次的对数据库的操做。从而提升性能 //那咱们怎么作呢?其实很简单,当咱们执行构造函数对多个对象进行注入的时候,咱们保证只有一个上下文容器类对象实例 //当咱们在Home控制器中经过构造函数注入两个对象,那么这个Home控制器会有一个线程进行管理,那么当这个线程过来的时候 //咱们就建立先去一个线程缓存中去获取一下这个线程名对应的缓存,若是缓存不存在,那么咱们就建立一个上下文容器类对象实例 //并将这个对象实例存储到线程缓存当中,而后返回这个上下文容器类对象。若是缓存存在,那么就直接获取这个缓存进行返回 //这样就保证了同一线程下,只有个上下文容器类实例对象,这样咱们就能够用任何上下文容器类对象对数据的增删改后,最后只 //须要用任何一个上下文容器类对象执行一次SaveChanges()就能够了 var model = fundal.QueryWhere(r => r.fID == 20).FirstOrDefault(); model.fName = "默认888"; //fundal.SaveChanges(); //执行保存修改其实就已经操做一次数据库了 var model2 = kvdal.QueryWhere(r => r.KID == 3).FirstOrDefault(); model2.KName = "公司88"; fundal.SaveChanges(); //经过对BaseDal中对建立上下文容器类的优化后,咱们看到在同一个线程中 两个上下文容器类对象(经过优化后其实这个两个上下文容器类对象实际上是同一个上下文容器类对象)
同时对数据进行改的操做,最后只执行了一个fundal.SaveChanges(); 方法,就将数据成功保存了。这样就达到了最少操做数据库。性能提高了 return View(model); } } }
public ActionResult Index() { ContainerBuilder builder = new ContainerBuilder(); builder.RegisterType<UserInfoSevices>(); //想拿到UserInfoSevices类的实例 builder.RegisterType<UserInfoRepository>().As<IUserInfoRepository>(); //与之关联的UserInfoRepository类也须要拿到,并转化成接口的形式保存 var aa = builder.Build().Resolve<UserInfoSevices>().QueryModel(r => r.Age > 0); //在这里使用 return View(); }
C# MVC 基于From的身份验证
event 和delegate的分别
忽然想起delegate委托是支持+= 和-=操做的,而后研究一下究竟这个是怎么作到的,好模仿一下。一开始觉得是+=的运算符重载,可是在类库参考中并无这个运算符重载,只有!= 和==运算符重载。有点纳闷,最终发现,原来+=这些直接就是语法层面的实现,只是针对delegate才有的福利,因此也不存在模仿的可能性了。
顺便就总结一下event事件和delegate委托的区别。这个区别有点相似字段和属性的不一样。好比接口能够有属性,可是不能有字段。为何?由于属性本质是方法,对字段进行了包装,这种包装有一点语法上的支持,在不一样上下文下,会有不一样的解释意义。
委托本质上是函数指针,是一个存储函数地址的变量,它很灵活,强大,这和字段同样,若是人们想要限制外部环境直接访问和控制委托,那么就能够经过event来达到这个目的。
固然,事件对委托的包装,添加了一层语义,那就是为了实现“事件模式”,若是你不想实现事件模式,那么就应该用普通函数来自定义这层包装。通常来讲,事件的套路很适合程序员的须要。
delegate | event |
赋值= 取值= 添加回调函数+= 移除回调函数-= 调用() |
添加处理函数+= 移除处理函数-= (若是没有单独定义事件的委托,在内部,事件能够等同委托来用) |
由于委托能够绑定多个函数,那么它的返回值到底是哪一个?返回值是它绑定的最后一个回调函数。
常见的异步方式async 和 await
以前研究过c#的async和await关键字,幕后干了什么,可是不知道为何找不到相关资料了。如今从新研究一遍,顺便记录下来,方便之后查阅。
基础知识
async 关键字标注一个方法,该方法返回值是一个Task、或者Task<TResult>、void、包含GetAwaiter方法的类型。该方法一般包含一个await表达式。该表达式标注一个点,将被某个异步方法回跳到该点。而且,当前函数执行到该点,将马上返回控制权给调用方。
以上描述了async方法想干的事情,至于如何实现,这里就不涉猎了。
我的看法
由此能够知道,async 和await关键字主要目的是为了控制异步线程的同步,让一个异步过程,表现得好像同步过程同样。
好比async 方法分n个任务去下载网页并进行处理:先await下载,而后马上返回调用方,以后的处理就由异步线程完成下载后调用。这时候调用方能够继续执行它的任务,不过,若是调用方马上就须要async的结果,那么应该就只能等待,不过大多数状况:他暂时不须要这个结果,那么就能够并行处理这些代码。
可见,并行性体如今await 上,若是await 点和最终的数据结果距离越远,那么并行度就越高。若是await的点越多,相信也会改善并行性。
资料显示,async 和await 关键字并不会建立线程,这是很关键的一点。他们只是建立了一个返回点,提供给须要他的线程使用。那么线程到底是谁建立?注意await 表达式的组成,他须要一个Task,一个Task并不表明必定要建立线程,也能够是另外一个async方法,可是层层包裹最里面的方法,极可能就是一个原生的Task,好比await Task.Run(()=>Thread.Sleep(0)); ,这个真正产生线程的语句,就会根据前面那些await点,逐个回调。
从这点来看,async 方法,未必就是一个异步方法,他在语义上更加贴近“非阻塞”, 当遇到阻塞操做,马上用await定点返回,至于其余更深一层的解决手段,它就不关心了。这是程序员须要关心的,程序员须要用真正的建立线程代码,来完成异步操做(固然这一步可由库程序员完成)。
注意async的几个返回值类型,这表明了不一样的使用场景。若是是void,说明客户端不关心数据同步问题,它只须要线程的控制权马上返回。能够用在ui 等场合,若是是Task,客户端也不关心数据,可是它但愿可以控制异步线程,这多是对任务执行顺序有必定的要求。固然,最多见的是Task<TResult>。
综上,async和await并非为了多任务而设计的,若是追求高并发,应该在async函数内部用Task好好设计一番。在使用async 和await的时候,只须要按照非阻塞的思路去编写代码就能够了,至于幕后怎么处理就交给真正的多线程代码建立者吧。
示范代码
static async Task RunTaskAsync(int step) { for(int i=0; i < step; i++) { await Task.Run(()=>Thread.Sleep(tmloop));//点是静态的,依次执行 Thread.Sleep(tm2); } Thread.Sleep(tm3); } //客户端 Task tk= RunTaskAsync(step); Thread.Sleep(tm1);//这一段是并行的,取max(函数,代码段)最大时间 tk.Wait( );//这里表明最终数据
为了达到高度并行,应该用真正的多线程代码:
static async Task RunTaskByParallelAsync(int step) { await Task.Run(()=>Parallel.For(0,step, s=>{loop(tmloop); loop(tm2); } )); loop(tm3); }
并行编码方法
并行执行有几个方法,第一个是建立n个Task,一块儿启动。问题是怎么处理await点。每一个task写一个await点是不行的,由于遇到第一个await就马上返回,而不会开启全部任务并行执行。所以await不能随便放。那么如何为一组Task设定await点呢?能够经过Task.WhenAll 这个方法,他会等待一组Task执行完毕返回。
特定状况下,能够用Parallel.For 来开启一组任务,可是这个类并无实现async模式,也就是它会阻塞当前线程,因此须要用一个Task来包裹它。
可见,非阻塞和并行不彻底是一回事。
C# Task用法
一、Task的优点
ThreadPool相比Thread来讲具有了不少优点,可是ThreadPool却又存在一些使用上的不方便。好比:
◆ ThreadPool不支持线程的取消、完成、失败通知等交互性操做;
◆ ThreadPool不支持线程执行的前后次序;
以往,若是开发者要实现上述功能,须要完成不少额外的工做,如今,FCL中提供了一个功能更强大的概念:Task。Task在线程池的基础上进行了优化,并提供了更多的API。在FCL4.0中,若是咱们要编写多线程程序,Task显然已经优于传统的方式。
如下是一个简单的任务示例:

using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Task t = new Task(() => { Console.WriteLine("任务开始工做……"); //模拟工做过程 Thread.Sleep(5000); }); t.Start(); t.ContinueWith((task) => { Console.WriteLine("任务完成,完成时候的状态为:"); Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted); }); Console.ReadKey(); } } }
二、Task的用法
2.一、建立任务
无返回值的方式
方式1:
var t1 = new Task(() => TaskMethod("Task 1"));
t1.Start();
Task.WaitAll(t1);//等待全部任务结束
注:
任务的状态:
Start以前为:Created
Start以后为:WaitingToRun
方式2:
Task.Run(() => TaskMethod("Task 2"));
方式3:
Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接异步的方法
或者
var t3=Task.Factory.StartNew(() => TaskMethod("Task 3"));
Task.WaitAll(t3);//等待全部任务结束
注:
任务的状态:
Start以前为:Running
Start以后为:Running

using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { var t1 = new Task(() => TaskMethod("Task 1")); var t2 = new Task(() => TaskMethod("Task 2")); t2.Start(); t1.Start(); Task.WaitAll(t1, t2); Task.Run(() => TaskMethod("Task 3")); Task.Factory.StartNew(() => TaskMethod("Task 4")); //标记为长时间运行任务,则任务不会使用线程池,而在单独的线程中运行。 Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning); #region 常规的使用方式 Console.WriteLine("主线程执行业务处理."); //建立任务 Task task = new Task(() => { Console.WriteLine("使用System.Threading.Tasks.Task执行异步操做."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } }); //启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler) task.Start(); Console.WriteLine("主线程执行其余处理"); task.Wait(); #endregion Thread.Sleep(TimeSpan.FromSeconds(1)); Console.ReadLine(); } static void TaskMethod(string name) { Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } } }
async/await的实现方式:

using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { async static void AsyncFunction() { await Task.Delay(1); Console.WriteLine("使用System.Threading.Tasks.Task执行异步操做."); for (int i = 0; i < 10; i++) { Console.WriteLine(string.Format("AsyncFunction:i={0}", i)); } } public static void Main() { Console.WriteLine("主线程执行业务处理."); AsyncFunction(); Console.WriteLine("主线程执行其余处理"); for (int i = 0; i < 10; i++) { Console.WriteLine(string.Format("Main:i={0}", i)); } Console.ReadLine(); } } }
带返回值的方式
方式4:
Task<int> task = CreateTask("Task 1");
task.Start();
int result = task.Result;

using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static Task<int> CreateTask(string name) { return new Task<int>(() => TaskMethod(name)); } static void Main(string[] args) { TaskMethod("Main Thread Task"); Task<int> task = CreateTask("Task 1"); task.Start(); int result = task.Result; Console.WriteLine("Task 1 Result is: {0}", result); task = CreateTask("Task 2"); //该任务会运行在主线程中 task.RunSynchronously(); result = task.Result; Console.WriteLine("Task 2 Result is: {0}", result); task = CreateTask("Task 3"); Console.WriteLine(task.Status); task.Start(); while (!task.IsCompleted) { Console.WriteLine(task.Status); Thread.Sleep(TimeSpan.FromSeconds(0.5)); } Console.WriteLine(task.Status); result = task.Result; Console.WriteLine("Task 3 Result is: {0}", result); #region 常规使用方式 //建立任务 Task<int> getsumtask = new Task<int>(() => Getsum()); //启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler) getsumtask.Start(); Console.WriteLine("主线程执行其余处理"); //等待任务的完成执行过程。 getsumtask.Wait(); //得到任务的执行结果 Console.WriteLine("任务执行结果:{0}", getsumtask.Result.ToString()); #endregion } static int TaskMethod(string name) { Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(TimeSpan.FromSeconds(2)); return 42; } static int Getsum() { int sum = 0; Console.WriteLine("使用Task执行异步操做."); for (int i = 0; i < 100; i++) { sum += i; } return sum; } } }
async/await的实现:

using System; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { public static void Main() { var ret1 = AsyncGetsum(); Console.WriteLine("主线程执行其余处理"); for (int i = 1; i <= 3; i++) Console.WriteLine("Call Main()"); int result = ret1.Result; //阻塞主线程 Console.WriteLine("任务执行结果:{0}", result); } async static Task<int> AsyncGetsum() { await Task.Delay(1); int sum = 0; Console.WriteLine("使用Task执行异步操做."); for (int i = 0; i < 100; i++) { sum += i; } return sum; } } }
2.二、组合任务.ContinueWith
简单Demo:

任务的串行:

子任务:

动态并行(TaskCreationOptions.AttachedToParent) 父任务等待全部子任务完成后 整个任务才算完成

2.三、取消任务 CancellationTokenSource

2.四、处理任务中的异常
单个任务:

多个任务:

async/await的方式:

2.五、Task.FromResult的应用

2.六、使用IProgress实现异步编程的进程通知
IProgress<in T>只提供了一个方法void Report(T value),经过Report方法把一个T类型的值报告给IProgress,而后IProgress<in T>的实现类Progress<in T>的构造函数接收类型为Action<T>的形参,经过这个委托让进度显示在UI界面中。

2.七、Factory.FromAsync的应用 (简APM模式(委托)转换为任务)(BeginXXX和EndXXX)
带回调方式的

不带回调方式的

c#源码的执行过程
我想也许要写些东西,记录我作程序员的日子吧
================================================
要讲到C#源码的执行过程 首先要提下程序集,由于Clr并非和托管摸块打交道的,而是和程序集(dll,exe)
一、从哪里来
程序集是由一个或者多个托管模块以及 资源文件等共同组成的,C#编译器(csc.exe)再把源码编程成IL代码和元数据的时候,会进一步连同资源文件合并成程序集,
实际上就是个PE32文件,里面包含一个清单文件 和多个托管模块和资源(如图),另外程序集中还有一些自描述信息。
二、执行过程
编译器生成好程序集之后,若是是可执行的程序集,会在Main方法执行以前,window会预先读取程序集的头文件(pe32),若是是x86则开一个32位的进程,x64的就开一个64位的进程
而后在进程空间里面加载MSCOREE.DLL的x86 或者x64版本或者arm版本,而后进程的主线程会调用MSCOREE.DLL的一个方法,初始化Clr,而Clr会加载程序集exe,再调用其入口方法Main。
3.Main方法内部执行
在Main执行以前,Clr 会检测出方法引用的全部类型,(Console),而后在内存中分配对应数据类型的空间,这个地址里面包含着这个类型全部的方法声名,每一项都对应着Clr里面的一个未编档函数(JITCompiler)
首次运行Main方法的试试JITCompiler会被调用,在这个方法里面1,负责在方法的实现类型中(console)程序集元数据中查询该方法的IL方法 2,动态分配内存块 3,把IL编译成本机Cpu的指令,存储到动态分配的空间里面
4,修改这个条目的地址,使它指向动态分配的地址 5.跳转到内存块中的本机代码执行,这时候执行的就是IL代码的cpu机器码
5.在次执行Console.WriteLine的时候,就不会运行JITCompiler,直接运行机器码