从零开始编写自己的C#框架(14)——T4模板在逻辑层中的应用(三)
原本关于T4模板原想分5个章节详细解说的,不过因为最近比较忙,也不想将整个系列时间拉得太长,所以就将它们整合在一块了,可能会有很多细节没有讲到,希望大家自己对着代码与模板去研究。
本章代码量会比较大,基本将Web层要使用到的大部分函数都用模板生成了出来,而模板中的函数,很多也是互相关联调用的。另外在DotNet.Utilities(公共函数项目)中也添加与修改了一些类和函数。
需要特别说明的是,在逻辑层添加了July大神编写的超强上传类,具体怎么使用功能怎么强大,在后面调用到时会用一个章节详细说明。呵呵......
1、先了解解决方案中各个新增文件功能,具体的文件对应说明,请查看《数据字典》中的“目录与文件说明”
这个是各个表对应的逻辑层类,里面是Web层常用的各种函数。Application文件夹是各种公共逻辑层函数类,Systems文件夹是后端系统管理模块常用函数类。以后添加新的文件时,可以按功能或业务进行对应分类存放。
LogicBase.cs是逻辑层所有模板生成类的父类,里面有两个虚函数,用来给模板类调用。在有需要的时候,在自定义类中重写这两个函数,就可以给模板中的相应函数自动调用执行。
DelCache()函数是模板类中进行添加、修改、删除、更新等对数据库记录进行变更时会同步调用到,主要用于拥有自定义缓存的类中,重写该函数后,进行前面的各项操作时自动执行该函数,以达到同步理清自定义缓存的功能。
GetExpression()函数是提供给模板类中的缓存加载函数(GetList())使用的。我们在开发时会创建很多表,有些表全部记录需要加载到缓存中;有一些表记录不需要加载到缓存当中(比如日志表);同时也有一些表的记录会过期,只需要加载指定条件的记录到缓存使用就可以了,对于后者我们就可以使用GetExpression()函数来进行控制,只要重写这个函数,系统在运行GetList()函数时就会自动加载我们自定义的条件,从数据库中筛选出我们想要的记录到缓存中。
CommonBll.cs是逻辑层的工具类,主要提供给模板生成类调用。具体使用方法请看注释和相关例子。
2、逻辑层T4模板文件CreateBll.tt(文件在文章后面下载源码里)
模板运行后将会生成下图中的这些函数
其中IIS缓存又包含下图中这些常用函数
原来是想使用Redis来处理缓存的,后来考虑到对于中小型项目来说,很多都没有独立的空间,使用的是虚拟机,用Redis也就不是很合适了,所以换成IIS缓存来处理
IIS缓存也由之前的表级别缓存修改为记录级别了,就是说你对某一条记录进行添加、删除、修改、更新等操作时,不用清空整个表缓存,直接对缓存中的记录进行操作,不过这个功能刚刚改为记录级别,得Web层代码开始写后才能测试看看效果怎么样
3、主要模板函数功能说明
1)模板函数调用使用单例模式
对于中小型项目来说,访问量并发量并不是很大,单例模式已经够用了,如果对某一个表并发量过大时,怕出现问题,也可以直接new出这个类,不使用单例调用就可以了
比如:
单例模式调用
InformationBll.GetInstence().GetList();
非单例模式调用
var information = new InformationBll();
information.GetList();
2)表缓存函数
private const string const_CacheKey = "Cache_Information";
private const string const_CacheKey_Date = "Cache_Information_Date"; #region 清空缓存
/// <summary>清空缓存</summary>
private void DelAllCache()
{
//清除模板缓存
CacheHelper.RemoveOneCache(const_CacheKey);
CacheHelper.RemoveOneCache(const_CacheKey_Date); //清除前台缓存
CommonBll.RemoveCache(const_CacheKey);
//运行自定义缓存清理程序
DelCache();
}
#endregion #region IIS缓存函数 #region 从IIS缓存中获取Information表记录
/// <summary>
/// 从IIS缓存中获取Information表记录
/// </summary>
/// <param name="isCache">是否从缓存中读取</param>
public IList<DataAccess.Model.Information> GetList(bool isCache = true)
{
try
{
//判断是否使用缓存
if (CommonBll.IsUseCache() && isCache){
//检查指定缓存是否过期——缓存当天有效,第二天自动清空
if (CommonBll.CheckCacheIsExpired(const_CacheKey_Date)){
//删除缓存
DelAllCache();
} //从缓存中获取DataTable
var obj = CacheHelper.GetCache(const_CacheKey);
//如果缓存为null,则查询数据库
if (obj == null)
{
var list = GetList(false); //将查询出来的数据存储到缓存中
CacheHelper.SetCache(const_CacheKey, list);
//存储当前时间
CacheHelper.SetCache(const_CacheKey_Date, DateTime.Now); return list;
}
//缓存中存在数据,则直接返回
else
{
return (IList<DataAccess.Model.Information>)obj;
}
}
else
{
//定义临时实体集
IList<DataAccess.Model.Information> list = null; //获取全表缓存加载条件表达式
var exp = GetExpression<Information>();
//如果条件为空,则查询全表所有记录
if (exp == null)
{
//从数据库中获取所有记录
var all = Information.All();
list = all == null ? null : Transform(all.ToList());
}
else
{
//从数据库中查询出指定条件的记录,并转换为指定实体集
var all = Information.Find(exp);
list = all == null ? null : Transform(all);
} return list;
}
}
catch (Exception e)
{
//记录日志
CommonBll.WriteLog("从IIS缓存中获取Information表记录时出现异常", e);
} return null;
}
#endregion
只要调用了GetList()函数,系统就会将全表记录(重写GetExpression()函数的,只加载符合条件的记录)以IList<T>存储方式缓存到IIS缓存中,供其他相关函数使用,缓存当天有效,第二天访问时会自动清空重新加载
一般来说,前端与后端在一个项目时,后端操作缓存会直接影响前端的数据。如果前后端分开,做为两个项目来开发时,进行增、删、改操作时就必须调用DelAllCache()函数来清除前后端的缓存,以便两个站点的缓存能及时同步(目前对于使用IIS缓存跨站点记录级别缓存同步还没有一个比较好的处理方法,只能直接清空整表缓存来实现),调用DelAllCache()函数时,会执行CommonBll.RemoveCache(const_CacheKey)函数,该函数会通过一个前端访问接口,发送经过加密处理后的数据,前端接口接收到请求后再清除对应缓存来实现前后端IIS缓存同步功能。
3)实体转换函数
#region 实体转换
/// <summary>
/// 将Information记录实体(SubSonic实体)转换为普通的实体(DataAccess.Model.Information)
/// </summary>
/// <param name="model">SubSonic插件生成的实体</param>
/// <returns>DataAccess.Model.Information</returns>
public DataAccess.Model.Information Transform(Information model)
{
if (model == null)
return null; return new DataAccess.Model.Information
{
Id = model.Id,
InformationClass_Root_Id = model.InformationClass_Root_Id,
InformationClass_Root_Name = model.InformationClass_Root_Name,
InformationClass_Id = model.InformationClass_Id,
InformationClass_Name = model.InformationClass_Name,
Title = model.Title,
RedirectUrl = model.RedirectUrl,
Content = model.Content,
Upload = model.Upload,
FrontCoverImg = model.FrontCoverImg,
Note = model.Note,
NewsTime = model.NewsTime,
Keywords = model.Keywords,
SeoTitle = model.SeoTitle,
SeoKey = model.SeoKey,
SeoDesc = model.SeoDesc,
Author = model.Author,
FromName = model.FromName,
Sort = model.Sort,
IsDisplay = model.IsDisplay,
IsHot = model.IsHot,
IsTop = model.IsTop,
IsPage = model.IsPage,
IsDel = model.IsDel,
CommentCount = model.CommentCount,
ViewCount = model.ViewCount,
AddYear = model.AddYear,
AddMonth = model.AddMonth,
AddDay = model.AddDay,
AddDate = model.AddDate,
Manager_Id = model.Manager_Id,
Manager_CName = model.Manager_CName,
UpdateDate = model.UpdateDate,
};
} /// <summary>
/// 将Information记录实体集(SubSonic实体)转换为普通的实体集(DataAccess.Model.Information)
/// </summary>
/// <param name="sourceList">SubSonic插件生成的实体集</param>
public IList<DataAccess.Model.Information> Transform(IList<Information> sourceList)
{
//创建List容器
var list = new List<DataAccess.Model.Information>();
//将SubSonic插件生成的实体集转换后存储到刚创建的List容器中
sourceList.ToList().ForEach(r => list.Add(Transform(r)));
return list;
} /// <summary>
/// 将Information记录实体由普通的实体(DataAccess.Model.Information)转换为SubSonic插件生成的实体
/// </summary>
/// <param name="model">普通的实体(DataAccess.Model.Information)</param>
/// <returns>Information</returns>
public Information Transform(DataAccess.Model.Information model)
{
if (model == null)
return null; return new Information
{
Id = model.Id,
InformationClass_Root_Id = model.InformationClass_Root_Id,
InformationClass_Root_Name = model.InformationClass_Root_Name,
InformationClass_Id = model.InformationClass_Id,
InformationClass_Name = model.InformationClass_Name,
Title = model.Title,
RedirectUrl = model.RedirectUrl,
Content = model.Content,
Upload = model.Upload,
FrontCoverImg = model.FrontCoverImg,
Note = model.Note,
NewsTime = model.NewsTime,
Keywords = model.Keywords,
SeoTitle = model.SeoTitle,
SeoKey = model.SeoKey,
SeoDesc = model.SeoDesc,
Author = model.Author,
FromName = model.FromName,
Sort = model.Sort,
IsDisplay = model.IsDisplay,
IsHot = model.IsHot,
IsTop = model.IsTop,
IsPage = model.IsPage,
IsDel = model.IsDel,
CommentCount = model.CommentCount,
ViewCount = model.ViewCount,
AddYear = model.AddYear,
AddMonth = model.AddMonth,
AddDay = model.AddDay,
AddDate = model.AddDate,
Manager_Id = model.Manager_Id,
Manager_CName = model.Manager_CName,
UpdateDate = model.UpdateDate,
};
} /// <summary>
/// 将Information记录实体由普通实体集(DataAccess.Model.Information)转换为SubSonic插件生成的实体集
/// </summary>
/// <param name="sourceList">普通实体集(DataAccess.Model.Information)</param>
public IList<Information> Transform(IList<DataAccess.Model.Information> sourceList)
{
//创建List容器
var list = new List<Information>();
//将普通实体集转换后存储到刚创建的List容器中
sourceList.ToList().ForEach(r => list.Add(Transform(r)));
return list;
}
#endregion
由于SubSonic3.0插件生成的Model附加了很多功能,在对实体进行赋值操作时使用了Linq运算,判断该字段是否有进行赋值操作,没有的话在最终生成SQL时,会自动过滤掉该字段。这个功能非常不错,但是将实体存储到缓存中或进行Json转换等操作时,由于这个运算导致程序进入了死循环,无法运行,所以必须将它进行一次转换,转化为最常见的普通实体,具体大家可以查看Solution.DataAccess层SubSonic文件夹下ActiveRecord.tt模板生成的类与CreateModel.tt模板生成的类文件比较一下。实体转换函数就是将这两种不同的实体进行相互转换的函数
SubSonic3.0插件ActiveRecord.tt生成的Model
自定义CreateModel.tt模板生成的实体
4)获取DataTable与绑定Grid表格
#region 获取Information表记录
/// <summary>
/// 获取Information表记录
/// </summary>
/// <param name="norepeat">是否使用去重复</param>
/// <param name="top">获取指定数量记录</param>
/// <param name="columns">获取指定的列记录</param>
/// <param name="pageIndex">当前分页页面索引</param>
/// <param name="pageSize">每个页面记录数量</param>
/// <param name="wheres">查询条件</param>
/// <param name="sorts">排序方式</param>
/// <returns>返回DataTable</returns>
public DataTable GetDataTable(bool norepeat = false, int top = , List<string> columns = null, int pageIndex = , int pageSize = , List<ConditionFun.SqlqueryCondition> wheres = null, List<string> sorts = null) {
try
{
//分页查询
var select = new SelectHelper();
return select.SelectDataTable<Information>(norepeat, top, columns, pageIndex, pageSize, wheres, sorts);
}
catch (Exception e)
{
//记录日志
CommonBll.WriteLog("获取Information表记录时出现异常", e); return null;
}
}
#endregion #region 绑定Grid表格
/// <summary>
/// 绑定Grid表格,并实现分页
/// </summary>
/// <param name="grid">表格控件</param>
/// <param name="pageIndex">第几页</param>
/// <param name="pageSize">每页显示记录数量</param>
/// <param name="wheres">查询条件</param>
/// <param name="sorts">排序</param>
public void BindGrid(FineUI.Grid grid, int pageIndex = , int pageSize = , List<ConditionFun.SqlqueryCondition> wheres = null, List<string> sorts = null) {
//用于统计执行时长(耗时)
var swatch = new Stopwatch();
swatch.Start(); try {
// 1.设置总项数
grid.RecordCount = GetRecordCount(wheres);
// 如果不存在记录,则清空Grid表格
if (grid.RecordCount == ) {
grid.Rows.Clear();
return;
} // 2.查询并绑定到Grid
grid.DataSource = GetDataTable(false, , null, pageIndex, pageSize, wheres, sorts);
grid.DataBind(); }
catch (Exception e) {
// 记录日志
CommonBll.WriteLog("获取用户操作日志表记录时出现异常", e); } // 统计结束
swatch.Stop();
// 计算查询数据库使用时间,并存储到Session里,以便UI显示
HttpContext.Current.Session["SpendingTime"] = (swatch.ElapsedMilliseconds / 1000.00).ToString();
}
#endregion #region 绑定Grid表格
/// <summary>
/// 绑定Grid表格,使用内存分页,显示有层次感
/// </summary>
/// <param name="grid">表格控件</param>
/// <param name="parentValue">父Id值</param>
/// <param name="wheres">查询条件</param>
/// <param name="sorts">排序</param>
/// <param name="parentId">父Id</param>
public void BindGrid(FineUI.Grid grid, int parentValue, List<ConditionFun.SqlqueryCondition> wheres = null, List<string> sorts = null, string parentId = "ParentId") {
//用于统计执行时长(耗时)
var swatch = new Stopwatch();
swatch.Start(); try
{
// 查询数据库
var dt = GetDataTable(false, , null, , , wheres, sorts); // 对查询出来的记录进行层次处理
grid.DataSource = DataTableHelper.DataTableTidyUp(dt, "Id", parentId, parentValue);
// 查询并绑定到Grid
grid.DataBind(); }
catch (Exception e) {
// 记录日志
CommonBll.WriteLog("绑定表格时出现异常", e); } //统计结束
swatch.Stop();
//计算查询数据库使用时间,并存储到Session里,以便UI显示
HttpContext.Current.Session["SpendingTime"] = (swatch.ElapsedMilliseconds / 1000.00).ToString();
} /// <summary>
/// 绑定Grid表格,使用内存分页,显示有层次感
/// </summary>
/// <param name="grid">表格控件</param>
/// <param name="parentValue">父Id值</param>
/// <param name="sorts">排序</param>
/// <param name="parentId">父Id</param>
public void BindGrid(FineUI.Grid grid, int parentValue, List<string> sorts = null, string parentId = "ParentId") {
BindGrid(grid, parentValue, null, sorts, parentId);
}
#endregion
主要是用于通过条件查询,获取指定表记录,然后与后端的FineUI.Grid绑定,具体使用例子会在Web层的相关章节中说明
大家在看源码时会发现CommonBll.WriteLog("获取Information表记录时出现异常", e);这样的代码,其实在整个框架中这种代码大量存在,它会将出现的异常或操作步骤,忠实的记录指定目录的文本文件中,方便我们查看分析。特别是项目上线后,在生产环境中我们很多时候是不能直接对生产环境进行测试的,而用户是做了什么操作才出现这种异常的没有日志记录就很难进行排查,所以我们在编写自定义逻辑层函数时,也随手将这样的代码写上,以方便我们以后分析问题。
而HttpContext.Current.Session["SpendingTime"] = (swatch.ElapsedMilliseconds / 1000.00).ToString();这种代码记录的是执行查询绑定FineUI.Grid花费的时间,并会在Web层相关列表页面显示出来,方便我们了解页面的执行效率。
模版会生成两种类型的Grid表格绑定函数,一个是正常的列表绑定,一个是用于多级分类时有层次感显示的列表绑定。正常列表是直接从数据库中查询出来,而有层次感的列表,使用的是内存分页,而不是整表缓存。它是将按条件查询出来的记录缓存到内存中,在点击翻页时,是在内存中获取分页然后显示。
有层次感列表例子:
5)添加与编辑记录函数
#region 添加与编辑Information表记录
/// <summary>
/// 添加与编辑Information记录
/// </summary>
/// <param name="page">当前页面指针</param>
/// <param name="model">Information表实体</param>
/// <param name="isCache">是否更新缓存</param>
public void Save(Page page, Information model, bool isCache = true)
{
try {
//保存
model.Save(); //判断是否启用缓存
if (CommonBll.IsUseCache() && isCache)
{
SetModelForCache(model);
} //添加用户访问记录
UseLogBll.GetInstence().Save(page, "{0}" + (model.Id == ? "添加" : "编辑") + "Information记录成功,ID为【" + model.Id + "】");
}
catch (Exception e) {
var result = "执行InformationBll.Save()函数出错!"; //出现异常,保存出错日志信息
CommonBll.WriteLog(result, e);
}
}
#endregion
执行更新操作后,会检查是否启用了缓存功能,启用了由会同步更新缓存记录。然后执行用户操作日志记录功能,将用户执行了什么操作更新到对应的数据库中。其他删除与更新操作都会做同样的记录。(有了详细的操作日志记录,万一后端系统管理人员操作出了什么问题,要落实操作责任也就很容易查找出来)
6)保存排序与自动排序函数
#region 保存列表排序
/// <summary>
/// 保存列表排序,如果使用了缓存,保存成功后会清空本表的所有缓存记录,然后重新加载进缓存
/// </summary>
/// <param name="page">当前页面指针</param>
/// <param name="grid1">页面表格</param>
/// <param name="tbxSortId">表格中绑定排序的表单名</param>
/// <param name="sortIdName">排序字段名</param>
/// <returns>更新成功返回true,失败返回false</returns>
public bool UpdateSort(Page page, FineUI.Grid grid1, string tbxSortId, string sortIdName = "SortId")
{
//更新排序
if (CommonBll.UpdateSort(page, grid1, tbxSortId, "Information", sortIdName, "Id"))
{
//判断是否启用缓存
if (CommonBll.IsUseCache())
{
//删除所有缓存
DelAllCache();
//重新载入缓存
GetList();
} //添加用户操作记录
UseLogBll.GetInstence().Save(page, "{0}更新了Information表排序!"); return true;
} return false;
}
#endregion #region 自动排序
/// <summary>自动排序,如果使用了缓存,保存成功后会清空本表的所有缓存记录,然后重新加载进缓存</summary>
/// <param name="page">当前页面指针</param>
/// <param name="strWhere">附加Where : " sid=1 "</param>
/// <param name="isExistsMoreLv">是否存在多级分类,一级时,请使用false,多级使用true,(一级不包括ParentID字段)</param>
/// <param name="pid">父级分类的ParentID</param>
/// <param name="fieldName">字段名:"SortId"</param>
/// <param name="fieldParentId">字段名:"ParentId"</param>
/// <returns>更新成功返回true,失败返回false</returns>
public bool UpdateAutoSort(Page page, string strWhere = "", bool isExistsMoreLv = false, int pid = , string fieldName = "SortId", string fieldParentId = "ParentId")
{
//更新排序
if (CommonBll.AutoSort("Id", "Information", strWhere, isExistsMoreLv, pid, fieldName, fieldParentId))
{
//判断是否启用缓存
if (CommonBll.IsUseCache())
{
//删除所有缓存
DelAllCache();
//重新载入缓存
GetList();
} //添加用户操作记录
UseLogBll.GetInstence().Save(page, "{0}对Information表进行了自动排序操作!"); return true;
} return false;
}
#endregion
保存排序函数是将列表中直接填写的排序直接保存到数据库中
自动排序函数执行后,会将当前页面所绑定表格的所有记录分级别全部从小到大重新进行排序,比如二级分类原排序值为1、2、5、10、11、12,执行后会变成1、2、3、4、5、6。
同时会清除缓存,并且添加用户操作记录。
Web层会将这两个函数进行封装,处理后无需再编写代码,只要添加了对应按键就会自支继承相应功能,减少Web层的重复代码编写
由于使用了新的工具类库,模板也做了一些修改,代码实现就花了好几天才完成,而直接完成代码后,文章都不知道怎么写才合适了,今天完成后反复看了几遍,都觉得写得差强人意,呵呵......
除了上面介绍的函数外,还有其他类与模板函数大家自己看吧,有什么问题再发上来大家一起讨论
4、小结
写到这里,其实框架的底层结构算是基本完成的,T4模板与SubSonic3.0的结合,产生一个轻量级的开发框架,无论是开发Winform、Web服务还是其他软件,在这个组合下都可以令我们轻松应对,去除了大量的重复编码时间,轻轻松松一键生成我们需要的大部分代码。而模板设计合理的话,应用一些新技术或替换某些功能(比如IIS模板换成Redis模板),Web层基本上不用做什么修改就可以直接使用了。对于数据库添加新表新字段,修改或删除字段操作,也变得很轻松。由于整个设计不存在硬编码,就算有些地方要修改,运行一下编译就能马上知道需要修改那个位置。
点击下载:
版权声明:
本文由AllEmpty原创并发布于博客园,欢迎转载,未经本人同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。如有问题,可以通过1654937@qq.com 联系我,非常感谢。
发表本编内容,只要主为了和大家共同学习共同进步,有兴趣的朋友可以加加Q群:327360708 ,大家一起探讨。
更多内容,敬请观注博客:http://www.cnblogs.com/EmptyFS/
从零开始编写自己的C#框架(14)——T4模板在逻辑层中的应用(三)的更多相关文章
- 从零开始编写自己的C#框架(12)——T4模板在逻辑层中的应用(一)(附源码)
对于T4模板很多朋友都不太熟悉,它在项目开发中,会帮我们减轻很大的工作量,提升我们的开发效率,减少出错概率.所以学好T4模板的应用,对于开发人员来说是非常重要的. 园子里对于T4模板的介绍与资料已经太 ...
- 从零开始编写自己的C#框架(13)——T4模板在逻辑层中的应用(二)
最近这段时间特忙,公事私事,忙得有时都没时间打开电脑了,这两周只能尽量更新,以后再将章节补回来. 直接进入主题,通过上一章节,大家明白了怎么使用模板类编写T4模板,本章进的是一些简单技巧的应用 1.首 ...
- 从零开始编写自己的C#框架(1)——前言
记得十五年前自学编程时,拿着C语言厚厚的书,想要上机都不知道要用什么编译器来执行书中的例子.十二年前在大学自学ASP时,由于身边没有一位同学和朋友学习这种语言,也只能整天混在图收馆里拼命的啃书.而再后 ...
- 从零开始编写自己的C#框架 ---- 系列文章
目录: 从零开始编写自己的C#框架(1)——前言从零开始编写自己的C#框架(2)——开发前的准备工作从零开始编写自己的C#框架(3)——开发规范从零开始编写自己的C#框架(4)——文档编写说明从零开始 ...
- 从零开始编写自己的C#框架(17)——Web层后端首页
后端首页是管理员登陆后进入的第一个页面,主要是显示当前登陆用户信息.在线人数.菜单树列表.相关功能按键和系统介绍.让管理员能更方便的找到息想要的内容. 根据不同系统的需要,首页会显示不同的内容,比如显 ...
- 从零开始编写自己的C#框架(26)——小结
一直想写个总结,不过实在太忙了,所以一直拖啊拖啊,拖到现在,不过也好,有了这段时间的沉淀,发现自己又有了小小的进步.哈哈...... 原想框架开发的相关开发步骤.文档.代码.功能.部署等都简单的讲过了 ...
- 从零开始编写自己的C#框架(15)——Web层后端登陆功能
对于一个后端管理系统,最重要内容之一的就是登陆页了,无论是安全验证.用户在线记录.相关日志记录.单用户或多用户使用帐号控制等,都是在这个页面进行处理的. 1.在解决方案中创建一个Web项目,并将它设置 ...
- 从零开始编写自己的C#框架(11)——创建解决方案
这段时间一直在充电,拜读了园子中大神们的博文(wayfarer的<设计之道>.TerryLee的<.NET设计模式系列文章>.卡奴达摩的<设计模式>还有其他一些零散 ...
- 从零开始编写自己的C#框架(2)——开发前准备工作
没想到写了个前言就受到很多朋友的支持,大家的推荐就是我最大的动力(推荐得我热血沸腾,大家就用推荐来猛砸我吧O^-^O),谢谢大家支持. 其实框架开发大家都知道,不过要想写得通俗点,我个人觉得还是挺吃力 ...
随机推荐
- 梅须逊雪三分白,雪却输梅一段香——CSS动画与JavaScript动画
CSS动画并不是绝对比JavaScript动画性能更优越,开源动画库Velocity.js等就展现了强劲的性能. 一.两者的主要区别 先开门见山的说说两者之间的区别. 1)CSS动画: 基于CSS的动 ...
- Tomcat shutdown执行后无法退出进程问题排查及解决
问题定位及排查 上周无意中调试程序在Linux上ps -ef|grep tomcat发现有许多tomcat的进程,当时因为没有影响系统运行就没当回事.而且我内心总觉得这可能是tomcat像nginx一 ...
- 恋爱虽易,相处不易:当EntityFramework爱上AutoMapper
剧情开始 为何相爱? 相处的问题? 女人的伟大? 剧情收尾? 有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易 ...
- AutoFac在项目中的应用
技能大全:http://www.cnblogs.com/dunitian/p/4822808.html#skill 完整Demo:https://github.com/dunitian/LoTCode ...
- 运用php做投票题,例题
要求大概是这样的,有一个题目,题目下面是复选框,要求点完复选框提交后会变成进度条,各选项的进度条百分比,和投票数量 首先还是要在数据库建两张表,如下: 要完成这个题目,需要建两个页面 <!DOC ...
- 使用 Android Studio 检测内存泄漏与解决内存泄漏问题
本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...
- QT内省机制、自定义Model、数据库
本文将介绍自定义Model过程中数据库数据源的获取方法,我使用过以下三种方式获取数据库数据源: 创建 存储对应数据库所有字段的 结构体,将结构体置于容器中返回,然后根据索引值(QModelIndex) ...
- 《动手实现一个网页加载进度loading》
loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...
- Linux命令【第三篇】
执行下面命令时发现提示需要输入密码,请问提示输入的密码是哪个用户的密码. [test@oldboy ~]$ sudo su - oldboy 解答: 输入当前执行命令test账户的密码. 相关说明: ...
- AutoMapper使用中的问题
指定值只会执行一次 public class MomanBaseProfile : Profile { public MomanBaseProfile() { CreateMap<Request ...