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






4.50/5 (2投票s)
使用 Codedom 和 IDataReader 类的通用编码
引言
在开发某些控件时,我们会遇到一些实际的困难。在我们的项目中,我们想显示一个值列表控件来选择特定的记录。这个列表可以是客户、供应商、项目,或者任何其他内容。我们需要它具备三个功能:
- 一部分数据在 Oracle 数据库中,另一部分在 SQL Server 数据库中。
- 我们可以使用数据集获取数据并将其绑定到 GridView 控件,但如果可能,出于性能考虑,我们希望避免这样做。
- 另外,我们只想更新/插入选定的数据到数据库(即根据用户的偏好)。
因此,为了解决上述问题,我们使用了 CodeDom 技术来生成动态类型,并实现了 INotifyChanged
接口。如果需要,我们还可以实现任何其他接口并重写其方法和属性。
附加的代码文件包含一个 CodeDom 类,您可以将其包含在您的项目中,直接使用,或根据您的需求添加/删除功能。
限制
如果我们要在 Silverlight 中实现相同的功能,则无法使用 CodeDom 技术,因为它在 Silverlight 中尚不可用;在这种情况下,我们必须使用 Reflection.Emit(这在 Silverlight 中更有用,因为 Silverlight 不支持数据集)。
进程
问题一:创建动态类型
当我们生成动态类型时,可以将整个过程分为几个步骤:
- 创建命名空间
- 导入所有必需的命名空间并引用所有必需的程序集
- 生成新类
- 实现
INotifyPropertyChanged
- 创建构造函数
- 创建属性
- 创建函数
- 编译代码
- 获取创建的类型的对象
如果我们必须使用 CodeDom 创建类型,那么我们需要导入这些命名空间:
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using System.IO;
命名空间创建
CodeNamespace cnsCodeDom = new CodeNamespace(NameSpaceName);
导入和引用程序集
在这里,我创建了 FuncImportNameSpaces
和 FuncReferencedAssemblies
函数,分别用于从集合中导入命名空间和引用程序集(该类的消费者需要指定他/她想导入和/或引用的类)。
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() + ");"
生成属性
- 首先,添加一个属性变量。
- 添加一个
get
语句。 - 如果您想在读取属性之前执行任何自定义语句,可以在这里添加。如果想在
get
语句之前执行,则必须在“GetStatements.Add
”函数之前写入以下语句:
CodeMemberField clsMember = new CodeMemberField();
clsMember.Name = "_" + item.PropName;
property.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(newCodeThisReferenceExpression(), "_" +
item.PropName + ";" )));
CodeSnippetStatement csn = new CodeSnippetStatement(GetStatements);
property.GetStatements.Add(csn);
get
和 set
语句之间没有太大区别;在自定义语句中,您可以提供任何合法的 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 通用类的技巧非常简单,并且这个简单的基本原理可以在多个地方使用。
欢迎任何建议/批评/反馈。