做程序主要体现的是细节,细节方面做的好,用户会觉得你的软件比较专业。良好的架构与设计模式可以使维护方便。在用户这边,要花一些心思琢磨用户的想法。有时候用户比我们的界面设计还专业,用户有很强的行业知识,他见识过的同行业的软件比我们多。下面的这几点界面设计的体会和经验,来自于用户,在这里与大家分享。

1 如果要写数据项到注册表中,最好以加密的形式保存

虽然.NET提倡的是XCOPY,但有时还是需要写一些必要的信息到注册表中,以减少用户的配置出错的可能性。比如,自动更新功能中,需要把自动更新的网址写到注册表中。ASP.NET Web程序如果不使用默认的80端口,也推荐在安装程序时把Port写到注册表中。对于需要用户反馈的程序,一种默认方法是把用户的feedback内容发送到邮箱中,这时需要提供邮箱的注册用户名和密码,这种情况是必须要加密的。

image

有时候不小心看了一下自己的注册表(regedit)内容,也是惨不忍睹,大量的程序都会写数据项到注册表中。

2 界面划分的基本原则是信息分类,将同类的信息放在一起,集中显示。

image

这是物料主档的功能的界面,先分大类,用Tabcontrol分开,基本信息,环保指令,包装规格,计划数据,附件信息。大类里面再小的类别,用GroupBox分开,比如控制点,规格。

image

规格这个地方,使用了ComboBox,这个有待于商量。当选择的数据项比较多时,应当使用ComboBox,数据项比较少时,我以为用RadioButton要合适一些,这也取决于空间的大小程度。

image

这个图片中有两组Item Type,上面一组是ComboBox的翻版,下面一组对文字进行了简化调整。

使用ComboBox的好处是,当它的item发生变化时,界面不需要变动,而RadioButton做不到这一点,也许这是选择ComboBox的一个很好的理由。

对信息分类导向的界面设计国中,菜单项的深度也应该尽量保持在三层以内,对相同的功能进行分类。请看下图

image

只用了二层,相同的功能菜单里面,还可以用分割符号分开,以增加可读性。

 

3 界面中的名词选择尽量选择专业的词汇,避免通用的词汇。比如以前我做ERP系统控制面板,总会加上Managment这个词语,如Sales Management/Purchasing Management以表达销售管理,采购管理,Management这个词语应该去掉。在很多信息分类的TabControl中,通常第一个tab page的名字叫General或者上面例子中的Basic Information,或General Information,当系统中有多个这样的界面布局时,应该保持一致。比如上面的物料主档用了Basic Information,其它的地方,在基本信息的tab page中也应该用Basic Information。举例说明

采购收货的界面,第一个tab page使用General Information。

image

物料清单的界面,第一个tab page也使用了General Information。

image

 

4  界面设计时字体的选择。
以我的选择,功能界面中使用Microsoft Sans Serif, 8.25pt字体。状态栏(status)和菜单(menu)使用默认的Segoe UI, 9pt。对于功能界面,除了规定程序员在设计时使用指定的字体外,还在运行时,循环遍历一下界面中的界面控件,对不是标准字体的控件予以调整。字体统一,界面才看起来整洁,美观。

不推荐在控件中使用特殊的字体,尽管你可以在部署的时候把字体也拷贝到目标机器中。Crystal Report的报表中,需要使用一些特殊的字体,运行报表前应该检测字体是否存在。比如你可以把字体以嵌入的资源的方法嵌入到程序集中,如果目标机器的指定的字体不存在,可以从程序集的资源中释放字体到字体文件夹中。

 

5 对可能造成数据无法恢复的操作必须提供确认信息,提供用户放弃选择的机会。比如删除数据,退出功能时没有保存数据,耗费很长时间的MRP运算。这在开始操作前,要给用户有反悔的选择。
对于删除确认,有一种情况,就是对用户的设计予以认可,充分信任。比如用户在设计菜单界面时,要删除一条菜单,这时,不应该弹出确认删除的对话框。Visual Studio中删除一项MenuStripItem时,没有要我们确认删除。

image

正准备Delete菜单项中的New项,Visual Studio会认可你的操作,没有要你确认之后再删除。

因为这个理由,我在自定义的Menu Designer,Form Designer,Workflow Designer中都实行这个策略,充分信任用户的操作。

posted @ 2012-01-06 09:17 James Li 阅读(2156) 评论(9) 编辑

时间过得真快,Visual Studio 2010推出来快两年了,.NET 4.0也逐渐普及。.NET 4里面有一项重要的功能,就是动态语言,可以在.NET代码中直接调用第三方的动态语言脚本,请参考下面的例子程序

ScriptRuntime py = Python.CreateRuntime();
//Dynamic feature only works on objects typed as 'dynamic'
dynamic helloworld = py.UseFile("helloworld.py");

Console.WriteLine("helloworld.py loaded!");
for (int i = 0; i < 1000; i++)
{
      Console.WriteLine(helloworld.welcome("Employee #{0}"), i);
}

这是Visual Studio 2010中使用IronPython的例子,为了运行这个程序,请下载IronPython, 例子中的的源代码如下

def welcome(name):
      return "Hello '" + name + "' from IronPython"

学到了这么多技术,最终都是为了改善产品,改善系统的可维护性。在之前的文章中我提到为应用程序添加脚本功能,可产生定制的动态的代码块,然后返回执行结果。这项功能为应用程序的灵活性提供了极大的方便。我在工作流的表达式求值,工资公式编辑器中有提到动态脚本Script.NET的能力。然而Script.NET停止开发很久了,而且我一直期盼的调试功能又没有实现出来。自己参考SharpDevelop做script debugger,没有结果,只好暂时停止对Script.NET的进一步探索与应用。现有IronPython 2.7.1 已经直接做成了Visual Studio的解决方案项目,简单方便省事。

image

几乎不用任何配置,直接安装下载回到的IronPython-2.7.1安装文件即可,它会为你的Visual Studio创建Python项目模板。在x86的电脑上,可以产生四个类型的项目模板,Console Application,WinForms Application,IronPython Silverlight Web Page,Wpf Application,而在x64的平台上,只产生了Console Application项目模板。

 

但是,这样还有点不方便,Python在这里被当成脚本语言,它的Build Action=None,意味着没有任何编译行为,这样会给代码的调试工作带来不方便之处。需要安装工具Python Tools for Visual Studio。

先卸载已经安装的IronPython工具包,才能继续安装Python Tools for Visual Studio。安装完成之后,项目模板又多了二项模板,Python Application和Python MPI Application。新建立一个Python Application项目

image

请先到Tools->Options的Python Tools中新建一个Interpreter,在Interpreter Options中点击Add Interpreter按钮,保存退出即可。然后就可直接在编辑器中放断点,调试的体验和调试.NET语言一样,Call Stask,Intermediate Window,Watch窗口都可用,这种感觉是相当的方便。再看看Program.py的文件属性,它的Build Action=Compile。

在安装工具包的同时,也同时会安装Python的命令行交互程序,这个程序可以当计算器用,直接输入表达式的值,它可以马上帮助你计算出结果。再来看看下面的Python的两个例子代码,以帮助你了解和学习这种语言。

 

Lambda表达式,这个可以简化代码的编写,请看Python的实现

func = lambda s: s * 3
print func("peter ")  

func2 = lambda a, b: a * b
print func2(2, 3)  

调试这段代码,在Output窗口可以显示它的执行结果。Lambda表达式减少了很多不必要的代码。

 

Python的for循环语句,它的一种写法是这样的

for i in range(0, 10,2):
    print i

起始变量0,10是终止变量(不包括10),2是步长。.NET 4中引入了并行循环,它的代码例子如下

Parallel.For(0, Int64.MaxValue, i =>
            {
                 
                 Console.WriteLine("i={0},thread id={1}", i, Thread.CurrentThread.ManagedThreadId);
                 Thread.Sleep(1000);
            });
 

整理一下文章的思路,您首先要选择一门脚本语言,来为你的应用程序提供动态代码执行能力。在这里,我选择Python语言,并选择它的.NET实现版本IronPython。其次,我要对脚本与.NET host之间的代码互操作有所了解,如何把.NET的变量传到脚本中,如何传回脚本的执行结果到.NET代码中。最后,我需要一个灵活方便的脚本编辑工具,并且可以提供强大的调试功能。

posted @ 2012-01-05 09:25 James Li 阅读(1227) 评论(11) 编辑

从毕业做.NET到现在,有好几年了,自认为只能是达到熟练的水平,谈不上精通。所以,总结一下,自己到底熟练掌握了哪些.NET方面的开发技术,以此对照,看看还有哪些不足,欢迎补充。

1 .NET Framework常见的API要熟练掌握。有些API可能需要多个类型配合使用,也有必要掌握。

IEntity2 clonedEntity = null;
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream memStream = new MemoryStream())
{
         formatter.Serialize(memStream, sourceEntity);
          memStream.Seek(0, SeekOrigin.Begin);
          clonedEntity = (IEntity2)formatter.Deserialize(memStream);
}
 

这个例子是.NET的深拷贝(deep copy)的实现,类似于这样的例子,还有很多。这些API的组合应用是需要掌握的。经过积累后,通常都会有自己的一个Utility类型库。

2 Linq to Object。虽然Linq to SQL已经被抛弃和遗忘,但是Linq to Object还真是一项很重要的技术。如果没有这项技术,数据的查找和操作的代码会被foreach充满,这样不容易维护,而且有很多代码都是routine代码,可以省略的。

string[] names = { "Tom","Dick","Harry","Mary","Jay" };
IEnumerable<string> query =
                 from    n in names
      where   n.Contains ("a")  // Filter elements
      orderby n.Length          // Sort elements
      select  n.ToUpper( );     // Project each element

foreach (string name in query)
     Console.Write (name + "/");

同时,与Linq搭配的技术Lambda技术,可以简化很多代码,这也需要掌握,上面的例子用Lambda技术改写一下

string[] names = { "Tom","Dick","Harry","Mary","Jay" };
IEnumerable<string> query = names
      .Where   (n => n.Contains ("a"))
      .OrderBy (n => n.Length)
      .Select  (n => n.ToUpper( ));

foreach (string name in query)
     Console.Write (name + "|");

曾经有个非常流行的考试题目,考察委托的三种写法,举例如下所示

List<int> list = new List<int>();
list.AddRange(new int[] { 1, 5, 10, 20 ,33 });
 
//使用传统委托语法调用FindAll
Predicate<int> callback = new Predicate<int>(IsEvenNumber);
List<int> evenNumbers = list.FindAll(callback);

//使用匿名方法
List<int> evenNumbers = list.FindAll(
               delegate(int i)
                {
                    return (i % 2) == 0;
                });

//使用Lambda表达式
List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);
(代码来自于LINQ之路 3:C# 3.0的语言功能(下)

 

3  .NET 4引入了新的并行编程库。这项技术不同于多线程技术,它是适应多核时代的需要。来看下面的例子程序

static double ParallelPi()
{
        double sum = 0.0;
        double step = 1.0 / (double)num_steps;
        object monitor = new object();
        Parallel.For(0, num_steps, () => 0.0, (i, state, local) =>
        {
            double x = (i + 0.5) * step;
            return local + 4.0 / (1.0 + x * x);
        }, local => { lock (monitor) sum += local; });
        return step * sum;
}

有了新面的Lambda表达式基础,这个例子也不难读懂。这是用并行库求PI的值。

.NET 4还引入了动态语言,如下的例子所示,Python的例子

ScriptRuntime py = Python.CreateRuntime();
//Dynamic feature only works on objects typed as 'dynamic'
dynamic helloworld = py.UseFile("helloworld.py");
Console.WriteLine("helloworld.py loaded!");

for (int i = 0; i < 1000; i++)
{
       Console.WriteLine(helloworld.welcome("Employee #{0}"), i);
}

//Python的代码
def welcome(name):
    return "Hello '" + name + "' from IronPython"

如果要改善.NET程序的性能,可以考虑使用.NET并行库。如果你在寻找脚本语言,可以寄宿到.NET中方便调用,这里的Python应该是一个很好的开始。对于.NET框架直接内置的技术,必定会有大量的第三方工具,产品来对它做Enhancement或Extension,你可以找到很称手的工具,为你的项目增加灵活性和改善性能。

 

4 ORM开发技术。可以选择NHibernate,也可以选择Entity Framework。ORM技术可以让你的代码专注于业务逻辑,大大简化数据访问代码的编码与调试。此外,ORM技术支持界面与逻辑分离,强类型的数据绑定,这些好处,都可以给你的项目增加灵活性。比如保存客户的代码,用ORM技术写,是这样的

using (DataAccessAdapterBase adapter =GetDataAccessAdapter())
{
    try
    {
        adapter.StartTransaction(IsolationLevel.ReadCommitted, "SaveCustomer");
        adapter.SaveEntity(Customer, true, false);
        adapter.Commit();
    }
    catch
    {
        adapter.Rollback();
        throw;
    }
}

可不能小看了这几句,首先它是用代码生成器生成的,其次,对于增改数据库字段,这里不用作任何的修改,再次,界面的变动和业务逻辑的变动,也不会影响到这里。就这几项好处,可以节约大量的时间,让你专注于业务逻辑。

 

5  分布式的通讯技术。.NET Remoting和WCF,至少要掌握一项才行。

6  界面组件包。.NET 框架自带的界面控件虽然简单好用,但不够强大。所以,推荐你选购一套流行的界面控件,为你的项目增加可读性。虽然都是说界面不重要,逻辑重要,但是我们心里也明白,界面看起来惨不忍睹,再好的逻辑和架构也也不会被客户接受。界面要做到简单,实用,说起起容易,做起来可相当不容易。这里可以选择的控件比较多,Infragistics,Syncfusion,ComponentOne,都是很著名的控件供应商。

posted @ 2011-12-31 09:05 James Li 阅读(6090) 评论(55) 编辑

公司的ERP系统是采用Infragistics的控件,这个系列的组件非常庞大,功能很多。在学习的过程中,经常需要查找它的Sample来了解它的属性,经过一段时间的研究,就想到把它的Sample重新组织一下以方便学习。有些Sample是VB写的,只熟悉C#程序,VB可以看懂但不能用于编程开发,于是就用VB Converter转换为C#代码

image

点击Convert Anywan进行转换,转换正确率达99%,遇到索引(index)转换需要手工修改,比如C#中DataRow drow[“CustomerId”] 这样引用列,转换后是的结果是drow(“CustomerId”)。

于是乎,三下五除二就把它的例子程序转移到了C#的解决方案中,形成我的Infragistics Solution。

image

这样集中学习它的例子,下次忘记了查阅这个解决方案文件和记得笔记代码,效果不错。

有一个地方还需要改善一下,Infragistics 公司提供的NetAdvatange Sample是一个个独立的解决方案文件。这在安装程序,维护版权方面是很有用的,但是不利于学习研究。它的每一个Sample中都有这样的代码

[STAThread]
static void Main() 
{
     Application.Run(new Form1());
}

Form1看起来并不刺眼,通常在Sample程序中,这表示是没有任何修改的通过Visual Studio模板生成的窗体。

比较麻烦的是,经过我的合并与添加,最后形成这样一个文件佳结构,以UltraGrid为例子

image

Intermediate Tutorial2中还有更多的例子,如果要写代码调用它们,可能要写很多个new Form1的代码,而且要经常修改启动窗体,这样显然不合理,有些麻烦。在熟练应用了反射技术后,自然就想到了动态生成类型与调用。

添加一个MainForm作为启动窗体,添加treeView控件,以显示Sample中的Form,在Form_Load中增加代码

private void InfragisticsSolutionForm_Load(object sender, EventArgs e)
{
     Assembly assembly = Assembly.GetExecutingAssembly();
     TreeNode root = tree.Nodes.Add("Infragistics Solution");
     foreach (Type type in assembly.GetTypes())
     {
            TreeNode node= root.Nodes.Add(type.FullName);
             node.Tag = type;
      }
      root.ExpandAll();
}

这样就把这个解决方案中的Form都添加到了窗体树中,生成的窗体效果如下图所示

image

再来设计启动Sample窗体的代码,在treeView的MouseDoubleClick和Call按钮的点击事件中调用如下的代码

if (tree.SelectedNode.Tag != null )
{
      Type type = tree.SelectedNode.Tag as Type;             
      Form frm = Activator.CreateInstance(type) as Form;
      if (frm != null)
      {
           frm.Show(this);
      }
}

这样就轻松的完成了Sample窗体的启动。还有一个地方值得改善,每次点击Call 按钮之后,都会new窗体,这会消耗大量的内存,Form没有显示在用户界面中,并不表示它已经从内存中被GC当成垃圾回收,经过改良后的代码如下

if (tree.SelectedNode.Tag != null )
{
        Type type = tree.SelectedNode.Tag as Type;
        Form frm=Application.OpenForms[type.Name];
        if (frm != null)
        {
             if(!frm.Visible)
                 frm.Show(this);
             return;
        }
        frm = Activator.CreateInstance(type) as Form;
        if (frm != null)
        {
            frm.Show(this);
        }
}

先从Application.OpenForms查找已经开启的窗体,如果有就把它显示出来。这个代码有个问题,type.Name为Form1,如果其它的窗体也叫这个名字Form1,尽管他们在不同的命名空间中。这样,其它的Form1名称的窗体都无法打开,如果改进type.FullName,则frm每次都是null,像这样修改过之后

Form frm=Application.OpenForms[type.FullName];

即使窗体已经显示出来了,frm的值也是null。查找MSDN文档,Name定义就是取自InitializeComponent

private void InitializeComponent()
{
     this.Name = "Form1";
}

这种缓存的办法是失效的,无法达到节约内存的目的。 经过数次的调整与修改,最后的代码如下

 if (tree.SelectedNode.Tag != null )
 {
          Type type = tree.SelectedNode.Tag as Type;
          Form frm = null;
          if (_types.Contains(type))
          {               
                foreach (Form t in _forms)
                {
                    if (t.GetType().FullName.Equals(type.FullName))
                    {
                          frm = t;
                          break;
                    }
                }
                if(frm.IsDisposed)
                    frm = Activator.CreateInstance(type) as Form;
                if (!frm.Visible)
                    frm.Show(this);
                  
               return;
         }

         frm = Activator.CreateInstance(type) as Form;
         if (frm != null)
         {
             frm.Show(this);
             _types.Add(type);
             _forms.Add(frm);
          }
}

两个集合分别用来存放缓存的对象和实例子,定义如下

List<Form> _forms;
List<Type> _types;      

希望能对你有帮助。

           
posted @ 2011-12-29 09:45 James Li 阅读(312) 评论(0) 编辑

继续讲解LLBL Gen的开发教程,这一篇学习Linq to LLBL Gen的应用。

MSDN对Linq的解释如下:LINQ(语言级集成查询)的意图就是提供一种统一且对称的方式,让程序员在广义的数据上获取和操作数据。虽然Linq to SQL已经不再更新,但是Linq to xml,Linq to Object仍然很实用,它可以简化程序代码的编写。在没有Linq的.NET 2.0时代,通常只能用foreach循环遍历来查找满足条件的数据。

LinqMetaData

如果要在LLBL Gen的项目中使用Linq,首先要保证你的项目属性是.NET 3.0/3.5以上,这会调用Linq的模板生成需要的元数据。在生成的DatabaseGeneric中会产生Linq的文件夹,并且增加类型LinqMetaData。它的定义如下所示

public partial class LinqMetaData: ILinqMetaData
{
     #region Class Member Declarations
      private IDataAccessAdapter _adapterToUse;
      private FunctionMappingStore _customFunctionMappings;
      private Context _contextToUse;
      #endregion
        
     /// <summary>CTor. Using this ctor will leave the IDataAccessAdapter object to use empty. 
/// To be able to execute the query, an IDataAccessAdapter instance
/// is required, and has to be set on the LLBLGenProProvider2 object in the query to execute.
///</summary>
public LinqMetaData() : this(null, null) { } }

这里省略了一些方法和属性,请参考生成的类型来学习它的代码。要使用Linq,请传入当前项目的DataAccessAdapter

using(DataAccessAdapter adapter = new DataAccessAdapter())
{
    LinqMetaData metaData = new LinqMetaData(adapter);
    var q = from c in metaData.Customer
            where c.Country=="USA"
            select c;
    // enumerate over q here so it gets executed
}

为什么可以这样的写?因为LinqMetaData提供了Customers的集合属性,定义如下所示

/// <summary>returns the datasource to use in a Linq query when targeting CustomerEntity instances in the database.</summary>
public DataSource2<CustomerEntity> Customer
{
   get { return new DataSource2<CustomerEntity>(_adapterToUse, new ElementCreator(), 
                    _customFunctionMappings, _contextToUse); }
}

LLBL Gen提供了两种模式的代码调用方式:Adapter和SelfServicing,导致同一种类型要定义二次。比如SelfServicing中实体定义为IEntity,而Adapter则定义于IEntity2,在名称后面加一个数字2。以此类推,凡是类型名后面有数字2的,表示它适用于Adapter模式。Customer属性是定义一个数据源对象。

在这里,推荐安装测试工具软件TestDriven.NET,它的Personal版本完全免费。在任何一个.NET项目文件中,新建立一个方法,点击右键Test With->Debugger,很方便的调试一段代码。因为这个软件,我就再也没有使用很著名的代码片段编辑工具Snippet Compiler,TestDriven.NET会让带给你编写.NET测试代码非常便利的体验。

转化Linq 查询结果

使用Linq to LLBL Gen查询数据之后,如果要把数据绑定到界面中,则需要提供成Entity2或EntityCollection样式。

这里有ILLBLGenProQuery接口用于转化Linq查询结果,以减少我们自已转化的代码。

var q = from c in metaData.Customer 
where c.Country=="Germany"
select c; EntityCollection<CustomerEntity> customers = ((ILLBLGenProQuery)q).Execute<EntityCollection<CustomerEntity>>();

如代码所示,customers就可以直接用于绑定到界面的GridView或DataGridView中。

写到这里,发现Windows Live Writer的Insert Code有一个小bug:对Linq查询中的from/select关键字没有高亮显示。瑕不掩玉,这个Insert Code工具帮助我的博客中的规范化的代码方面产生了重要作用。

Group by 分组

按照国家和城市为组,对客户进行分类

var q = from c in metaData.Customer
        group c by new { c.Country, c.City } into g
        select new { g.Key, g.Count()};

按照客户的国家分类

var q = from c in metaData.Customer
        group c by c.Country into g
        select g;
 

Order By 排序

var q = from c in metaData.Customer
        orderby c.CustomerId[1]
        select c;

CustomerEntity的CustomerId属性是一个字符串,按照它的第二个字母排序。


Queryable: Contains 包含的查询

// Query 1, 检查实体是否在集合中 simple entity check in entity list
var q = from c in metaData.Customer
        where c.Orders.Where(o=>o.EmployeeId==3).Contains(order)
        select c;

// Query 2, 操作数是查询结果的实体集 operand is entity which is result of query
var q = from c in metaData.Customer
        where c.Orders.Contains(
                 (from o in metaData.Order 
                  where o.EmployeeId == 2 select o).First())
        select c;

// Query 3, 操作数和源都是查询operand and source are both queries.
var q = from c in metaData.Customer
        where c.Orders.Where(o => o.EmployeeId == 2).Contains(
                   (from o in metaData.Order 
                    where o.EmployeeId == 2 select o).First())
        select c;

// Query 4, 查询中的常量比较 constant compare with value from query. Yes this is different.
var q = from c in metaData.Customer
        where c.Orders.Where(o => o.EmployeeId > 3).Select(o => o.ShipVia).Contains(2)
        select c;

// Query 5, 检查常量表是否在查询结果中 check if a constant tuple is in the result of a query
var q = from c in metaData.Customer
        where c.Orders.Select(oc => new { EID = oc.EmployeeId, CID = oc.CustomerId }).Contains(
                         new { EID = (int?)1, CID = "CHOPS" })
        select c;

// Query 6, as 5 but now compare with a tuple created with a query
var q = from c in metaData.Customer
        where c.Orders.Select(oc => new { EID = oc.EmployeeId, CID = oc.CustomerId }).Contains(
                      (from o in metaData.Order where o.CustomerId == "CHOPS" 
                       select new { EID = o.EmployeeId, CID = o.CustomerId }).First())
        select c;

// Query 7, 检查实体的属性是否包含指定的常量集合
   checking if the value of a field in an entity is in a list of constants
List<string> countries = new List<string>() { "USA", "UK" };
var q = from c in metaData.Customer
        where countries.Contains(c.Country)
        select c;

// Query 8, 与7对比,换成IEnumerable。as 7 but now with an IEnumerable
LinkedList<string> countries = new LinkedList<string>(new string[] { "USA", "UK"});
var q = from c in metaData.Customer
        where countries.Contains(c.Country)
        select c;

// Query 9, combination of 2 queries where the first is merged with the second and
// only the second is executed. (this is one of the reasons why you have to write 
// your own Funcletizer code.
var q1 = (from c in metaData.Customer
          select c.Country).Distinct();
var q2 = from c in metaData.Customer
         where q1.Contains(c.Country)
         select c;

// Query 10, as 7 but now with an array obtained from another array.
string[][] countries = new string[1][] { new string[] { "USA", "UK" } };
var q = from c in metaData.Customer
        where countries[0].Contains(c.Country)
        select c;

// Query 11, complex contains query with comparison of in-memory object list
List<Pair<string, string>> countryCities = new List<Pair<string, string>>();
countryCities.Add(new Pair<string, string>("USA", "Portland"));
countryCities.Add(new Pair<string, string>("Brazil", "Sao Paulo"));

// now fetch all customers which have a tuple of country/city in the list of countryCities.
var q = from c in metaData.Customer
        where countryCities.Contains(
                   (from c2 in metaData.Customer
                    where c2.CustomerId == c.CustomerId
                    select new Pair<string, string>() 
                         { Value1 = c2.Country, Value2 = c2.City }).First())
        select c;

Linq的查询有些复杂,如果不能理解,可以在工作中需要用到的时候再来仔细体会。

 

Excluding / Including fields  不包含/包含字段

这个需求很重要。在写SQL语句时,不要写SELECT *,而是用具体的字段名,Excluding / Including 也就是用来指定需要SELECT出来的字段名。下面的查询,在查询结果集中不包括Photo和Notes字段。

var q = (from e in metaData.Employee
         select e).ExcludeFields(e=>e.Photo, e=>e.Notes);
 

Calling an in-memory method in the projection 在Linq投影中调用方法

/// Utility class which obtains value from a webservice
public class InMemoryCallExample
{
    public static int GetInterestRate(string customerId)
    {
         return MyService.GetInterestRate(customerId);
    }
}

// this class can now be used in the following query:
var q = from o in metaData.Order
        select new 
        {
            o.OrderId,
            InterestRate = InMemoryCallExample.GetInterestRate(o.CustomerId)
        };

在Linq的返回结果中调用方法,select new产生一个匿名对象,可以对它进行再调用方法处理加工。

也可以直接用Lambda表达式来完成这样的操作,例子代码如下所示

Func<string, string> stringChopper = s=>s.Substring(0, 3);

// this function can now be used in a query:
var q = from c in metaData.Customer
        select stringChopper(c.CompanyName); 

 

Prefetch paths 子查询

方法 1: WithPath and PathEdges

// query  A
var q =  from c in metaData.Customer.WithPath(...) select c;

// query  B
var q =  (from c in metaData.Customer select c).WithPath(...);

// query  C
var q =  (from c in metaData.Customer.WithPath(...) where ... select c) join o in  metaData.Order on ...

 

方法2:WithPath and Lambda expressions

var q = (from c in metaData.Customer
         where c.Country=="UK"
         select c).WithPath(p=>p.Prefetch(c=>c.Orders));
 

Function mappings 函数映射

从开头的一段代码中我们看到,Linq to LLBL Gen会产生直接的数据库查询动作。查询数据库时,ORM框架也会调用一些数据库系统的函数,比如SUM,AVG或是自定义的函数,这就要求有一种机制来定义函数的调用方法。

数据库函数定义代码如下

ALTER   FUNCTION fn_CalculateOrderTotal(@orderID int, @useDiscounts bit)
RETURNS DECIMAL
AS
BEGIN
    DECLARE @toReturn DECIMAL

    SELECT @toReturn = SUM((UnitPrice-(Discount * @useDiscounts)) * Quantity)
    FROM [Order Details] 
    WHERE OrderID = @orderID
    GROUP BY OrderID

    RETURN @toReturn
END

LLBL Gen的代码生成工具会对这个函数进行.NET封装调用,产生如下所示的代码以调用数据库中的fn_CalculateOrderTotal

public class NorthwindFunctions
{
     public static decimal CalculateOrderTotal(int orderId, bool useDiscounts)
    {
        // empty body, as it's just here to make the query compile. The call is converted to a SQL function.
        return 0.0M;
    }
}

public class NorthwindFunctionMappings : FunctionMappingStore
{
    public NorthwindFunctionMappings() : base()
    {
        this.Add(new FunctionMapping(typeof(NorthwindFunctions), "CalculateOrderTotal", 2, 
                        "fn_CalculateOrderTotal({0}, {1})", "Northwind", "dbo"));
    }
}

NorthwindFunctions类型会根据数据库中的函数,生成.NET调用代码。NorthwindFunctionMappings 则把这个函数转化为Linq to LLBL Gen的映射定义方式,最后看到应用代码是这样的

metaData.CustomFunctionMappings = new NorthwindFunctionMappings();
var q = from o in metaData.Order
             where o.CustomerId == "CHOPS"
             select new { o.OrderId, OrderTotal = NorthwindFunctions.CalculateOrderTotal(o.OrderId, true) };
 

Full-text search 全文索引

以上面为基础,实现Linq中的全文索引就比较容易,对于SQL Server,在自定义的数据库函数中调有数据Contains。

public class NorthwindFunctions
{
    public static bool FullTextSearch(string fieldToSearch, string toFind)
    {
        // empty body, as it's just here to make the query compile. The call is converted to a SQL function.
        return true;
    }
}

/// Class which defines the custom mapping between NorthwindFunctions.FullTextSearch and CONTAINS()
public class NorthwindFunctionMappings : FunctionMappingStore
{
    public NorthwindFunctionMappings() : base()
    {
         // FullTextSearch(2) on 1 field
         this.Add(new FunctionMapping(typeof(NorthwindFunctions), "FullTextSearch", 2, "CONTAINS({0}, {1})"));
    }
}

应用代码与前面的相同,在使用前设置CustomFunctionMappings

metaData.CustomFunctionMappings = new NorthwindFunctionMappings();
// fetch the employees which have 'BA' in their Notes field which is Full text search enabled.
var q = from e in metaData.Employee
        where NorthwindFunctions.FullTextSearch(e.Notes, "BA")
        select e;

经过这两个例子,就引伸出关于调用数据库中函数的方法,也就是如何调用我们经常用到的SQL函数SUM/AVG。请参考帮助文档中的Supported default method / property mappings to functions。

一般来说,调用数据库的函数比前面的调用自定义函数更简单,ORM框架会预先设置好这些函数的映射。对于SQL Server 2005及以上的版本,因为引入了CLR Host技术,会略有些不同。如果我们的SQL代码不涉及CLR 函数和类型,调用方法和前面的一样。如果有涉及,要确保添加正确的的Function Mapping。

posted @ 2011-12-28 09:14 James Li 阅读(959) 评论(1) 编辑
摘要: LLBL Gen作为ORM工具,有时候为了能生成一些基础的元数据,也需要了解它的对象及其之前的关系,这在通用的框架代码中的作用更加明显。举例说明,它生成的解决方案视图一般是这样的 现在有如下的需求需要满足,以提供基础的元数据,参考测试代码如下 string AssemblyFile = @"E:\Solution\Enterprise\Bin\Northwind.CRM.Business...阅读全文
posted @ 2011-12-27 09:30 James Li 阅读(731) 评论(0) 编辑
摘要: LLBL Gen作为项目开发的ORM框架,选择.NET Remoting作为分布式技术框架。一直也很想把ERP框架从.NET Remoting升级到WCF,只是关于方法重载的配置方法需要特殊处理。举例说明如下 public interface IEmployeeManager{ EmployeeEntity GetEmployee(System.Int32 Employeeid);...阅读全文
posted @ 2011-12-26 09:04 James Li 阅读(1629) 评论(1) 编辑
摘要: 公司的ERP框架是用ORM技术来访问数据库的,但有些查询还是会用DataTable保存数据,并且会把用户修改后的数据保存到服务器中。在习惯了ORM的写法后,对于用DataTable的保存用户修改过的数据,然后保存到数据库中反而有些不适应。ORM会自动检测到哪些数据项被改动了,进而生成必要的UPDATE子句,如果没有数据被更改,则不会产生任何UPDATE语句,这是ORM的好处与便利。把这个技巧应用到...阅读全文
posted @ 2011-12-23 17:59 James Li 阅读(1038) 评论(3) 编辑
摘要: 对于习惯于用ORM来开发系统的开发人员来说,几乎不用写SQL语句,但是也要针对ORM框架,来设计合适的查询,ORM框架会生成合适的T_SQL语句并发送到SQL Server中。由于ORM框架有好几种,比如NHibernate,LLBL Gen,Entity Framwork,掌握熟练的SQL查询技术在这里没有用武之地,真是可惜。这篇文章是介绍我的Management Console中的一个工具程序...阅读全文
posted @ 2011-12-23 13:32 James Li 阅读(1185) 评论(0) 编辑
摘要: 园子里这两天讨论的比较多的是CSDN-中文IT社区 600万用户数据在互联网上的传播,作为一名技术人员,暂且放下各自的想法的意见,仅仅从技术的角度来讲,分析和考验一下我们的编程基础。把下载到的压缩文件释放到硬盘中,得到文件www.csdn.net.sql。作为技术人员,对这个文件产生好奇心和编程的冲动。于是提出了下面的两条编程题目:1 如何把600万数据导入到SQL Server中?2 如何选择合适的密码加密方法?这个SQL文件有273MB,用SQL Server Management Studio打开它,会产生OutOfMemory异常,用记事本打开也很慢,推荐用Ultra Edit源代码编阅读全文
posted @ 2011-12-23 09:09 James Li 阅读(10275) 评论(80) 编辑