Click here to Skip to main content
15,878,945 members
Articles / Desktop Programming / WPF

WPF INotifyPropertyChanged With CodeDom and Reusing DataReader Code for Oracle and SQL Server

Rate me:
Please Sign up or sign in to vote.
4.50/5 (2 votes)
24 Jun 2009CPOL5 min read 24.1K   11   1
Generic coding with Codedom and IDataReader class usage

Introduction

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:

  1. Some part of data was in an Oracle database and some of it in a SQL Server database.
  2. We can always use a dataset to get data and bind it to a grid view control, but if possible, we wanted to avoid this due to performance issues.
  3. Also, we wanted to update/insert only selected data in the database (i.e., according to user preferences).

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.

Limitations

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).

Process

Problem I: Creating a Dynamic Type

When we generate a dynamic type, we can divide the entire process into certain steps:

  1. Create a namespace
  2. Import all the required namespaces and reference all the assemblies required
  3. Generate a new class
  4. Implement INotifyPropertyChanged
  5. Create constructors
  6. Create properties
  7. Create functions
  8. Compile the code
  9. Get the object of the created type

If we have to create a type using CodeDom, then we have to import these namespaces:

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

Namespace Creation

C#
CodeNamespace  cnsCodeDom = new CodeNamespace(NameSpaceName);

Importing and Referencing Assemblies

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).

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

Creating the Type and Implementing INotifyPropertyChanged

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:

C#
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:

C#
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

Generate Constructor

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

If it requires to add a parameter in the constructor, we can use the below mentioned method:

C#
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:

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

Inside that statement, we can even use the parameters which we have passed:

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

Generate Properties

  1. First, add a variable for the property:
  2. C#
    CodeMemberField clsMember = new CodeMemberField();
    clsMember.Name = "_" + item.PropName;
  3. Add a get statement:
  4. C#
    property.GetStatements.Add(new CodeMethodReturnStatement(
      new CodeFieldReferenceExpression(newCodeThisReferenceExpression(), "_" + 
                                       item.PropName + ";" )));
  5. Add any custom statements you want to execute after reading your property; the below mentioned statements have to be written before your "GetStatements.Add" function if you want to execute it prior to your get statement:
  6. C#
    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:

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

Create Method

Below is the code to generate a method through the CodeDom technology:

C#
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:

C#
cmm.ReturnType = GetType(bool);

Compile your Code

C#
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.

C#
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:

C#
//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);

Get the Object

Just provide the namapespace name and class name to get the object:

C#
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.

Consume Created Object

Below is the code to set all the properties and get the required object:

C#
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 = trueObject 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.

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

To consume a custom event:

C#
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.

Problem II: Make a Generic Class for Data

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 / OracleConnection
  • SqlCommond / OracleComand
  • SqlDataReader / OracleDataReader

The solution is simple; declare objects of the following interfaces in place of the above mentioned class:

C#
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.

C#
//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.

Conclusion

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Phoenix Tech Labs
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralJust thought I'd give you my 5 cents Pin
Martin Lottering29-Jun-09 23:13
Martin Lottering29-Jun-09 23:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.