Click here to Skip to main content
Click here to Skip to main content

Full Database Abstraction Layer Generator

By , 22 Oct 2003
 

Introduction

This article presents yet another Data Abstraction Layer generator (popular topic). The DAL structure is directly inspired from the DotNetNuke, DAL document and the SQL generation is done using SQLpp.

The generator takes care of pretty much everything for you:

  • SQL code generation: database creation and stored procedure. Moreover, the SQL is specialized to take into account the SQL syntactic difference from each provider,
  • Web.Config generation : XML entries for configuring the DAL,
  • Business Logic Layer for each table and for the procedures (C#),
  • Abstract Data Provider for each table and for the procedures (C#),
  • Specialized Data Provider, customized for each data provider (C#),
  • IDataReader Wrapper classes for tables and procedures (C#)

Some interesting features are:

  • No SQL manipulation: with the generator, you don't write a single line of SQL,
  • Multiple Database system support: MSSQL, MySQL. Others could be implemented easily,
  • Handles the stored procedure problem by storing the queries in the web.config if necessary,
  • Supports complex table join: the generator uses the Boost.Graph.Library on the background to generate the queries
  • The C# code is documented !
  • It's totally free for non-commercial and commercial use!!!

All downloads, docs and trackers are available from the SourceForge page.

Requirements

The following providers need some specific (free) libraries:

Moreover, to fully understand the DAL structure, you should have a look at the DNN AL document.

Illustrative example

The generation process will be illustrated with a simple module creation example. Suppose you want to create a Users-Products-Orders database defined by the following structure:

Since SqlppNet is still a GUI-less tool, so you need to write things down.

Database provider

The first step is to create a database adaptor. For example, the MSSQL provider is constructed as:

MsSqlAdaptor msSql = new MsSqlAdaptor(
    "MyCatalog",
    "providerpath",
    "dbo",
    "connectionstring"
    );

where

  • MyCatalog is the database name
  • providerpath is the provider path,
  • dbo is the database owner,
  • connectionString is the connection string for this provider,

Of course, you can create other adaptors later and generate code for them.

Database creation

Once the adaptor is created, a database is created and the adaptor is attached to it:

Database db = new Database( msSql );

Adding tables

Tables are added using the following function call:

DbTable users = db.AddTable(
    "User", // data name
    "dnn_", // object qualifier
    "User" // field prefix
    );

where

  • the data name is used to compose the table name and create the wrapper classes:
    • Users will be the table name,
    • UserData, UserDataProvider, etc... will be the class names associated to Users
  • as mentioned in the DAL document, the object qualifier is used to "decorate" the table name: dnn_Users is the real table name,
  • the field prefix is used as an automatic field prefix appended to each field of the table (expect foreign key fields): if you add a field named ID, it will be renamed as "UserID".

The other tables are added similarly:

DbTable products = db.AddTable("Product","mytables_","Product");
DbTable orders = db.AddTable("Order","mytables_","Order");

Add primary keys

Primary key addition is straightforward:

users.AddPrimaryKey("ID","int"); // adds UserID INT as primary key

Notes:

  • this method automatically defines an auto-increment on the primary key
  • More complex primary key can be defined but are outside the scope of this article
  • the int string is parsed internally by the framework and converted into a SQL type. The parser contains a large subset of the admissible SQL types.

Again, the other tables are treated similarly:

orders.AddPrimaryKey("ID","int"); 
products.AddPrimaryKey("ID","int");

Linking tables

It's now time to set the relations between the tables. Relations/constraints are enforced by adding foreign keys (reference constraints) or unique constraints to the table.

Adding the Users foreign key to the Orders table is done as follows:

orders.AddForeignKey( users );

Note that this method uses a lot of default settings such as the ON DELETE, ON UPDATE behavior. The Orders table now contains a UserID field referencing the Users table.

The foreign key to the products table is added similarly:

orders.AddForeignKey( products );

Add fields

Following the same logic, fields are added using Table.AddField:

products.AddField(
    "Name", // field name (remember that field prefix is added
    "nvarchar(50)", // type
    true // not null
    );
products.AddField("Price","decimal",true);

orders.AddField("Quantity","int",true);
orders.AddField("Timestamp","timestamp",true);

At this point, your database structure is ready.

Create some queries

Of course, you will need some queries on the database defined above. This is also handled by SQLppNet. Suppose we want to create the following query:

Select user id, product name, quantity, price and order id 
from users, products and orders.
  1. Create a query,
    SelectQuery q = db.CreateQuery();
  2. Add tables with alias (optional) and link them,
    QueryTable qusers = q.AddTable(
        users, // table to add 
        "U" // alias
        );

    The other tables are added similarly. The tables need to be joined:

    q.Join( 
        qorders, // table 
        qusers // reference table
        );
    q.Join(qorders,qproducts);
    

    SQLpp will handle the rest of the Join process.

  3. Add fields
    q.AddField( 
        FieldExpression.Link(    // links qusers and userID field
            qusers, //
            users.get_Field("ID") // retreiving UserID field
            ),
        "UserID"  // giving the AS name
        );

    Other fields can be added the same way. In the C++ version, you can add more complicated SQL expressions such as aggregate functions, arithmetic expressions, etc... See note below.

  4. Add Where clause (not wrapped yet, see note below)

Note: SQLppNet is a thin managed C++ wrapper around SQLpp. At the present time, the wrapper is incomplete. For more functionalities, go back to C++.

Setting up the generator

A C# generator object needs to be created and configured:

CsGenerator cs = new CsGenerator(
    db,              // the database to generator
    ".",             // output file path
    "MyNamespace",   // created namespace
    "StoredProc"     // name of the stored procedure class(explained later)
    );

The C# generator does not generate wrappers for all the database tables by default, you have to add the tables to be "generated" to the generator (since Users table already exists in the database, we don't need to generate it):

cs.AddTable(orders);
cs.AddTable(products);

You also need to add the specific queries you generated:

cs.AddProcedure( StoredProcedure.Wrap(q, "GetOrders") );

Generating the DAL

For sure the easiest part:

cs.Generate();

Resuming the code:

This is a short summary of the code you need to write to generate the simple database structure:

using namespace SQLppNet;
using namespace SQLppNet.Adaptors;
using namespace SQLppNet.Generators;
using namespace SQLppNet.Queries;

try 
{
    // creating MsSql adaptor
    MsSqlDatabaseAdaptor msSql = new MsSqlDatabaseAdaptor(
        "MyCatalog",
        "",
        "dbo",
        "connectionstring"
        );

    // creating db
    Database db = new Database( msSql );

    // add tables
    DbTable users = db.AddTable("User","","User");
    DbTable products = db.AddTable("Product","ex_","Product");
    DbTable orders = db.AddTable("Order","ex_","Order");

    // add pk
    users.AddPrimaryKey("ID","int");
    orders.AddPrimaryKey("ID","int");
    products.AddPrimaryKey("ID","int");

    // link tables
    orders.AddForeignKey( users );
    orders.AddForeignKey( products );

    // add fields
    products.AddField("Name","nvarchar(50)",true);
    products.AddField("Price","decimal",true);
    orders.AddField("Quantity","int",true);
    orders.AddField("Timestamp","timestamp",true);

    // create generator
    CsGenerator cs = new CsGenerator(
        db,
        "c:\\Inetpub\\wwwroot\\DnnFramework\\DotNetNuke\\DesktopModules",
        "UserQuotes",
        "StoredProc"
        );
    // adding tables to generate
    cs.AddTable(orders);
    cs.AddTable(products); 

    // a query
    SelectQuery q = db.CreateQuery();
    // adding tables
    QueryTable qusers = q.AddTable(users,"U");
    QueryTable qorders = q.AddTable(orders,"O");
    QueryTable qproducts = q.AddTable(products,"P");

    // joining tables
    q.Join(qorders, qusers);
    q.Join(qorders,qproducts);

    q.AddField( FieldExpression.Link(qusers, 
             users.get_Field("ID")),"UserID"  );
    q.AddField( FieldExpression.Link(qproducts,
             products.get_Field("Name")),"ProductName"  );
    q.AddField( FieldExpression.Link(qorders,
             products.get_Field("Price")),"Price"  );
    q.AddField( FieldExpression.Link(qorders, 
             orders.get_Field("Quantity")),"Quantity"  );

    cs.AddProcedure( StoredProcedure.Wrap(q, "GetOrders") );

    // generation
    cs.Generate();
}
catch(Exception ex)
{
   Console.WriteLine(ex.ToString());
}

So this is less than 100 lines long, it will generate a lot more lines than that.

Generated code

This section describes what was been generated by the above application.

Generate file structure

The file/class structure is laid out as follows and "C# ready" (ready to be included in a C# project):

file/path description
. root path
/Config Configuration classes directory (Config namespace)
/Config/webconfig.xml XML to include in the web.config of your application
/Config/DataProviderConfigurationHandler.cs Class handling the data in the web.config file
/Data DAL classes directory (Data namespace)
/Data/DbSql.sql SQL code for creating the Orders and Products table.
/Data/OrderAbstractDataProvider.cs Abstract data provider class for the Orders table
/Data/OrderSqlDataProvider.cs MSSQL data provider class for the Orders table
/Data/OrderStoreProcSql.sql Stored procedures used by the OrderSqlDataProvider data provider
/Data/OrderDB.cs Business logic layer for the Orders table (OrderDB class)
/Data/OrderData.cs DataRow wrapper of the Orders table
And the same for the products table and the stored procedures

The SQL files are ready to be run by the OSQL tool.

Configuring the project : web.config entries

After adding the .cs files to the project and executing the .sql files, you need to update the web.config file with the generated webconfig.xml file that looks like the following:

<web.config>
  <!--add this before system.web-->
  <configSections>
   <section name="mynamespace" 
    type="MyNamespace.Config.DataProviderConfigurationHandler, MyNamespace"/>
  </configSections>
  <system.web/>
  <!--add this after system.web-->
  <mynamespace>
    <data defaultProvider="Sql">
      <providers>
        <clear/>
        <add type="Sql" name="MyDatabase" 
          connectionString ="connectionstring" 
          providerPath ="" databaseOwner="dbo"/>
      </providers>
      <dataProviders>
        <clear/>
        <add name="Order" objectQualifier="ex_">
          <providers>
            <provider name="Sql" 
              type="MyNamespace.Data.OrderSqlDataProvider"/>
          </providers>
        </add>
        <add name="Product" objectQualifier="ex_">
          <providers>
            <provider name="Sql" 
               type="MyNamespace.Data.ProductSqlDataProvider"/>
          </providers>
        </add>
        <add name="StoredProc" objectQualifier="">
          <providers>
            <provider name="Sql" 
               type="MyNamespace.Data.StoredProcSqlDataProvider"/>
          </providers>
        </add>
      </dataProviders>
      <procedures/>
    </data>
  </mynamespace>
</web.config>

Normally, you just need to integrate this file into your web.config as specified.

Data row wrapper class

This class is not part of the DNN DAL document. The wrapper has the following advantages:

  • fields are accessed through wrapper properties:
    • avoids spelling errors while retrieving the field name
    • ease up programming through Intellisense.
  • closes the reader on Dispose to avoid un-closed database connection

The ProductData class is defined as follows (long code):

///<summary>A wrapper class for the ex_Products table.</summary>
///<summary>A wrapper class for the ex_Products table.</summary>
public class ProductData : IDisposable
{
    private IDataReader m_dr;
    private int m_ProductID;
    private String m_ProductName;
    private decimal m_ProductPrice;

     ///<summary>Create a data wrapper</summary>
     ///<param name="dr">a opened data reader.</param>
     ///<exception cref="ArgumentNullException">if dr is null</param>
     public ProductData(IDataReader dr)
     {
         if (dr == null)
             throw new ArgumentNullException("datareader");
         m_dr = dr;
     }

     ///<summary>Create a data wrapper</summary>
     ///<param name="dr">a opened data reader.</param>
     ///<exception cref="ArgumentNullException">if dr is null</param>
     public ProductData()
     {
         m_dr = null;
     }

    ///<summary>Reads the row data from the data reader</summary>
    ///<returns>true if data was read, false otherwize</returns>
    ///<exception cref="System.ArgumentNullException">
    ///Thrown if dr is null</exception>
    public bool Read()
    {
        if (m_dr == null)
            throw new Exception("data reader is null");
        if (!m_dr.Read())
        {
            m_dr.Close();
            m_dr = null;
            return false;
        }

        m_ProductID=(int)m_dr["ProductID"];
        m_ProductName=(String)m_dr["ProductName"];
        m_ProductPrice=(decimal)m_dr["ProductPrice"];
        return true;
    }
    ///<summary>Closes the reader, if any</summary>
    public void Close()
    {
        if (m_dr != null)
        {
            m_dr.Close();
            m_dr = null;
        }
    }

    ///<summary>Release and close the reader</summary>
    public void Dispose()
    {
        Close();
    }

    ///<summary>ProductID set/get property</summary>
    public int ProductID
    {
        get{ return m_ProductID;}
        set{ m_ProductID=value;}
    }

    ///<summary>ProductName set/get property</summary>
    public String ProductName
    {
        get{ return m_ProductName;}
        set{ m_ProductName=value;}
    }

    ///<summary>ProductPrice set/get property</summary>
    public decimal ProductPrice
    {
        get{ return m_ProductPrice;}
        set{ m_ProductPrice=value;}
    }

} // ProductData

As one can see, data retrieval is done once only and hidden from the user.

Using the generated classes for manipulating tables

The generated classes mimics exactly the DNN DAL proposal (see scheme below). Let's start with a simple example where the Products table is manipulated.

DAL structure, extracted from the DNN DAL document.

  • Creating the business logic object:
    // creating the products table business logic
    ProductDB productDB = new ProductDB();
  • Add a new product. You can use the wrapper or directly pass the parameters to the AddProduct method. If the table contains an auto-increment field, this field will be returned or the wrapper updated.
    // creating a product row wrapper
    ProductData pd = new ProductData();
    // setting fields, each field is a property
    pd.ProductName = "a product name";
    pd.ProductPrice = (decimal)49.99;
    
    // add the product
    productDB.AddProduct(ref pd);

    As one can see, pd is passed as reference to the AddProduct method. In fact, the ProductID field is retrieved from the database (was an auto-increment field) and updated into pd. You can also avoid using ProductData by calling directly:

    int pid = productID.AddProduct("name", (decimal)49.99);
  • Retrieve a product by ID. The ProductData class stores a IDataReader object on creation and closes it when disposed. The wrapper has a method Read that behaves similarly to IDataReader.Read.
    using(ProductData pd = productDB.GetProduct(pid))
    {
        // try to reade the Datareader
        if (!pd.Read())
            throw new Exception("...");
        // pd contains the data.
        string name = pd.ProductName;
    }
    // the reader is automatically closed from this loop (exception safe)
  • Update a product:
    productDB.UpdateProduct( pd );
    productDB.UpdateProduct(pid, name, price); // equivalent
  • Delete a product by ID:
    productDB.DeleteProduct( pd ); 
    productDB.DeleteProduct( pid );

These are the four main generated methods to manipulate the tables.

Using the generated classes for doing queries

Queries also have their own business logic object. In this case, it is called StoredProcDB where the methods are named after the stored procedure name (remember StoredProcedure.Wrap above). In fact, to call GetOrders stored procedure, we do:

  • Using IDataReader
    StoredProcDC spDB = new StoredProcDB();
    using (GetOrdersData orderData = new GetOrdersData( spDB.GetOrders() ))
    {
        while(orderData.Read())
        {
           // orderData contains the current row data
        }
    }
  • Using DataSet
    DataSet ds = spDB.GetOrdersDataSet();

This concludes the description of the visible part of the generated code. Let's see what's happening behind the scenes.

Behind the scene

The things happening behind the scene are quite similar to what you can find in the DNN DAL description. For instance, the method ProductDB.GetProduct is laid out as follows:

public IDataReader GetProduct(int ProductID)
{
    return ProductDataProvider.Instance().GetProduct(ProductID);
}

ProductDataProvider.Instance is a static method defined in ProductDataProvider.cs: it looks for a data provider constructor in the cache, if not found. It gets the data provider type from the Web.config and inserts it in the cache.

static public ProductDataProvider Instance()
{
    // Use the cache because the reflection used later is expensive
    System.Web.Caching.Cache cache = System.Web.HttpContext.Current.Cache;
    string providerKey = m_ProviderName + m_ProviderType + "provider";
    if ( cache[providerKey] == null)
    {
        // Get the name of the provider
        DataProviderConfiguration providerConfiguration  = 
         DataProviderConfiguration.GetProviderConfiguration(m_ProviderType);
        //  The assembly should be in \bin or GAC,
        // so we simply need to get an instance of the type
        Provider provider = 
            providerConfiguration.Providers
            (providerConfiguration.DefaultProvider);
        DataProvider dataProvider = 
            providerConfiguration.DataProviders("Product");
        String type = dataProvider.Type( provider.Name );
        // Use reflection to store the constructor of
        // the class that implements DataProvider
        Type t = Type.GetType(type, true);

        // Insert the type into the cache
        cache.Insert(providerKey, 
          t.GetConstructor(System.Type.EmptyTypes));
    }
    return (ProductDataProvider) 
      ((ConstructorInfo)cache[providerKey]).Invoke(null);
}

Providers

Currently, there are two available providers: MSSQL and MySQL. However, this could be extended to other database systems: OleDB, PostgreSQL, Oracle, ...

MSSQL provider

This provider uses the Microsoft Applications blocks as in the DNN DAL description.

MySQL provider

This provider uses the ByteFX library available at SourceForge site. Since MySQL does not support stored procedures, the queries are stored in the web.config. For example, let's add a MySQL provider to the previous example:

MySqlAdaptor mysql = new MySqlDatabaseAdaptor(
    "MysqlDB",
    "",
    "",
    "connectionstring");
cs.AddAdaptor(mysql);

The following elements are added to the web.config:

<web.config>
  ...
  <mynamespace>
    <data defaultProvider="Sql">
      <providers>
        <clear/>
        <add name="Sql" catalog="MyDatabase" 
           connectionString ="connectionstring" 
           providerPath ="" databaseOwner="dbo"/>
        <add name="MySql" catalog="MysqlDB" 
              connectionString ="connectionstring" 
              providerPath ="" databaseOwner=""/>
      </providers>
      ...
      <procedures>
        <clear/>
        <add name="GetOrders">
          <versions>
            <version provider="MySql">
              <![CDATA[SELECT 
U.UserID AS 'UserID',
P.ProductName AS 'ProductName',
O.ProductPrice AS 'Price',
O.OrderQuantity AS 'Quantity'
FROM (ex_Orders AS O 
INNER JOIN Users AS U
ON U.UserID = O.UserID)
INNER JOIN ex_Products AS P
ON P.ProductID = O.ProductID
;]]>
            </version>
          </versions>
        </add>
      </procedures>
    </data>
  </mynamespace>

Want to contribute?

SQLppNet is an open source project that always needs new people and ideas to evolve. If you are interested, please drop a message on the SourceForge page.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Jonathan de Halleux
Engineer
United States United States
Member
Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSupport for Oracle ?memberalhambra-eidos10 Jul '09 - 2:37 
Thanks misters.
 
Great !!
 
AE

GeneralFree template based DAL generatormemberm2xdam15 Nov '05 - 20:57 
Try this free template based generator. It works quite simple and easy...
 
http://www.m2xdam.nl/bin/DALGenerator101.zip[^]

GeneralSQL in xml catalogmembergabo_uy31 Dec '04 - 6:31 
Check out XDataAccess its a DAL, that uses xml to store sql instructions, and leave them out of the DAL code.
 
gabo_uy
Questionhow to upload aphoto into oracle data base throw webmemberking_hima25 Oct '04 - 23:07 
hi all . i made oracle web based application and i have a problem that i need to save photos and vedios and sounds in oracle database throw web. i use her java servlets and html . i was saving the locaiton of image and when i need to see it again i retrive this location from data base and view this photo . but their is another problem that it's dosent work in the web. it dosent work as uploading.
plz help me if you can .
thanx alot.

GeneralDAL documentsussDavid Pokluda14 Apr '04 - 23:52 
I like the SQLpp but I can't get DAL document from DotNetNuke. Could you help me with this?
 
Regards,
David.
GeneralRe: DAL documentmemberJonathan de Halleux15 Apr '04 - 1:12 
It is named Data Access WhitePaper
 
http://www.dotnetnuke.com/Default.aspx?tabid=478[^]
 
I have uploaded a few days ago the latest version of SQLpp (http://slqpp.sourceforge.net[^] ).

 
Jonathan de Halleux - www.dotnetwiki.org -
MbUnit - QuickGraph - NCollection

Generalshould connect to any data basememberSUBZ9312 Mar '04 - 23:21 
Confused | :confused: need help to develop a project got stock in making asp.net module for connectiong to "any" database .the user should be able to connect to the database of its choice.THE SAME WAY AS WE GET A MENU FOR CONNECTIONG TO THE DATABASE FOR MAKING A DATAFORM IN .NET FOR CONECTING TO TE DATA BASE.
GeneralRe: should connect to any data basememberJonathan de Halleux15 Mar '04 - 20:29 
SUBZ93 wrote:
need help
 
Good luck ?
 
Jonathan de Halleux.

www.dotnetwiki.org

GUnit

GeneralData ValidationmemberNigel Thorne5 Feb '04 - 15:19 
I have an object with a start_date and an end_date. I want to ensure the start_date is earlier than the end_date. Which object would this code go in?

GeneralTwo Foreign key with the same tablemembertonno31 Jan '04 - 8:01 
How can I create two foreign key with the same table?

GeneralNUnit Testing of Generated Codememberanup_ag213 Nov '03 - 6:45 
Hi.. First of all I want to express my sincere thanks for writing such a useful code. I am however facing some problems associated with NUnit testing of the generated code by DAL Generator.
 
As far as I understand this code, It needs cache and therfore I need to add a web project sothat it can get the cache and execute normally. Is it possible to modify this code (in instance() method) sothat I can assign dataprovide without cache.
 
Looking for your comments.
 
Regards,
Anup
 

GeneralRe: NUnit Testing of Generated CodememberJonathan de Halleux3 Nov '03 - 8:24 
It is important to use the cache in terms of performance. Indeed, Reflection could slow down the process.
 
The generated DAL is done for web application (it reads the web.config) so I don't know you can use it outside of a web application? Maybe you have a suggestions.
 
Please use the sourceforge site for such feature requests, thanks.
 
Jonathan de Halleux.

www.pelikhan.com

GeneralRe: NUnit Testing of Generated CodememberJonathan de Halleux3 Nov '03 - 8:32 
Note that I'm currently working on User control generation Smile | :)
 
Jonathan de Halleux.

www.pelikhan.com

GeneralRe: NUnit Testing of Generated Codemembercodar ismail2 Oct '04 - 1:04 
1-Change your dll to console application and add a new app.config file with DAL configs.
2-Rename .config file to yourdll.dll.config and test it.
I am using this methods with NUnit and its working.
GeneralAbout Bug report, feature request and others (Message from the autor)memberJonathan de Halleux1 Nov '03 - 0:51 
Please (don't hesitate to) post your bug reports, feature requests and other message on the sourceforge site:
 
http://sf.net/projects/sqlpp[^]
 
It is easier to track the bugs Smile | :)
 
Jonathan de Halleux.

www.pelikhan.com

GeneralSQLppNET errormemberX0nic31 Oct '03 - 22:36 
Does anyone else get an error with SQLppNET?
I get this exception generated once code that uses SQLppNET loads.
 
An unhandled exception of type 'System.IO.FileNotFoundException' occurred in system.windows.forms.dll
 
Additional information: File or assembly name SQLppNET, or one of its dependencies, was not found.
 
The file does exist, and i have MS DA also referenced in the project.
The version is 2.3.1 and i dont get the error with 2.2 (although generate creates an exception)
 
Thanks for your help,
GeneralRe: SQLppNET errormemberJonathan de Halleux1 Nov '03 - 0:49 
Please post this message on the sourceforge site thanks... I'm working on it.
 
Jonathan de Halleux.

www.pelikhan.com

QuestionHow do I model columns that are both primary and foreign keysmemberDavid Montgomery29 Oct '03 - 5:56 
I'm trying to model something very similar to the EmployeeStores table in the original sqlpp article. I have 2 columns that act as part of a multi-part primary key _AND_ as foreign keys into other tables.
AnswerRe: How do I model columns that are both primary and foreign keysmemberJonathan de Halleux29 Oct '03 - 6:10 
First add them as foreign keys to the table and then define them as primary key:
 
The code in C++ would look like:
table_shared_ptr A, B; // 3 tables with primary AID, BID
table_shared_ptr C; // you multi PK table

// adding foreign keys
C->add_foreign_key( A );
C->add_foreign_key( B );
 
// setting primary key
C->set_primary_key( "AID,BID" );
 
or
 
field_container_type f;
f->push_back( C->get_field("AID") );
f->push_back( C->get_field("BID") );
C->set_primary_key( f );
 
You are using it ? Do you have feedback ?
 
Jonathan de Halleux.

www.pelikhan.com

GeneralRe: How do I model columns that are both primary and foreign keysmemberDavid Montgomery3 Nov '03 - 6:36 
That worked beautifully, thanks. I hadn't noticed the SetPrimaryKey method before -- just the AddPrimaryKey.

GeneralRe: How do I model columns that are both primary and foreign keysmemberJonathan de Halleux3 Nov '03 - 8:28 
If you have feedback, please don't hesitate.
 
Jonathan de Halleux.

www.pelikhan.com

QuestionWhy not use DataTables for storing the retrieved data?memberSumeet Kumar23 Oct '03 - 18:11 
Hi.
 
The code generation logic and also the technique of specifying joins and all is pretty neat.
 
My issues are not with the generated tiers (The generator i feel is very cool), but architectural issues such as the need to join tables on the client side (versus the database itself), the representation of data within the tiers and the location of process oriented code.
 
1) Why join in code?
 
Is it not better to create views on the database and use these - this way hints, etc specific to each database could be used to optimize the operations and also provide the facility of having view based triggers, etc
 
2) Why use custom fields and arrays to store the data?
 
Most DAL's take this approach probably because of previous experience with earlier enivornments.
 
However, .Net has excellent disconnected data representation in the form of datatables that provide many features that no DAL generator code that i have seen provides viz. filtering, sorting, concurrency checking by remembering the previously fetched values, read/write databinding, notification events, etc.
 
DataTables seem to store data very efficiently (thru my explorations of datatable, datarow and datacolumn using reflector)
 
DataTables remote well across process and machine boundaries.
 
They can be saved to XML or retrieved from XML providing better interoperability with third part applications in a B2B setting.
 
Another advatage occurs when the user adds a record to a collection of 1000 records - with datatables it is very easy to use GetChanges to get a new datatable with only the changed record and pass only that record back to the data tier for saving to the database. Alternatively if 10 records were added they can be sent acroos with one call itself.
 
I feel that within the application, data should be represented by datatables with typed datarows (very similar to the .Net typed dataset, but based on a datatable with typed rows that provide early bound properties for each field). Datatables have a overridable method "GetRowType" that automatically adds the custom row derived from DataRow to datatables during population from datadapter/datareader.
 
Thus a single Class represents both Collection of records as well as a single record - the class itself is a wrapper for the DataTable (with custom methods such as Add, Delete, Load, Save, GetByID, etc) and each individual record is represented by the TypedDataRow that constitutes the DataTable and provides early bound properties for each field. It is very convenient to iterate thru all the records in the collection using for each on the datatable.
 
Also, the facade class can implement IComponent and thus be added to the toolbox providing developers the convenience of design time binding of data with user controls on a winform or a webform.
 
3) Why have process oriented code outside the database?
 
As long as the process involves data from a single database, it is much more efficient to implement it is a stored procedure and call the SP from the DAL. This is actually much more efficient because the multiple fetches and saves typically required occur with the RDBMS. Another advantage is the ease of changing the business logic without needing to compile the application.
 
-------------------------------------------------------------
The above comments are from my past experience in developing ERP applications in the healthcare setting and my on going work in improving past frameworks given the new facilites offerred by .Net
-------------------------------------------------------------
 
Hope this promotes a good discussion on the topic.
 

 
Sumeet
AnswerRe: Why not use DataTables for storing the retrieved data?memberJonathan de Halleux23 Oct '03 - 22:24 
Hi, some answer to your question + questions:
 

Sumeet Kumar wrote:
1) Why join in code?
 
I must be misunderstood: no join happens in the code itself. Joins are performed in the database. If you look at the stored procedure for MySql, it contains the "joining" instruction.
SELECT 
U.UserID AS 'UserID',
P.ProductName AS 'ProductName',
O.ProductPrice AS 'Price',
O.OrderQuantity AS 'Quantity'
FROM (ex_Orders AS O 
INNER JOIN Users AS U
ON U.UserID = O.UserID)
INNER JOIN ex_Products AS P
ON P.ProductID = O.ProductID
 

Sumeet Kumar wrote:
2) Why use custom fields and arrays to store the data?
 
To my personnal opinion, to avoid doing direct call to the IDataReader class (avoid spelling errors in field names) and , since I'm lazy), take advantage of the intellisence. As soon as you create a IDataReader wrapper, you know which fields it contains by looking up it's properties.
 
Nevertheless, I'm not sure I fully understand this point, I'll have to get a closer look to the DataTabel doc.
 
Sumeet Kumar wrote:
3) Why have process oriented code outside the database?
 
As in point 1, all SQL calls are stored in stored procedure if this feature is available from the database system. For example, MySql does not support stored procedures. On the other side, MsSql supports stored procedures, so this is the code generated from MsSql (Order table):
 
/*
   Stored procedures
*/
USE MyCatalog
 
GO
 

/*
    Get procedure for the ex_Orders table
*/
IF EXISTS (
    SELECT name FROM dbo.sysobjects
    WHERE name = 'ex_GetOrder'
      AND type = 'P'
    )DROP PROCEDURE dbo.ex_GetOrder
GO
 
CREATE PROCEDURE dbo.ex_GetOrder
@OrderID INT
AS
SELECT 
*
FROM ex_Orders
WHERE 
ex_Orders.OrderID = @OrderID
 
GO
 
GO
 
/*
    Delete procedure for the ex_Orders table
*/
IF EXISTS (
    SELECT name FROM dbo.sysobjects
    WHERE name = 'ex_DeleteOrder'
      AND type = 'P'
    )DROP PROCEDURE dbo.ex_DeleteOrder
GO
 
CREATE PROCEDURE dbo.ex_DeleteOrder
@OrderID INT
AS
DELETE FROM ex_Orders
WHERE ex_Orders.OrderID = @OrderID
 
GO
 
IF EXISTS (
    SELECT name FROM dbo.sysobjects
    WHERE name = 'ex_AddOrder'
      AND type = 'P'
    )DROP PROCEDURE dbo.ex_AddOrder
GO
 
/*
    Insertion procedure for the ex_Orders table
*/
CREATE PROCEDURE dbo.ex_AddOrder
@UserID INT,
@ProductID INT,
@OrderQuantity INT,
@OrderTimestamp TIMESTAMP
AS
INSERT INTO ex_Orders
(UserID, ProductID, OrderQuantity, OrderTimestamp)
VALUES (@UserID, @ProductID, @OrderQuantity, @OrderTimestamp)
 SELECT CAST(@@Identity AS INTEGER) 
GO
 
IF EXISTS (
    SELECT name FROM dbo.sysobjects
    WHERE name = 'ex_UpdateOrder'
      AND type = 'P'
    )DROP PROCEDURE dbo.ex_UpdateOrder
GO
 
/*
    Update procedure for the ex_Orders table
*/
CREATE PROCEDURE dbo.ex_UpdateOrder
@OrderID INT,
@UserID INT,
@ProductID INT,
@OrderQuantity INT,
@OrderTimestamp TIMESTAMP
AS
UPDATE ex_Orders
SET
    UserID=@UserID
    , ProductID=@ProductID
    , OrderQuantity=@OrderQuantity
    , OrderTimestamp=@OrderTimestamp
WHERE
ex_Orders.OrderID = @OrderID
 
GO

 
Jonathan de Halleux.

www.pelikhan.com

GeneralRe: Why not use DataTables for storing the retrieved data?memberSumeet Kumar24 Oct '03 - 3:16 
Jonathan,
 
Yes, you are right, the join is in the database, not in code. I should have said, instead of having joined SQL, it may be better to create a view and fetch from it.
 
Regarding the datatable point.
 
Yes, the custom classes do give intellisense a very good feature.
 
What I was referring to is of having the custom classes wrap a datarow instead of having internal variables store the data, thus getting the best of both worlds.
 
The structure would be something like this:
 
----------------------------------------------------------------
' a skeleton example of a facade class that stores its data
' in a TypedDataTable which is populated with TypedDataRows
' and of client code that can acces the data using early bound properties
 
'the underlying plumbing that fills the datatable with data and saves it to the database can be implemented in many ways
'including the DALs posted by Jonathan and Deyan on CodeProject.
 
''sample code
Public Class Test
 
'Example of client code
Public Sub LoopSample(ByVal facade As facadeProduct)
Dim row As facadeProduct.TypedDataRow
 
For Each row In facade.DataTable.Rows
Debug.Print(row.MRN & " - " & row.FIRSTNAME)
Next
 
End Sub
End Class
 

'the facade class
Public MustInherit Class facadeProduct
'implementing IComponent enables this class to be a toolbox item and be dropped onto a form
'code for implementation is not shown in this snippet
Implements IComponent
 
'the implementation of these simply delegates to the contained datatable.rows collection
'it allows datagrid and textboxes, etc to be directly bound to an instance of this class dropped on the form
'code for implementation is not shown in this snippet
Implements IList, IListSource
 
'this datatable contains a collection of rows from the Product Table
Protected m_DataTable As TypedDataTable
 
'allow external access to the data
'the DAL layer calls DataAdapter.Fill(facadeProduct.dataTable) to populate with data fetched from database
'the overridden GetRowType method of the TypedDataTable ensures the datatable gets populated by instances of our TypedDataRow
'TypedDataRow contains properties that wrap the datarow colums and allow intellisense to be used in client code
Public ReadOnly Property DataTable() As TypedDataTable
Get
Return m_DataTable
End Get
End Property
 
Public Sub New()
MyBase.new()
End Sub
 
'TypedDataRow contains properties that wrap the datarow colums and allow intellisense to be used in client code
Public Class TypedDataRow
Inherits DataRow
 
Public Sub New(ByVal rb As DataRowBuilder)
MyBase.New(rb)
End Sub
 
'expose field MRN
Public Property MRN() As String
Get
'Note: nz is a custom function converts DbNull values to an empty string, etc
Return nz(Me.Item("MRN"), TypeCode.String)
End Get
 
Set(ByVal Value As String)
Me.Item("MRN") = Value
End Set
End Property
 
'expose field FirstName
Public Property FIRSTNAME() As String
Get
Return nz(Me.Item("FIRSTNAME"), TypeCode.String)
End Get
 
Set(ByVal Value As String)
Me.Item("FIRSTNAME") = Value
End Set
End Property
 
End Class 'TypedDataRow
 
'this is required to override
Public Class TypedDataTable
Inherits DataTable
 
''this tells the builder to create instances of TypedDataRow when populating from datareader or dataadapter
Protected Overrides Function GetRowType() As System.Type
Return GetType(TypedDataRow)
End Function
 
''this is called by the dataadapter/datagrid while filling the datatable, to add new rows
Protected Overrides Function NewRowFromBuilder(ByVal builder As DataRowBuilder) As DataRow
Return New TypedDataRow(builder)
End Function
End Class 'TypedDataTable
 
End Class
 
-----------------------------------------------------------------
 
Sumeet
GeneralRe: Why not use DataTables for storing the retrieved data?memberJonathan de Halleux24 Oct '03 - 3:30 
Sumeet Kumar wrote:
it may be better to create a view and fetch from it
 
Yep, but the only problem is that I'm aiming multiple database systems such as MySql that does not supports views yet.

 
Jonathan de Halleux.

www.pelikhan.com

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 23 Oct 2003
Article Copyright 2003 by Jonathan de Halleux
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid