Sample: The Data Access Layer Analyzer

This sample uses both the Database Object Model.

Create the module class
  • You can either use the template for Visual Studio here
  • Or write the code bellow :)
using System;
using System.Collections.Generic;
using System.Text;
using Iris.Common.Modules;
using LocalizeMe;
using System.ComponentModel;
using System.CodeDom;
using System.Data.SqlClient;
using Iris.Common.ObjectOrientedModel;
using Iris.Common.Data;
using System.IO;
using Iris.Common.Exceptions;
using Iris.Common.DatabaseModel;
using System.Drawing.Design;
using System.Data;
using Iris.Common.IDEs;

namespace ClassLibrary1
{
    public class ModuleTest : ModuleBase
    {

        #region Private variables



        #endregion

        #region .ctor

        /// <summary>
        /// .ctor
        /// </summary>
        public ModuleTest()
            : base()
        {
            //Implement translation:
            //ResourceSource rs = new XmlSource("Iris.Modules.DotNetDALWriter",
            //                                Path.Combine(Application.StartupPath, @"\Languages\Iris.Modules.DotNetDalWriter"),
            //                                "lang");
            //Resource.AddSource(rs);


        }

        #endregion

        #region Properties



        #endregion

        #region Methods

        /// <summary>
        /// Runs the analyse step. This step can be by skiped.
        /// </summary>
        /// <returns>null</returns>
        public override object Analyze()
        {

        }

        /// <summary>
        /// Runs the generation step. The module outputs the code.
        /// </summary>
        /// <returns></returns>
        public override object Generate()
        {

        }

        /// <summary>
        /// Updates the configuration. Used when the parent configuration is changed.
        /// </summary>
        public override void UpdateConfiguration()
        {

        }

        #endregion
       }
}


The default constructor has some statements here to load the translation file of the module. It does not interfere with the generation process.

First of all we should define the parameters of the module. Simple properties will do.

We will just define the generated Package (namespace for .Net users). Here we also define a TypeConverter as the properties will be shown through a PropertyGrid. It is the ExpandableObjectConverter which is defined in the .Net Framework.
As I wanted my module to be localized, i used of mini framework of mine (LocalizeMe) to such purposes. The second attribute defines a localized description for the property (the value will be localised in the PropertyGrid).

private Package _ns;

/// <summary>
/// The Namespace that will be created once the analyse done.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocalDescription("Description.Namespace")]
public Package Namespace
{
     get
     {
          return _ns;
      }
      set
      {
           _ns = value;
       }
}


Nothing difficult so far...

Let's implement the generation methods...

First of all as it is a module that only analyses, it does not generate anything. So nothing will be done during the generation step. So the Generate method will look like this:

public override object Generate()
{
      return null;
}


That was tough work...

Nota: In the constructor you can also set the value of the ShouldGenerate value to false... I don't know why i did not. I will change that for the next release.

Still harder...

We will now create the core method of our module. What i want to do is to have a class for each table. Each class will define the methods to access the requests each table has. It's not that hard I promise.

So here is the Analyze Method:

public override object Analyze()
        {
            MetaType.LoadEquivalences("DotNet2005");
			
            if (this.Parameters != null)
            {
                foreach (KeyValuePair<string, object> obj in this.Parameters)
                {
                    if (obj.Value is Database)
                    {
                        Database db = (Database)obj.Value;
						_ns = null;
                        _ns = CreateNamespace(db);
						AddClasses(ref _ns);
                        return _ns;
                    }
                }
            }
            return null;
        }


Simple i told you... Well there are two or some things i should explain anyway.

The MetaType class is define in the API. As any language defines type in many ways, i needed an abstract notation to manage types easily. MetaTypes are defined in the Types sub directory of the application. It contains translation for SQLServer 2005 types and .Net types (most common types). As you can imagine, you can add MetaTypes and Types as simply as Copy-Pasting an XML line. So the first line of code loads the translations between MetaTypes and .Net Types.

As our Analyzer needs a Database Model, we will test if it has Parameters. The value is set by the generator Engine when loading modules.

The Paremeters property is a Dictionnary. The key is defined by the name of the module that generated the object and the value is the object itself. So we will search all Parameters for a Database kind. This process should change a bit as i am willing to implement the search with the key and not with a foreach statement. It will be implemented in the API and optimized a bit (i realise how awfull it is reading it). Nonetheless here are the basics.

When we are sure the parameter is a Database Type :), we create a Package (remember? It is a namespace). The name of our Package will be the name of our Database. Then we AddClasses. When all is over, we return the generated Package.

Creating the Package. This method is of much difficulty:

private Package CreateNamespace(Database db)
        {
            if (db == null)
                return null;

            return new Package(db.Name, db);
        }


We called the constructor of the Package type and returned it. Here is our Package.

The next Step is building the content of the Package... and Here comes the AddClasses method:

private void AddClasses(ref Package ns)
        {
            if (ns.RelatedObject == null)
                return;

            if (ns.RelatedObject is Database)
            {
                foreach (Table t in (ns.RelatedObject as Database).Tables)
                {
                    Iris.Common.ObjectOrientedModel.Type type = new Iris.Common.ObjectOrientedModel.Type();
                    type.Name = t.Name;
                    type.IsClass = true;
                    type.Namespace = ns;
                    type.Visibility = Visibility.Public;
                    type.RelatedObject = t;

                    foreach (Request request in t.Requests)
                    {
                        Method method = new Iris.Common.ObjectOrientedModel.Method();
                        method.Name = request.Name;
                        method.MetaTypeReturn = request.ReturnMetaType;
                       
                        method.ParentType = type;
                        method.RelatedObject = request;

                        foreach (Column c in request.Parameters)
                        {
                            Parameter param = new Iris.Common.ObjectOrientedModel.Parameter();
                            param.Name = c.Name;
                            param.ParameterType = c.MetadataType;
                            param.PassingMode = PassingMode.ByValue;
                            param.RelatedObject = c;

                            method.Parameters.Add(param);
                        }

                        type.Members.Add(method);
                    }

                    ns.Types.Add(type);
                }
            }
        }


This needs a some explanations:

What is the RelatedObject of the Package Type?
Remember when we created the Package. The constructor needed both the name and the database as parameters. The database is the related object. This notion exists for some Object Oriented Type in the API.

Once we've found the Database we will loop through the Tables of this Database. For each table we create a new Type. Here the Type will be a class. We give it the name of the table, define it as public, set its parent to be the current Package and finally defines the table to be the related object (How curious it is).

Now that we have our classes, let's fill them. We want a method for each Request associated to the current table.

That's what we do looping through the Table's Requests. For each Request, we create a Method. We give it the name of the current Request, the return type of the method, the Parent type which is the class created just before and last but not least we set the related object to be the request. That's almost finished. We need to define parameters that shall be passed to the method. Those parameters are the same as those of the underlying Request. So we loop throgh the Parameters collection of the current Request. For each one of them we create a Parameter that will be added to the Parameters collection of the method...

Here we are...

Good luck in creating your Modules. If you meet any problem or if you need an improvement feel free to create a discussion.

Last edited Nov 13, 2007 at 8:58 PM by ld9474, version 1

Comments

No comments yet.