QuickStart (I)

The source code for this quick start can be found in the form of a downloadable Visual Studio 2008 solution.

For this quick start, we will define a very simple domain model : our application will deal with products, whose sole property is their name.
UML.png
The associated database could be created with the following script :
CREATE TABLE Products (
    ProductID INT IDENTITY(1, 1) NOT NULL,
    ProductName VARCHAR(40) NOT NULL
    CONSTRAINT PRIMARY KEY (ProductID)
);

This is indeed very simple, but has the advantage to enable us to use the famous Northwind sample as a test database.

Based on this model, we can create our Data Access Layer. What we need first is a class to handle the primary key for our product. We have a base class for it, named PrimaryKey<T> in the Salamanca.DataAccess namespace :
using System;
using Salamanca.DataAccess;

namespace QuickStart
{
    public class ProductPrimaryKey:
        PrimaryKey<int>
    {
        public ProductPrimaryKey():
            base()
        {
        }

        public ProductPrimaryKey(int key):
            base(key)
        {
        }

        public static implicit operator ProductPrimaryKey(int key)
        {
            return new ProductPrimaryKey(key);
        }

        public static explicit operator int(ProductPrimaryKey key)
        {
            return key.Value;
        }
    }
}

Then we need a class to be used as a data holder. More specifically, it will be used as a Data Transfer Object :
using System;
using Salamanca.DataAccess;

namespace QuickStart
{
    public class ProductDataTransfer:
        IDataTransferObject
    {
        public object Clone()
        {
            return MemberwiseClone();
        }

        public int Id;
        public string Name;
    }
}

What remains to be defined is our Domain Model and our Data Mapper. To avoid too tight coupling between both of them, we'll define an interface that will have to be implemented by a product related Data Mapper :
using System;
using Salamanca.DataAccess;
using Salamanca.DataAccess.Collections;

namespace QuickStart
{
    public interface IPersonMapper:
        IDataMapper
    {
        Product Find(ProductPrimaryKey key, DataMapperCollection dataMappers);
        DomainModelKeyedCollection<Product> FindAll(DataMapperCollection dataMappers);
    }
}

For a starter, we'll only need two methods in a Data Mapper :
  • one to get a specific product instance from the data backend.
  • one to get all the products form the data backend. The DomainModelKeyedCollection returned can be used a regular Collection, as well as a Dictionary where products are indexed with their primary keys.

Now we're ready to define our Domain Model :
using System;
using System.Collections.Generic;
using Salamanca.DataAccess;
using Salamanca.DataAccess.Collections;
using Salamanca.DataRules;


namespace QuickStart
{
    public class Product:
        DomainModel<ProductPrimaryKey, ProductDataTransfer>
    {

        public Product():
            base()
        {
        }

        public Product(DataMapperCollection dataMappers):
            base(dataMappers)
        {
        }

        protected override ProductPrimaryKey CreateId()
        {
            return new ProductPrimaryKey(Data.Id);
        }

        public static Product Find(ProductPrimaryKey key, DataMapperCollection dataMappers)
        {
            return ((IProductMapper)dataMappers[typeof(Product)]).Find(key, dataMappers);
        }

        public static IList<Product> FindAll(DataMapperCollection dataMappers)
        {
            return ((IProductMapper)dataMappers[typeof(Product)]).FindAll(dataMappers);
        }

        internal void SetId(int id)
        {
            Data.Id=id;
        }

        [StringLengthRule(40)]
        [NotNullRule]
        public string Name
        {
            get
            {
                Load(true);
                return Data.Name;
            }
            set
            {
                if (Data.Name!=value)
                {
                    Data.Name=value;
                    RaisePropertyChanged("Name");
                }
            }
        }
    }
}

As you can see, this is quite straightforward : just inherit from Salamanca.DataAccess.DomainModel, override CreateId and add your properties. The static methods have just been defined here for convenience. Also, note the attributes above the Name property, that define our first business rules.

Final step : define a Data Mapper for our product. We'll choose the ADO .NET based abstract implementation that is found in the Salamanca.DataAccess.Data namespace as a base :
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using Salamanca.DataAccess;
using Salamanca.DataAccess.Collections;
using Salamanca.DataAccess.Data;

namespace QuickStart
{
     public partial class ProductMapper:
         DataMapper<Product , ProductDataTransfer ProductPrimaryKey>,
         IProductMapper
     {
         public ProductMapper(DbConnection connection, ICacheManager cacheManager):
             base(connection, DomainModelCreator.CreateDefault<Product>(), cacheManager)
         {
         }
    
         public DomainModelKeyedCollection<Product> FindAll(DataMapperCollection dataMappers)
         {
             IDbCommand[] commands=new IDbCommand[1];
    
             commands[0]=Connection.CreateCommand();
             commands[0].CommandType=CommandType.Text;
             commands[0].CommandText="SELECT ProductID, ProductName FROM Products";
    
             return Load(commands, dataMappers);
         }
    
         protected override IList<IDbCommand> CreateSelectCommands(ProductPrimaryKey key)
         {
             IDbCommand[] commands=new IDbCommand[1];
    
             commands[0]=Connection.CreateCommand();
             commands[0].CommandType=CommandType.Text;
             commands[0].CommandText="SELECT ProductName FROM Products WHERE ProductID=@Id";

             IDbDataParameter p=commands[0].CreateParameter();
             p.ParameterName="@Id";
             p.DbType=DbType.Int32;
             p.Value=key.Value;
             p.Direction=ParameterDirection.Input;
             commands[0].Parameters.Add(p);
    
             return commands;
         }

         protected override IList<IDbCommand> CreateDeleteCommands(ProductDataTransfer data)
         {
             IDbCommand[] commands=new IDbCommand[1];
    
             commands[0]=Connection.CreateCommand();
             commands[0].CommandType=CommandType.Text;
             commands[0].CommandText="DELETE FROM Products WHERE ProductID=@Id";
    
             IDbDataParameter p=commands[0].CreateParameter();
             p.ParameterName="@Id";
             p.DbType=DbType.Int32;
             p.Value=data.Id;
             p.Direction=ParameterDirection.Input;
             commands[0].Parameters.Add(p);
    
             return commands;
         }

         protected override IList<IDbCommand> CreateInsertCommands(ProductDataTransfer data)
         {
             IDbCommand[] commands=new IDbCommand[1];
    
             commands[0]=Connection.CreateCommand();
             commands[0].CommandType=CommandType.Text;
             commands[0].CommandText="INSERT INTO Products (ProductName) VALUES(@ProductName)";
    
             IDbDataParameter p=commands[0].CreateParameter();
             p.ParameterName="@Name";
             p.DbType=DbType.String;
             p.Value=data.Name;
             p.Direction=ParameterDirection.Input;
             commands[0].Parameters.Add(p);
    
             return commands;
         }

         protected override IList<IDbCommand> CreateUpdateCommands(ProductDataTransfer data)
         {
             IDbCommand[] commands=new IDbCommand[1];
    
             commands[0]=Connection.CreateCommand();
             commands[0].CommandType=CommandType.Text;
             commands[0].CommandText="UPDATE Products SET ProductName=@Name WHERE ProductID=@Id";
    
             IDbDataParameter p=commands[0].CreateParameter();
             p.ParameterName="@Name";
             p.DbType=DbType.String;
             p.Value=data.Name;
             p.Direction=ParameterDirection.Input;
             commands[0].Parameters.Add(p);
    
             p=commands[0].CreateParameter();
             p.ParameterName="@Id";
             p.DbType=DbType.Int32;
             p.Value=data.Id;
             p.Direction=ParameterDirection.Input;
             commands[0].Parameters.Add(p);
    
             return commands;
         }

         protected override ProductDataTransfer GetDataTransferObject(IList<IDataRecord> records)
         {
             ProductDataTransfer data=new ProductDataTransfer();

             int ord=records[0].GetOrdinal("ProductID");
             if (!records[0].IsDBNull(ord))
                 data.Id=records[0].GetInt32(ord);

             ord=records[0].GetOrdinal("ProductName");
             if (!records[0].IsDBNull(ord))
                 data.Name=records[0].GetString(ord);
    
             return data;
         }
    
         protected override void OnInserted(Product domainModel, IList<IDbCommand> commands)
         {
             IDbCommand command=Connection.CreateCommand();
             command.CommandType=CommandType.Text;
             command.CommandText="SELECT @@IDENTITY";
             command.Transaction=commands[0].Transaction;
    
             domainModel.SetId(Convert.ToInt32(command.ExecuteScalar()));
    
             base.OnInserted(domainModel, commands);
         }
     }
}

That's it : inherit from DataMapper and fill the gaps with pure ADO .NET code. You can already understand that this is where code generation will greatly improve productivity.

Our Data Access Layer is now complete : we can build a simple application based on it. The only (?) tricky thing is that you will need to create an instance of DataMapperCollection. For our sample, simply use this code :
DataMapperCollection _DataMappers=new DataMapperCollection();
_DataMappers.Add(typeof(Product), new ProductMapper(Connection, new NoCacheManager()));

It creates a new collection based on a prexisting Connection, and our data will simply not be cached in memory.

You can download a complete working example, as a Visual Studio 2008 solution. As you can see, we can build a simple application that integrates our Domain Model in a very few lines of code, with business rules validation (the underlying database is Northwind on SQL Server, for which a data file is included in the downloadable sample).
QuickStart.png

In the second part, we will see how to create and implement our first basic activity based on this domain model.

Last edited Oct 22, 2009 at 8:17 AM by cartoixa, version 2

Comments

No comments yet.