[C#]ASP.NET MVC 多语言解决方案

mikel阅读(1056)

    现在ASP.NET MVC 已经出了第四版了,现在多了很多特性,但是如何在 ASP.NET MVC 下方便的实现多语言特性呢?

就一个网站的多语言特性来说,我认为分为两个方面:

1、HTML界面上显示的文字需要多语言

2、HTML界面上JS输出的文字需要多语言

原来在HTML部分直接写的文字都不能直接写要输出的文字,而是要采用标记的方法来替换。JS也是同理。

那么在MVC下怎么能透明的实现多语言呢?所谓透明的实现是指,程序员在开发程序当中,不需要过多的考虑多语言的问题,直接调用一个方法就能实现多语言,而且所要用到的语言文件每个语言一个文件就够了,集中翻译这个语言就完成了多语言的功能。

例如
<html>

<head>

</head>

<body>

   多语言输出的文字  //这里就不能直接写中文了,一个好方法是 直接用 <%= Html.Lang("string1") %> 来进行输出

</body>

</html>

这里<%= Html.Lang("clickme") %> 是对 HTMLHelper 进行了扩展,增加了一个 Lang 的方法,参数是需要翻译的资源字符串的Name。

 怎么为 HTMLHelper 进行扩展?下面这个类就是增加了的扩展类

 

 


using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Globalization;
using System.Web.Compilation;
using System.Web.Mvc;
using System.Web.Routing;
namespace System.Web.Mvc {
    
public static class LocalizationHelpers {
        
/// <summary>
        
/// 在外边的 Html 中直接使用
        
/// </summary>
        
/// <param name="htmlhelper"></param>
        
/// <param name="key"></param>
        
/// <returns></returns>
        public static string Lang(this HtmlHelper htmlhelper, string key) {
            
string FilePath = htmlhelper.ViewContext.HttpContext.Server.MapPath("/"+ "Resource\\";
            
return GetLangString(htmlhelper.ViewContext.HttpContext, key, FilePath);
        }
        
/// <summary>
        
/// 在外边的 Html 中直接使用,对 JS 进行输出字符串
        
/// </summary>
        
/// <param name="htmlhelper"></param>
        
/// <param name="key"></param>
        
/// <returns></returns>
        public static string LangOutJsVar(this HtmlHelper htmlhelper, string key) {
            
string FilePath = htmlhelper.ViewContext.HttpContext.Server.MapPath("/"+ "Resource\\";
            
string langstr = GetLangString(htmlhelper.ViewContext.HttpContext, key, FilePath);
            
return string.Format("var {0} = '{1}'", key,langstr);
        }
        
/// <summary>
        
/// 在 C# 中使用
        
/// </summary>
        
/// <param name="httpContext"></param>
        
/// <param name="key"></param>
        
/// <returns></returns>
        public static string InnerLang(HttpContextBase httpContext, string key) {
            
string FilePath = httpContext.Server.MapPath("/"+ "Resource\\";
            
return GetLangString(httpContext, key, FilePath);
        }
        
private static string GetLangString(HttpContextBase httpContext, string key, string FilePath) {
            LangType langtype 
= LangType.cn;
            
if (httpContext.Session["Lang"!= null) {
                langtype 
= (LangType)httpContext.Session["Lang"];
            }
            
return LangResourceFileProvider.GetLangString(key, langtype, FilePath);
        }
    }
    
public static class LangResourceFileProvider {
        
public static string GetLangString(string Key, LangType langtype, string FilePath) {
            
string filename;
            
switch (langtype) {
                
case LangType.cn: filename = "zh-cn.resources"break;
                
case LangType.en: filename = "en-us.resources"break;
                
default: filename = "zh-cn.resources"break;
            }
            System.Resources.ResourceReader reader 
= new System.Resources.ResourceReader(FilePath + filename);
            
string resourcetype;
            
byte[] resourcedata;
            
string result = string.Empty;
            
try {
                reader.GetResourceData(Key, 
out resourcetype, out resourcedata);
                
//去掉第一个字节,无用
                byte[] arr = new byte[resourcedata.Length  1];
                
for (int i = 0; i < arr.Length; i++) {
                    arr[i] 
= resourcedata[i + 1];
                }
                result 
= System.Text.Encoding.UTF8.GetString(arr);
            }
            
catch (Exception ex) {
            }
            
finally {
                reader.Close();
            }
            
return result;
        }
    }
    
public enum LangType {
            cn,
            en
        }
}

 这个类叫 LocalizationHelpers ,公开了 Lang,LangOutJsVar,InnerLang 三个方法,其中 Lang,LangOutJsVar 可以在 Html 界面中直接调用,InnerLang 可以在C#后台使用。

这里使用了 .resx 资源文件,注意这里这个文件需要被编译后才能使用,否则找不到已经增加的项。编译这个可以使用.NET 自带的 ResGen.exe。

上面这个类很简单,就是根据传入的 Session["Lang"] 中的语言类型来做判断,该读那个资源文件(资源文件必须在 Resource 目录下),然后读取所需要的NAME,返回对应的字符串VALUE,VALUE中就是最后要输出的文字了。

 

在前台的 .aspx 中就可以直接用  <%= Html.Lang("String1") %>来输出了。至于JS的输出,看下面例子

    <script language="JavaScript" type="text/JavaScript">
    <%= Html.LangOutJsVar("msg")%>
        function show()
        {
            alert(msg);
        }
    </script>

这样就OK了。

如果有的需要在C#中取资源字符串,那么可以使用           

ViewData["Message"] = LocalizationHelpers.InnerLang(this.ControllerContext.HttpContext, "Welcome");来输出。

我根据ASP.NET MVC 4做了个DEMO,截图如下


具体代码点击下载

[C#]扩展ASP.NET MVC HtmlHelper类

mikel阅读(976)

原文地址:Extending ASP.NET MVC HtmlHelper Class

在这篇帖子中我会使用一个示例演示扩展ASP.NET MVC HtmlHelper类,让它们可以在你的MVC视图中工作。这个示例中我会提供一个简单的方案生成Html表格。

HtmlHelper类

HtmlHelper类用于在ASP.NET MVC framework中帮助视图呈现html部分。

这个类提供了一些方法,你可以使用这些方法呈现html中的一些类型(textbox,checkbox等),或者html的一部分(如form)。ASP.NET MVC framework helper有这些内容:

  • Html.ActionLink()
  • Html.BeginForm()
  • Html.CheckBox()
  • Html.DropDownList()
  • Html.EndForm()
  • Html.Hidden()
  • Html.ListBox()
  • Html.Password()
  • Html.RadioButton()
  • Html.TextArea()
  • Html.TextBox()

比如说你要显示一个用name属性为myChkbox并且已经勾选的复选框,可以这样写:

<%=Html.CheckBox(“myChkbox”, true) %>

所有的html helper都是由扩展方法创建的,设在System.Web.Mvc.Html名称空间。

为HtmlHelper创建Html表格扩展

在本示例中我写了一个扩展方法,用于支持html表格的呈现。你可以修改它或者创建你自己的示例。

public static class MVCHelpers
{
    public static string Table(this HtmlHelper helper, string name, IList items, IDictionary<string, object> attributes)
    {
        if (items == null || items.Count == 0 || string.IsNullOrEmpty(name))
        {               
            return string.Empty;
        }
        return BuildTable(name, items, attributes);           
    }
    private static string BuildTable(string name, IList items, IDictionary<string, object> attributes)
    {
        StringBuilder sb = new StringBuilder();
        BuildTableHeader(sb, items[0].GetType());
        foreach (var item in items)
        {
            BuildTableRow(sb, item);
        }
        TagBuilder builder = new TagBuilder("table");
        builder.MergeAttributes(attributes);
        builder.MergeAttribute("name", name);
        builder.InnerHtml = sb.ToString();           
        return builder.ToString(TagRenderMode.Normal);
    }
    private static void BuildTableRow(StringBuilder sb, object obj)
    {
        Type objType = obj.GetType();
        sb.AppendLine("\t<tr>");
        foreach (var property in objType.GetProperties())
        {
            sb.AppendFormat("\t\t<td>{0}</td>\n", property.GetValue(obj, null));
        }
        sb.AppendLine("\t</tr>");
    }
    private static void BuildTableHeader(StringBuilder sb, Type p)
    {
        sb.AppendLine("\t<tr>");
        foreach (var property in p.GetProperties())
        {
            sb.AppendFormat("\t\t<th>{0}</th>\n", property.Name);
        }
        sb.AppendLine("\t</tr>");
    }
}

你可以看到我如何利用扩展方法Table扩展HtmlHelper类。BuildTable方法是主要方法,它利用ASP.NET MVC TagBuilder类来创建table标签。你可以看到在我的示例用,我使用了反射,获取各项的属性列表,并且把这些属性名称作为表头,它们的值填充为 表格单元格。

在视图中使用Html.Table扩展方法

如果你想使用这个自定义html helper,只需要做这些:

  • 在视图中用注册helper所在的名称空间:<%@ Import Namespace=”TaskList.Models” %>
  • 使用视图的Html属性中的Table方法,例如创建一个name属性为myTable,并使用视图中当前模型的例 子:<%=Html.Table(”myTable”, (IList)ViewData.Model, null) %>,注意Model如果不是IList,会出现异常。

总结

在这篇帖子中我介绍了HtmlHelper类,和如何为这个类创建一个简单的扩展方法。也可以通过创建你自己的类(如TableHelper)来扩展 HtmlHelper,你需要做的只是创建方法,并返回所要呈现的html。在我看来,使用扩展方法比较简单一点。

[C#]ASP.NET MVC 1.0开源

mikel阅读(988)

最新消息:ASP.NET MVC使用微软的开源许可协议发布了。Microsoft Public License已由Open Source Initiative组织认证,适合大部分需要开源许可的项目使用,其中包括Novell的Mono。

目前我们无法解释微软为何使用这种方式发布框架,不过我们已经向Scott Guthrie确认了一点,那就是这些代码并没有平台限制。据推测,这些代码也可以被移植到Java或Ruby平台中去,不过既然已经有了SpringRuby on Rails框架,因此这方面的需求应该几乎为零。

您可以在微软下载中心获取到ASP.NET MVC源代码

查看英文原文:ASP.NET MVC is Open Source

[C#]无缝缓存读取简化:仅Lambda表达式传递委托

mikel阅读(546)

之前写了一篇:无缝的缓存读取:双存储缓存策略,其中使用了两个存储地址交替提供缓存数据。

在其中用了两个存储指针转换以达到无缝读取缓存,在Cat Chen一语提醒之后,想了一想:的确是没有必要在缓存中使用两个存储指针的,其实一个存储地址,只要保证写入时在其它线程就可以。

更改存储介质至以下两个属性:

namespace CHCache {
/// <summary>
/// 缓存介质
/// </summary>
public class Medium {
/// <summary>
/// 存储区
/// </summary>
public object Store { get; set; }
/// <summary>
/// 是否正在更新
/// </summary>
public bool IsUpdating { get; set; }
}
}

这里存储区用于存储要缓存的实体内容,而IsUpdating则标识其是否正在更新。

对于缓存类,则更改了写入和读取方式。

/*
* http://www.cnblogs.com/chsword/
* chsword
* Date: 2009-3-31
* Time: 17:00
*
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace CHCache {
/// <summary>
/// 双存储的类
/// </summary>
public class DictionaryCache : IEnumerable {
/// <summary>
/// 在此缓存构造时初始化字典对象
/// </summary>
public DictionaryCache() {
Store = new Dictionary<string, Medium>();
}
public void Add(string key, Func<object> func) {
if (Store.ContainsKey(key)) {//修改,如果已经存在,再次添加时则采用其它线程
var elem = Store[key];
if (elem.IsUpdating) return;  //正在写入未命中
var th = new ThreadHelper(elem, func);
var td = new Thread(th.Doit);
td.Start();
}
else {//首次添加时可能也要读取,所以要本线程执行
Console.WriteLine("Begin first write");
Store.Add(key, new Medium { Store = func() });
Console.WriteLine("End first write");
}
}
/// <summary>
/// 读取时所用的索引
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object this[string key] {
get {
if (!Store.ContainsKey(key)) return null;
var elem = Store[key];
return elem.Store;
}
}
Dictionary<string, Medium> Store { get; set; }
public IEnumerator GetEnumerator() {
return ((IEnumerable)Store).GetEnumerator();
}
}
}

这里在添加时只控制了首次写入缓存在主线程,而读取时则直接读取缓存内容。

而线程辅助类也进行了简化,仅将其执行并写入就好了,抛出线程完全由DictionaryCache控制。

using System;
namespace CHCache {
/// <summary>
/// 一个线程Helper,用于帮助多抛出线程时传递参数
/// </summary>
public class ThreadHelper {
Func<object> Fun { get; set; }
Medium Medium { get; set; }
/// <summary>
/// 通过构造函数来传递参数
/// </summary>
/// <param name="m"></param>
/// <param name="fun"></param>
public ThreadHelper(Medium m, Func<object> fun) {
Medium = m;
Fun = fun;
}
/// <summary>
/// 线程入口,ThreadStart委托所对应的方法
/// </summary>
public void Doit() {
Medium.IsUpdating = true;
Console.WriteLine("Begin write.");
var ret = Fun.Invoke();
Medium.Store = ret;
Console.WriteLine("End write.");
Medium.IsUpdating = false;
}
}
}

其实有的时候思考问题还是不由自主的向着自己的经验方向刻意安排,这样通常把问题搞复杂了。

还好有园子里的朋友帮助,才简单的解决了问题,这样的由简至繁,再由繁衍至简的过程其实在实际开发中发生的还真不少。

 

[C#]ASP.NET MVC Training Kit发布了

mikel阅读(732)

Scott Guthrie昨天宣布了ASP.NET MVC 按照Ms-PL协议开源发布,具体内容参见ASP.NET MVC 1.0 has been released,也可以参看Scott Hanselman的新闻稿Microsoft ASP.NET MVC 1.0 is now Open Source MS-PL

微软同时也推出了一套ASP.NET MVC frameworke培训教材, 其中包含了许多范例程序、PPT、实验演练(Labs)教材等,想学习新技术的人不用在等了,赶快下载回来学习吧。

下面是主要内容截图:

image

 image

作者: 自由、创新、研究、探索……
出处:http://shanyou.cnblogs.com/
版权:本文版权归作者和博客园共有
转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】,谢谢
要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
个人网站: http://www.openbeta.cn

[Flash]flash跨域名动态加载数据

mikel阅读(758)

    以前开发的一个页面功能是由flash实现的,考虑到用户体验的连续性,flash与后台的交互不能通过普通的getURL的方式实现(否则每次交互都会导致页面刷新,用户体验很差),而必须用AS里面的LoadVars类来实现动态加载。
    通常活动开发的相关flash会放在html/…/promote目录下面(对应promote域名),而cgi会放在cgi-bin /service目录下面(对应service域名)。结果发现这个flash总是加载不到cgi输出的信息。把cgi移到cgi-bin /promote目录(对应promote域名),flash就可以加载到cgi的输出信息。看apache日志也发现:cgi放在promote目录, 就有HTTP请求;cgi放到service目录,则flash居然连访问cgi的请求包都没发送。很奇怪。
    通过HTTP Watch仔细对比两种情况下的请求,发现后者多了下面这个请求:
00:00:00.231 0.525 521 GET 404 text/html: charset=iso-88…  http://service.paipai.com/crossdomain.xml
    看看名字,估计就是flash对跨域加载数据作了安全性限制。baidu一番以后,在html/…/service目录添加crossdomain.xml文件,内容如下:
<?xml version="1.0"?>
<cross-domain-policy>
  <allow-access-from domain="*.paipai.com" />
</cross-domain-policy>
    这个xml文件的格式很简单,就不多解释了。加了这个文件以后,LoadVars类就可以从paipai.com的任何一个子域名访问到service域名的cgi了。
    如果不希望所有cgi都能随便访问,那么需要再作精细化配置,诸位可自行研究。

[Flex]AIR APIs详解 - 文件访问系统 part6

mikel阅读(888)

连载索引:
AIR APIs详解 – 文件访问系统 part1
AIR APIs详解 – 文件访问系统 part2
AIR APIs详解 – 文件访问系统 part3
AIR APIs详解 – 文件访问系统 part4
AIR APIs详解 – 文件访问系统 part5
Part6:
是 时候讨论一下同步和异步的问题了。同步(synchronous)和异步(asynchronous)编程不单单是AIR开发中才会遇到的问题,而是所有 编程中都很重要的一部分。往通俗了说,同步已经可以解决很多问题,但是同步解决不了特定环境下的问题。而异步处理用好了就是幸福,用不好就是灾难。单纯的 从功能角度而言,只要逻辑执行效率够高,速度够快(我们期待的快),很多问题用同步就可以解决,比如文件的操作。但是事实不是这样,我们AIR APIs连载的前五篇中的范例,全部是同步操作,这是基于这些范例的操作逻辑足够简单,操作的目标对象被处理的速度非常快,对于使用Flex(Flash Player单线程机制)而言,处于文件(夹)操作之后的逻辑完全可以等待这些小型的文件操作完成再顺序执行,这就是同步,后面的操作要等前面的逻辑执行 完毕后才会执行。但是如果我们操作的是一个1GB或者更大的文件目录体系呢?用同步的方式,后面的逻辑都要等这个操作完成,才能开始执行,显然这个不是同 步可以解决的问题。这时候需要在AIR里引入异步处理,很高兴,Adobe在AIR的很多APIs操作中都有内置的异步方式支持。
异步操作的最大 特点是没有必要非要在一个逻辑执行完毕后才去执行后续的逻辑,这个逻辑初次听起来还有点困惑,但是AIR中异步的实用价值在于当一个操作花费很长时间的时 候,后面的其他逻辑没必要等待,比如大文件夹的浏览和操作,大数据量文件的读写。不仅仅在AIR中,连平时Flash操作XML文件都可能要根据网络速度 和XML数据的大小来决定是否正确的使用异步操作。我们现在就来看一个异步执行语句逻辑的AS范例(你可以贴在Fla文件的timeline的第一帧上执 行,确认你的sandbox在publish setting是local file access only):

//命名一个URLLoader对象
var myloader:URLLoader;
myloader=new URLLoader();
myloader.addEventListener(Event.COMPLETE,completeLoadingHandler);
//从我的站点可以下载rss.xml,就在首页部分右下角的xml logo上,将rss.xml存储到与swf同一目录
myloader.load(new URLRequest("rss.xml"));
trace(myloader.data);
private function completeLoadingHandler(event:Event):void{
  trace(myloader.data);
}

上 面的代码你在执行后,会发现第一个trace语句的执行永远都是在myloader.load之前,而且是undefined,而真正有load数据的则 是completeLoadingHandler里面的trace部分,这就说明URLLoader类的load()方法本身就是异步的,所以,第一个 trace语句会先于load执行,所以结果是undefined。
那么使用同步和异步的规则很简单,当逻辑执行可以非常快的完成时,你就可以使用同步操作的方式了。而在AIR的文件访问API中,很多文件类的操作方法兼具同步和异步两个,具体使用哪个,可以根据情况而定,而今后的连载,你也将会看到有异步方法的操作了。
好了,part6就到这里。

[C#]蛙蛙推荐:简化基于数据库的DotNet应用程序开发

mikel阅读(811)

分析

  要做一个基于数据库的应用程序,我们有大量的重复劳动要去做,建表,写增删改查的SQL语句,写与数据库表对应的实体类,写执行SQLC#代码,写 添加、修改、列表、详细页面等等。这些活动都是围绕着一个个都数据表来开展的,在.NET领域有很多的OR Mapping的方案,但好多方案用起来好用,但原理很复杂,而且性能也不好把握,所以我们可以做一个轻型的ORM方案。有了ORM框架,根据数据表写 C#实体类这些劳动,其实也可以写一个代码生成器来帮我们生成,甚至代码生成器还能帮我们生成一些界面的代码。我们大概需要解决如下问题
1、我们要有一个通用的数据库操作帮助类,类似微软的DAAB,但最好能支持多种数据库;
2、我们要有一个使用简单的orm框架,能方便的用c#代码来进行数据库存取操作,而且要尽量保证性能,比如使用参数化查询;
3、我们要有一个代码生成器帮助我们解决一些重复性劳动,比如生成实体类,生成调用存储过程的c#代码等;

围绕这3个问题,我们一一来展开

一、通用的数据库吃操作帮助类

  ADO.NET 2.0为我们访问数据库提供了一套与具体数据库无关的模型,其核心类是DbProviderFactory,它遵循了Provider模式,就是把对各种 数据库的操作抽象出一个Provider,再由各种数据库去写与具体数据库相关的Provider,然后通过配置在运行时方便的切换数据库,而尽量少的不 修改业务逻辑层的代码,业务逻辑层依赖的是抽象的Provider。这也是典型的依赖倒置,就是说业务逻辑说我需要哪些接口,我依赖这些接口,而让别人去 实现这些接口,在运行的时候再去加载调用实现这些接口的具体类。
  为了提高性能,减少SQLServer执行计划的重编译,我们尽量使用参数化的查询,而一个固定的语句或者存储过程它的ADO.NET参数是固定的, 所以我们可以把这些参数缓存起来,避免每次执行SQL语句都创新新的参数对象。另外oledb的ado.net provider的参数是不能命名的,所以给参数赋值要按顺序赋值。

  为了使用方便,我们为执行SQL语句提供如下的API

public System.Data.DataSet SqlExecuteDateSet(string sql, string[] paramters, params object[] values)
public System.Data.DataTable SqlExecuteDateTable(string sql, string[] paramters, params object[] values)
public int SqlExecuteNonQuery(string sql, string[] paramters, params object[] values)
public System.Data.Common.DbDataReader SqlExecuteReader(string sql, string[] paramters, params object[] values)
public object SqlExecuteScalar(string sql, string[] paramters, params object[] values)

  当然,为了支持存储过程的执行,以及数据库事务,还需要提供相关的重载的API。大概的使用示例(面向SQLServer)如下:

DbHelper dbhelper = new DbHelper();
string sql = "delete from Citys where CityId = @id";
using (DatabaseTrans trans = new DatabaseTrans(dbhelper))
{
    
try
    {
        dbhelper.SqlExecuteNonQuery(trans, sql, 
new string[] { "@id" }, 1);
        dbhelper.SqlExecuteNonQuery(trans, sql, 
new string[] { "@id" }, 2);
        trans.Commit();
        OutPut(
"ok");
    }
    
catch (Exception)
    {
        trans.RollBack();
        OutPut(
"no ok");
    }
}

 

二、通用的ORM框架

先看如下的代码

 

//1、添加
xxxCase xxxCase = new xxxCase();
xxxCase.Title 
= "abc";
xxxCase.Content 
= "呵呵";
xxxCase.CaseFrom 
= CaseFrom.客服投诉;
xxxCase.PostUser 
= "huhao";
xxxCase.CreateTime 
= DateTime.Now;
xxxCase.CaseType 
= CaseType.生产环境查询;
xxxCase.Priority 
= CasePriority.中;
xxxCase.ReleationServices 
= "aaa,bbb";
xxxCase.ReleationClient 
= "ccc,ddd";
EntityBase.Insert(xxxCase);
//2、修改
xxxCase.ClearInnerData();
xxxCase.CaseId 
= 1;
xxxCase.Title 
= "嘿嘿";
EntityBase.Update(xxxCase);
//3、删除
xxxCase.ClearInnerData();
xxxCase.CaseId 
= 1;
EntityBase.Delete(xxxCase);
//4、复杂条件查询,查询大于昨天的客服投诉或者wawa关闭的问题
WhereCondition condition = new WhereCondition(
    xxxCase.CaseFromColName,SqlOperator.Equal, (
short)CaseFrom.客服投诉)
    .And(
    
new WhereCondition(xxxCase.CreateTimeColName, SqlOperator.GreaterThan ,
        DateTime.Now.AddDays(
1)))
    .Group()
    .Or(
    
new WhereCondition(xxxCase.CloseUserColName, SqlOperator.Equal, "wawa"));
IList
<xxxCase> list = EntityBase.Select<xxxCase>(
    
new string[] {"Title""PostUser"}, condition);
foreach (xxxCase item in list)
{
    Console.WriteLine(
"{0}-{1}",item.Title,item.PostUser);
}
Console.ReadKey();

  上面的代码是以面向对象(请忽略那些关于贫血模型的讨论,说上面的代码不够OO,上面的代码至少相对的面向对象,而且看起来很直观)的方式去执 行一些业务,这应该比到处写SQL语句要强很多吧,而且如果这些操作内部使用的仍然是参数化查询而不是拼sql字符串的话,性能也不会很差(请忽略具体语 句是否能使用索引的讨论,那得具体分析)。

  我们看一下EntityBase.Insert方法的实现,逻辑很简单明了,其他的Update,Delete,Select也是类似的思路。

 

private static DbHelper _db = new DbHelper();
public static void Insert(EntityBase entity) {
    
string sql = GetInsertSql(entity);
    
string[] parameters = GetParameters(entity.InnerData);
    
object[] parameterValues = GetParameterValuess(entity.InnerData);
    _db.SqlExecuteNonQuery(sql, parameters, parameterValues);
}
private static string GetInsertSql(EntityBase entity) {
    
int len = entity.InnerData.Count;
    StringBuilder sql 
= new StringBuilder();
    sql.AppendFormat(
"Insert INTO [{0}]\r\n", entity.TableName);
    sql.Append(
"(\r\n");
    
for (int i = 0; i < len; i++) {
        
if (i != len  1)
            sql.AppendFormat(
"[{0}],", entity.InnerData[i].Key);
        
else
            sql.AppendFormat(
"[{0}]", entity.InnerData[i].Key);
    }
    sql.Append(
")\r\n");
    sql.Append(
"VALUES(\r\n");
    
for (int i = 0; i < len; i++) {
        
if (i != len  1)
            sql.AppendFormat(
"@{0},", entity.InnerData[i].Key);
        
else
            sql.AppendFormat(
"@{0}", entity.InnerData[i].Key);
    }
    sql.Append(
")\r\n");
    
return sql.ToString();
}
private static string[] GetParameters(IList<DbCommonClass<stringobject>> items) {
    
int len = items.Count;
    List
<string> parameters = new List<string>();
    
for (int i = 0; i < len; i++) {
        parameters.Add(
string.Format("@{0}", items[i].Key));
    }
    
return parameters.ToArray();
}
private static object[] GetParameterValuess(List<DbCommonClass<stringobject>> items) {
    
int len = items.Count;
    List
<object> parameters = new List<object>();
    
for (int i = 0; i < len; i++) {
        parameters.Add(items[i].Value);
    }
    
return parameters.ToArray();
}

当然Select方法稍微复杂一些,因为我们要考虑复杂的Where字句,Top字句,OrderBy字句等,我们为Where字句建立了一个 WhereCondition对象,来方便的用c#代码来描述SQL的where语句,但是为了实现简单,我们不去实现表连接,复杂的子语句等支持(我个 人认为向NBear等框架做的过于强大了)。

三、代码生成器

  ADO.NET的各种数据库实现都有获取某个数据库Schema的API,其中最重要的是 SqlConnection.GetSchema(SqlClientMetaDataCollectionNames.Tables)和 SqlCommand.ExecuteReader( CommandBehavior.KeyInfo | CommandBehavior.CloseConnection)方法,有了这两个方法,我们可以枚举一个数据库的所有表,及某个表的所有字段,及每个 字段的类型,长度、可否为空,是否为主键,是否为标识列等信息,有了这些元数据,我们再根据一个模板就可以生成特定格式的代码了。而且我们需要新增加一种 代码生成的格式的话,只需添加一个模板就可以了,这样的代码生成器还有扩展性,而不是一个写死的针对特定框架的代码生成器。
  为了脱离对特定数据库的依赖,我们建立一个代码生成器的元数据模型,如下

public class CodeModel
{
 
public string ClassName;
 
public string TableName;
 
public string Descript;
 
public string Namespace;
 
public string PkColName;
 
public List<CodeProperty> Properties;
}
public class CodeProperty
{
 
public string DbColName;
 
public int? DbLength;
 
public bool DbAllowNull
 
public SqlDbType DbType;
 
public string DbTypeStr;
 
public bool DbIsIdentity;
 
public bool DbIsPk;
 
 
public string Descript;
 
public string PropertyName;
 
public System.Type CSharpType;
 
public string CSharpTypeStr;
 
 
public bool UiAllowEmpty;
 
public bool UiIsShowOn;
 
public long? UiMaxCheck;
 
public long? UiMinCheck;
 
public string UiRegxCheck;
}

得到元数据后,剩下的就是读取模板,然后替换字符串了,比如实体类的模板,如下

using System;
using System.Collections.Generic;
using WawaSoft.Common;
namespace $model.namespace$ {
    
public class $model.classname$ : EntityBase {
$
foreach.prop$
        
public const string $prop.property$ColName = "$prop.dbcolname$";
$endforeach$    
        
private static readonly List<string> _Cols = new List<string>();
        
static $model.classname$()
        {            
$
foreach.prop$
            _Cols.Add($prop.property$ColName);
$endforeach$            
        }
        
public $model.classname$() {
            _tableName 
= "$model.tablename$";
            _PkName 
= "$model.pkcolname$";            
        }
$
foreach.prop$
        
private $prop.csharptype$ $prop.property2$;
$endforeach$
 
$
foreach.prop$
        
public $prop.csharptype$ $prop.property$ {
            
get { return $prop.property2$; }
            
set {
                $prop.property2$ 
= value;
                AddInnerData(
"$prop.property2$", value);
            }
        }
$endforeach$
        
protected override IList<string> Cols
        {
            
get { return _Cols; }
        }
        
public override void ConvertToEntity(IEnumerable<DbCommonClass<stringobject>> items) {
            
foreach (DbCommonClass<stringobject> item in items) {
                
switch (item.Key) {
$
foreach.prop$
                    
case $prop.property$ColName:
                        $prop.property2$ 
= ($prop.csharptype$)item.Value;
                        
break;
$endforeach$
                }
            }
        }
    }
}

生成的实体类,如下

using System;
using System.Collections.Generic;
using WawaSoft.Common;
namespace Entities {
    
public class User : EntityBase {
        
public const string UserIdColName = "UserId";
        
public const string UsernameColName = "Username";
        
public const string NameColName = "Name";
        
public const string PasswordColName = "Password";
        
public const string CreateTimeColName = "CreateTime";
        
public const string IsAdminColName = "IsAdmin";
        
private static readonly List<string> _Cols = new List<string>();
        
static User() {
            _Cols.Add(UserIdColName);
            _Cols.Add(UsernameColName);
            _Cols.Add(NameColName);
            _Cols.Add(PasswordColName);
            _Cols.Add(CreateTimeColName);
            _Cols.Add(IsAdminColName);
        }
        
public User() {
            _tableName 
= "User";
            _PkName 
= "UserId";
        }
        
private Nullable<Int32> userid;
        
private String username;
        
private String name;
        
private String password;
        
private Nullable<DateTime> createtime;
        
private Nullable<Boolean> isadmin;
        
public Nullable<Int32> UserId {
            
get { return userid; }
            
set {
                userid 
= value;
                AddInnerData(
"userid", value);
            }
        }
        
public String Username {
            
get { return username; }
            
set {
                username 
= value;
                AddInnerData(
"username", value);
            }
        }
        
public String Name {
            
get { return name; }
            
set {
                name 
= value;
                AddInnerData(
"name", value);
            }
        }
        
public String Password {
            
get { return password; }
            
set {
                password 
= value;
                AddInnerData(
"password", value);
            }
        }
        
public Nullable<DateTime> CreateTime {
            
get { return createtime; }
            
set {
                createtime 
= value;
                AddInnerData(
"createtime", value);
            }
        }
        
public Nullable<Boolean> IsAdmin {
            
get { return isadmin; }
            
set {
                isadmin 
= value;
                AddInnerData(
"isadmin", value);
            }
        }
        
protected override IList<string> Cols {
            
get { return _Cols; }
        }
        
public override void ConvertToEntity(IEnumerable<DbCommonClass<stringobject>> items) {
            
foreach (DbCommonClass<stringobject> item in items) {
                
switch (item.Key) {
                    
case UserIdColName:
                        userid 
= (Nullable<Int32>)item.Value;
                        
break;
                    
case UsernameColName:
                        username 
= (String)item.Value;
                        
break;
                    
case NameColName:
                        name 
= (String)item.Value;
                        
break;
                    
case PasswordColName:
                        password 
= (String)item.Value;
                        
break;
                    
case CreateTimeColName:
                        
if (item.Value != DBNull.Value)
                            createtime 
= (Nullable<DateTime>)item.Value;
                        
break;
                    
case IsAdminColName:
                        
if (item.Value != DBNull.Value)
                            isadmin 
= (Nullable<Boolean>)item.Value;
                        
break;
                }
            }
        }
    }
}

小结

解决了以上几个问题,再开发数据库应用,应该会提高不少效率。
相关代码下载:code_wawa.zip

[C#]生成器(抽象工厂加存储过程)

mikel阅读(717)

首先建立一个Exam解决方案

在添加下面的类库

打开生成器

填写好后  安生成(注意  是多线程的   速度很快)

 

点  打开文件夹  里面生成的文件夹对应下面的文件 复制到项目里面就可以了

我时间有限  只写了 增 、删、改、查。  自己可以扩展  注释都写好了的

下面是生成的存储过程

下载地址:/Files/Evans/生成器r.rar

[C#]无缝的缓存读取:双存储缓存策略

mikel阅读(797)

最近在做一个WEB的数据统计的优化,但是由于数据量大,执行一次SQL统计要比较长的时间(一般700ms算是正常)。

正常的做法只要加个缓存就好了。

但是同时业务要求此数据最多1分钟就要更新,而且这一分种内数据可能会有较多变化(而且原系统不太易扩展)。

也就是说缓存1分钟就要失效重新统计,而且用户访问这页还很是频繁,如果使用一般缓存那么用户体验很差而且很容易造成超时。

 

看到以上需求,第一个进入我大脑的就是从前做游戏时接触到的DDraw的双缓冲显示方式。

image

在第一帧显示的同时,正在计算第二帧,这样读取和计算就可以分开了,也就避免了读取时计算,提高了用户体验。

我想当然我们也可以将这种方式用于缓存的策略中,但这样用空间换取时间的方式还是得权衡的,因为并不是所有时候都值得这么做,但这里我觉得这样做应该是最好的方式了。

注:为了可以好好演示,本篇中的缓存都以IEnumerable的形式来存储,当然这个文中原理也可以应用在WebCache中。

这里我使用以下数据结构做为存储单元:

namespace CHCache {
/// <summary>
/// 缓存介质
/// </summary>
public class Medium {
/// <summary>
/// 主要存储介质
/// </summary>
public object Primary { get; set; }
/// <summary>
/// 次要存储介质
/// </summary>
public object Secondary { get; set; }
/// <summary>
/// 是否正在使用主要存储
/// </summary>
public bool IsPrimary { get; set; }
/// <summary>
/// 是否正在更新
/// </summary>
public bool IsUpdating { get; set; }
/// <summary>
/// 是否更新完成
/// </summary>
public bool IsUpdated { get; set; }
}
}
   有了这个数据结构我们就可以将数据实现两份存储。再利用一些读写策略就可以实现上面我们讲的缓存方式。

整个的缓存我们使用如下缓存类来控制:

/*
* http://www.cnblogs.com/chsword/
* chsword
* Date: 2009-3-31
* Time: 17:00
*
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace CHCache {
/// <summary>
/// 双存储的类
/// </summary>
public class DictionaryCache : IEnumerable {
/// <summary>
/// 在此缓存构造时初始化字典对象
/// </summary>
public DictionaryCache()
{
Store = new Dictionary<string, Medium>();
}
public void Add(string key,Func<object> func)
{
if (Store.ContainsKey(key)) {//修改,如果已经存在,再次添加时则采用其它线程
var elem = Store[key];
if (elem.IsUpdating)return;  //正在写入未命中
var th = new ThreadHelper(elem, func);//ThreadHelper将在下文提及,是向其它线程传参用的
var td = new Thread(th.Doit);
td.Start();
}
else {//首次添加时可能也要读取,所以要本线程执行
Console.WriteLine("Begin first write");
Store.Add(key, new Medium {IsPrimary = true, Primary =  func()});
Console.WriteLine("End first write");
}
}
/// <summary>
/// 读取时所用的索引
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object this[string key] {
get {
if (!Store.ContainsKey(key))return null;
var elem = Store[key];
if (elem.IsUpdated) {//如果其它线程更新完毕,则将主次转置
elem.IsUpdated = false;
elem.IsPrimary = !elem.IsPrimary;
}
var ret = elem.IsPrimary ? elem.Primary : elem.Secondary;
var b = elem.IsPrimary ? " from 1" : " form 2";
return ret + b;
}
}
Dictionary<string, Medium> Store { get; set; }
public IEnumerator GetEnumerator() {
return ((IEnumerable)Store).GetEnumerator();
}
}
}

这里我只实现了插入一个缓存,以及读取的方法。

我读取缓存单元的逻辑是这样的

image 

从2个不同缓存读取当然是很容易了,但是比较复杂的就是向缓存写入的过程:

image

这里读取数据以及写入缓存时我使用了一个委托,在其它线程中仅在需要执行时才会执行。

这里除了首次写入缓存占用主线程时间(读取要等待)以外,其它时间都可以无延时的读取,实现了无缝的缓存。

但我们在委托中要操作缓存的元素Medium,所以要传递参数进其它线程,所以我这里使用了一个辅助类来传递参数进入其它线程:

using System;
namespace CHCache {
/// <summary>
/// 一个线程Helper,用于帮助多抛出线程时传递参数
/// </summary>
public class ThreadHelper {
Func<object> Fun { get; set; }
Medium Medium { get; set; }
/// <summary>
/// 通过构造函数来传递参数
/// </summary>
/// <param name="m">缓存单元</param>
/// <param name="fun">读取数据的委托</param>
public ThreadHelper(Medium m,Func<object> fun) {
Medium = m;
Fun = fun;
}
/// <summary>
/// 线程入口,ThreadStart委托所对应的方法
/// </summary>
public void Doit()
{
Medium.IsUpdating = true;
if (Medium.IsPrimary) {
Console.WriteLine("Begin write to 2.");
var ret = Fun.Invoke();
Medium.Secondary = ret;
Console.WriteLine("End write to 2.");
}
else {
Console.WriteLine("Begin write to 1.");
var ret = Fun.Invoke();
Medium.Primary = ret;
Console.WriteLine("End write to 1.");
}
Medium.IsUpdated = true;
Medium.IsUpdating = false;
}
}
}

这样我们就实现了在另个线程读取数据的过程,这样就在任何时候读取数据时都会无延时直接读取了。

最后我们写一个主函数来测试一下效果

/*
* http://www.cnblogs.com/chsword/
* chsword
* Date: 2009-3-31
* Time: 16:53
*/
using System;
using System.Threading;
namespace CHCache
{
class Program
{
public static void Main(string[] args)
{
var cache = new DictionaryCache();
Console.WriteLine("Init...4s,you can press the CTRL+C to close the console window.");
while (true)
{
cache.Add("1", GetValue);
Thread.Sleep(1000);
Console.WriteLine(cache["1"]);
}
}
/// <summary>
/// 获取数据的方法,假设是从数据库读取的,费时约4秒
/// </summary>
/// <returns></returns>
static object GetValue()
{
Thread.Sleep(4000);
return DateTime.Now;
}
}
}

得到如下数据:

image

这样就实现了平滑的读取缓存数据而没有任何等待时间

当然这里还有些问题,比如说传递不同参数时的解决方法,但是由于我仅是在一个统计时需要这种缓存提高性能,所以暂没有考虑通用的传参方式。

如果大家对这个话题感兴趣,欢迎讨论。

源码下载:点击下载