65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF INotifyPropertyChanged 结合 CodeDom 和为 Oracle 和 SQL Server 重用 DataReader 代码

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2009年6月24日

CPOL

5分钟阅读

viewsIcon

24612

使用 Codedom 和 IDataReader 类的通用编码

引言

在开发某些控件时,我们会遇到一些实际的困难。在我们的项目中,我们想显示一个值列表控件来选择特定的记录。这个列表可以是客户、供应商、项目,或者任何其他内容。我们需要它具备三个功能:

  1. 一部分数据在 Oracle 数据库中,另一部分在 SQL Server 数据库中。
  2. 我们可以使用数据集获取数据并将其绑定到 GridView 控件,但如果可能,出于性能考虑,我们希望避免这样做。
  3. 另外,我们只想更新/插入选定的数据到数据库(即根据用户的偏好)。

因此,为了解决上述问题,我们使用了 CodeDom 技术来生成动态类型,并实现了 INotifyChanged 接口。如果需要,我们还可以实现任何其他接口并重写其方法和属性。

附加的代码文件包含一个 CodeDom 类,您可以将其包含在您的项目中,直接使用,或根据您的需求添加/删除功能。

限制

如果我们要在 Silverlight 中实现相同的功能,则无法使用 CodeDom 技术,因为它在 Silverlight 中尚不可用;在这种情况下,我们必须使用 Reflection.Emit(这在 Silverlight 中更有用,因为 Silverlight 不支持数据集)。

进程

问题一:创建动态类型

当我们生成动态类型时,可以将整个过程分为几个步骤:

  1. 创建命名空间
  2. 导入所有必需的命名空间并引用所有必需的程序集
  3. 生成新类
  4. 实现 INotifyPropertyChanged
  5. 创建构造函数
  6. 创建属性
  7. 创建函数
  8. 编译代码
  9. 获取创建的类型的对象

如果我们必须使用 CodeDom 创建类型,那么我们需要导入这些命名空间:

using System.CodeDom;  
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using System.IO;

命名空间创建

CodeNamespace  cnsCodeDom = new CodeNamespace(NameSpaceName);

导入和引用程序集

在这里,我创建了 FuncImportNameSpacesFuncReferencedAssemblies 函数,分别用于从集合中导入命名空间和引用程序集(该类的消费者需要指定他/她想导入和/或引用的类)。

cnsCodeDom.Imports.Add(new CodeNamespaceImport(item));
// cnsCodeDom = NameSpace Object     
cp.ReferencedAssemblies.Add(item);
//cp = Compile Parameter class Object

创建类型并实现 INotifyPropertyChanged

GenerateNewClass 函数用于创建新类型。在这里,我创建了自定义类型,然后如果消费者将 Property 参数(即 IsImplementPropChanged)设置为 true,则实现 INotifyPropertyChanged

为了实现 InotifyPropertyChanged,我导入了 System.ComponentModel 类,然后将接口名称添加到我们类对象的 BaseTypes 集合中。

在这里,您也可以实现自己的自定义接口,但那样的话,您必须指定包含自定义接口的程序集的路径。

例如,如果您有一个接口 ICustomer,其 DLL 位于“D:\Customer\Customer.dll”,则可以使用以下方法:

cp.ReferencedAssemblies.Add(Entire Path Of Assembly);

现在,需要实现“PropertyChanged”事件,这可以通过使用 CodeMemberEvent 类来实现。

我们也可以像这样创建自定义事件:

CodeMemberEvent cme = new CodeMemberEvent();
cme.Name = "DynamicEvent";
cme.Type = new CodeTypeReference("System.EventHandler");
cme.Attributes = MemberAttributes.Public;
clsDecl.Members.Add(cme); //add our event or any created event in our class

生成构造函数

//Creating Empty Constructor  
CodeConstructor clsConstructor = new CodeConstructor();

如果需要在构造函数中添加参数,可以使用以下方法:

clsConstructor.Parameters.Add(
  new CodeParameterDeclarationExpression(GetType(Int32),"CustomerName");
//Second argument is parametername

我们可以使用以下方法在构造函数中写入任何自定义代码:

CodeSnippetStatement csn = new CodeSnippetStatement("String Statement");

在该语句中,我们甚至可以使用传递的参数。

"MessageBox.Show(" + ((char)34).ToString() + CustomerName + ((char)34).ToString() + ");"

生成属性

  1. 首先,添加一个属性变量。
  2. CodeMemberField clsMember = new CodeMemberField();
    clsMember.Name = "_" + item.PropName;
  3. 添加一个 get 语句。
  4. property.GetStatements.Add(new CodeMethodReturnStatement(
      new CodeFieldReferenceExpression(newCodeThisReferenceExpression(), "_" + 
                                       item.PropName + ";" )));
  5. 如果您想在读取属性之前执行任何自定义语句,可以在这里添加。如果想在 get 语句之前执行,则必须在“GetStatements.Add”函数之前写入以下语句:
  6. CodeSnippetStatement csn = new CodeSnippetStatement(GetStatements);
    property.GetStatements.Add(csn);

    getset 语句之间没有太大区别;在自定义语句中,您可以提供任何合法的 C# 语句,但由于 C# 区分大小写,请在传递字符串时确保大小写正确。

    示例

    "if (DynamicEvent != null){DynamicEvent(this,null);â€
    "MessageBox.Show(" + ((char)34).ToString() + "Test" + ((char)34).ToString() + ");"

创建方法

以下是使用 CodeDom 技术生成方法的代码:

CodeMemberMethod cmm = new CodeMemberMethod();
cmm.Name = "NotifyPropertyChanged"; 
cmm.Parameters.Add(new CodeParameterDeclarationExpression(
                   newCodeTypeReference("System.String"),"info")); 
cmm.Attributes = MemberAttributes.Public; 
cmm.Statements.Add(new CodeSnippetStatement("if (PropertyChanged != null)" + 
   "{PropertyChanged(this, new PropertyChangedEventArgs(info));}")); 
clsDecl.Members.Add(cmm);

如果需要 return 语句,则必须指定其数据类型。

cmm.ReturnType = GetType(bool);

编译您的代码

CompilerResults result = cscp.CompileAssemblyFromSource(cp, source[0]);

在这里,result 变量非常重要,尤其是在自定义代码中存在错误时,例如,如果您的自定义字符串不是正确的 C# 代码,或者您继承了一个接口但未实现其成员;您可以从 result 变量中找出确切的错误。

codeGenerator.GenerateCodeFromNamespace(cnsCodeDom, codeWriter, cgo);

此语句是从命名空间生成代码。如果您想将整个代码保存为类或 cs 文件,也可以通过以下语句实现:

//Stream codeFile = File.Open("c:\\sample.cs", FileMode.Create);  
//StreamWriter sw = new StreamWriter(codeFile);
//codeGenerator.GenerateCodeFromNamespace(cnsCodeDom, sw, cgo);
//sw.Close();
//codeFile.Close();
//CompilerResults result =  cscp.CompileAssemblyFromFile(cp, s);

获取对象

只需提供命名空间名称和类名称即可获取对象。

Object o = Activator.CreateInstance(
  result.CompiledAssembly.GetType(NameSpaceName + "."  + ClassName));

好了;您的对象已准备就绪。现在可以将其提供给任何数据上下文,并且可以对该对象执行所有 LINQ 操作。

使用创建的对象

以下代码用于设置所有属性并获取所需对象:

CodeDomLibrary.DataClass dc = new CodeDomLibrary.DataClass();
dc.AssemblyName = "Customers"; 
dc.ClassName = "Customer";  
dc.NameSpaceName = "Project"; 
dc.ImportNameSpaces = new 
List<string>(){"System.Xml","System","System.Windows.Forms",
                  "System.Collections.ObjectModel"};
dc.ReferencedAssemblies = new List<string>() { "System.Xml.dll", 
                          "System.Windows.Forms.dll","System.dll" }; 
CodeDomLibrary.Properties p = new CodeDomLibrary.Properties(); 
p.PropName = "CustomerName"; 
p.PropType = "System.String"; 
p.CustomSetCodeStatements = new List<string>() 
{ "MessageBox.Show(" + ((char)34).ToString() + 
  "Inside Customer Name"  + ((char)34).ToString() + ");"
};
dc.PropertyCollection = new List<CodeDomLibrary.Properties>() { p }; 
dc.IsImplementPropChanged = true; 
Object o = dc.CreateObject();

我们必须使用反射来设置属性或调用创建对象的任何方法。如果我们实现了自定义接口,则可以无需反射即可使用它。

o.GetType().GetProperty("CustomerName").SetValue(o, "Mike", null);

使用自定义事件

EventInfo e = o.GetType().GetEvent("DynamicEvent");
MethodInfo RRMeth = typeof(CodeDomLibrary).GetMethod("Create", 
  System.Reflection.BindingFlags.Static |BindingFlags.NonPublic); 
Delegate peDel2 = Delegate.CreateDelegate(e.EventHandlerType, RRMeth); 
e.AddEventHandler(o, peDel2);
static private void Create(Object Sender, EventArgs e)
{
    //MessageBox.Show("Inside Delegate");
}

第一个问题已解决,我们将转向第二个问题。

问题二:创建通用的数据类

正如我之前提到的,我想使用相同的代码,并从 SQL Server / Oracle 数据库获取数据,所以无法创建以下对象的实例:

  • SqlConnection / OracleConnection
  • SqlCommand / OracleCommand
  • SqlDataReader / OracleDataReader

解决方案很简单;用以下接口的对象替换上述类:

private System.Data.IDbConnection cn;
private System.Data.IDbCommand cmd;
private System.Data.IDataReader sdrdr;

现在,只需传递一个标志来指示您想连接哪个数据库:Oracle 还是 SQL Server。

//Code Inside Constructor
if (flag == "Oracle")
{
 cn = new OracleConnection(ConnString);
 cmd = new OracleCommand (sql,(OracleConnection) cn);
 cmd.CommandType = CommandType.Text;
 cn.Open();
}   
else
{
 cn = new SqlConnection(ConnString);
 cmd = new SqlCommand(sql, (SqlConnection) cn);
 cmd.CommandType = CommandType.Text;
 cn.Open();
}

其余代码将保持不变,因为我们可以使用存储过程或简单的 SQL 语句来获取或更新/插入数据。

结论

通过创建继承 INotifyPropertyChanged 的动态类型,我们可以使用该对象绑定到任何 UI 元素;还可以对指定对象应用(内存中的)LINQ。同时,也可以减少创建数据集的开销。

我们用于创建 Oracle / SQL Server 通用类的技巧非常简单,并且这个简单的基本原理可以在多个地方使用。

欢迎任何建议/批评/反馈。

© . All rights reserved.