![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
Data Binding
Intermediate
License: The Code Project Open License (CPOL)
WPF INotifyPropertyChanged With CodeDom and Reusing DataReader Code for Oracle and SQL ServerBy HarishBhattbhattGeneric coding with Codedom and IDataReader class usage |
C#1.0, C#2.0, C#3.0.NET3.5, .NET4.0, SQL-Server, WPF, Oracle, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
There are certain practical difficulties we face during the development of certain controls. In our project, we wanted to show a List of Value control for the selection of a particular record. That list can be of customers, vendors, items, or it could be anything. We required three facilities in it:
So, to resolve the above mentioned issues, we used the CodeDom technology to generate dynamic types, and also implemented the INotifyChanged interface.
If required, we could also implement any other interface and override its method and properties.
The attached code file consists of a CodeDom class which you can include in your project, and use as it is, or add/delete features as per your requirements.
If we have to do the same thing in Silverlight, we cannot use the CodeDom technology as it is not available yet in Silverlight; in that case, we have to use Reflection.Emit (it becomes more useful in Silverlight as Silverlight does not support datasets).
When we generate a dynamic type, we can divide the entire process into certain steps:
INotifyPropertyChangedIf we have to create a type using CodeDom, then we have to import these namespaces:
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using System.IO;
CodeNamespace cnsCodeDom = new CodeNamespace(NameSpaceName);
Here, I have created the FuncImportNameSpaces and FuncReferencedAssemblies functions to import namespaces and reference assemblies
from a collection, respectively (the onsumer of this class has to specify which classes he/she would like to import and/or reference).
cnsCodeDom.Imports.Add(new CodeNamespaceImport(item));
// cnsCodeDom = NameSpace Object
cp.ReferencedAssemblies.Add(item);
//cp = Compile Parameter class Object
The GenerateNewClass function is used to create a new type. Here, I have created that custom type and then implemented INotifyPropertyChanged if
the consumer sets the true for the Property parameter (i.e., IsImplementPropChanged).
To implement InotifyPropertyChanged, I have imported the System.ComponentModel class and then added an interface name into
the BaseTypes collection of our class object.
Here, you can implement your custom interface as well, but in that case, you have to specify the Path of your assembly which consists of your custom interface.
For example, if you have an interface ICustomer, the DLL of which exists at "D:\Customer\Customer.dll", then we can use it as below:
cp.ReferencedAssemblies.Add(Entire Path Of Assembly);
Now, it requires to implement the "PropertyChanged" event, and that is possible by using the CodeMemberEvent class.
We can also create our custom event like this:
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();
If it requires to add a parameter in the constructor, we can use the below mentioned method:
clsConstructor.Parameters.Add(
new CodeParameterDeclarationExpression(GetType(Int32),"CustomerName");
//Second argument is parametername
Whatever custom code we would like to write inside the constructor, we can use:
CodeSnippetStatement csn = new CodeSnippetStatement("String Statement");
Inside that statement, we can even use the parameters which we have passed:
"MessageBox.Show(" + ((char)34).ToString() + CustomerName + ((char)34).ToString() + ");"
CodeMemberField clsMember = new CodeMemberField();
clsMember.Name = "_" + item.PropName;
get statement:property.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(newCodeThisReferenceExpression(), "_" +
item.PropName + ";" )));
GetStatements.Add" function if you want to execute it prior to your get statement:CodeSnippetStatement csn = new CodeSnippetStatement(GetStatements);
property.GetStatements.Add(csn);
There is not much difference between get and set statements; in your custom statement, you can provide any legitimate C# statement,
but as C# is case sensitive, please ensure case sensitivity when you pass on your string.
Examples:
"if (DynamicEvent != null){DynamicEvent(this,null);â€
"MessageBox.Show(" + ((char)34).ToString() + "Test" + ((char)34).ToString() + ");"
Below is the code to generate a method through the CodeDom technology:
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);
Here, if a return statement is required, then we have to specify its data type:
cmm.ReturnType = GetType(bool);
CompilerResults result = cscp.CompileAssemblyFromSource(cp, source[0]);
Here, the result variable is very important especially when there is some mistake in your custom code, i.e., if your custom string is not proper
C# code, or if you have inherited an interface but its member is not implemented; you can find out the exact error from the result variable.
codeGenerator.GenerateCodeFromNamespace(cnsCodeDom, codeWriter, cgo);
This statement is generating code from the namespace. If you want to save the entire code as a class or a cs file, then it is also possible through the following statements:
//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);
Just provide the namapespace name and class name to get the object:
Object o = Activator.CreateInstance(
result.CompiledAssembly.GetType(NameSpaceName + "." + ClassName));
That's it; your object is ready. You can now provide it to any data context, as well as you can do all LINQ operations on this object.
Below is the code to set all the properties and get the required object:
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();
We have to use Reflection to set the properties or invoke any method of the created object. If we have implemented a custom interface, then we can consume it without Reflection.
o.GetType().GetProperty("CustomerName").SetValue(o, "Mike", null);
To consume a custom event:
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");
}
As we have completed our first issue, we will move on to the second.
Now, as I had mentioned earlier, I want to use the same code and want to get data from a SQL Server / Oracle database, so it is not possible to create an object of:
SqlConnection / OracleConnectionSqlCommond / OracleComandSqlDataReader / OracleDataReaderThe solution is simple; declare objects of the following interfaces in place of the above mentioned class:
private System.Data.IDbConnection cn;
private System.Data.IDbCommand cmd;
private System.Data.IDataReader sdrdr;
Now, just pass on a flag to show which database you would like to connect: Oracle or 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();
}
Rest of the code will remain the same, as we may use a Stored Procedure or simple SQL statements to get or update/insert data.
By creating a dynamic type by inheriting INotifyPropertyChanged, we can use that object to bind with any UI Element; also, it is possible
to apply LINQ (in memory) to a specified object. Overheads of creating a dataset can also be reduced.
The trick which we have used to create a generic class for Oracle / SQL Server is very simple, and this simple fundamental principle can be used at several places.
Any suggestions / critics / feedback are most welcome.
| You must Sign In to use this message board. | ||||||||
|
||||||||
|
||||||||
|
||||||||
|
||||||||
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 24 Jun 2009 Editor: Smitha Vijayan |
Copyright 2009 by HarishBhattbhatt Everything else Copyright © CodeProject, 1999-2010 Web19 | Advertise on the Code Project |