New CoNatural Components 1.4 Released to CodePlex

Roger Torres - February 4th, 2010

This morning I released a new version of the CoNatural Components (version 1.4) to the CodePlex repository. There are some significant changes to CoNatural.Data, so existing code will require some modifications in order to compile with this version. The main theme is the re-factoring work started in v1.3 to follow interface oriented programming practices in order to make the framework more testable and IoC friendly. There are also some new features I will list below.

Release Notes

  • New interfaces

    • IParameter: Data parameter to command property mapping interface. A concrete command factory instantiates commands with parameter mappings. A default implementation can be found in the /Base folder (ParameterBase).
    • ITypeMaterializer<T>, ITypeMaterializerFactory: The components in charge of processing data reader results, instantiating types of T, and populating them with the results of each data record returned by the command reader. Default implementation using reflection with caching can be found in the /Base folder. Other implementations, using XML mappings for example, are possible.
    • ICommandDeployer: The component in charge of deploying CoNatural data commands as sql stored procedures (creating or updating existing procedures). This functionality was moved from IConnection-IDbProvider to this new interface to decouple the basic framework from deployment scenarios. The default base class in /Base as usual, and a concrete SqlServer implementation can be found in the CoNatural.Data.SqlServer project (SqlServerCommandDeployer ).
    • ITypeMapper<T>, ITypeMapperFactory: The components in charge of mapping commands bound to instances of type T. This basically replaces the ICommand<T> pattern in version 1.3, thus a code change is required to support the new pattern. Default TypeMapperBase, TypeMapperFactoryBase implementations can be found in the /Base folder, very similar to the default type materializer (using reflection and caching the mappings). IConnection now supports DefaultTypeMapperFactory and DefaultTypeMaterializerFactory properties (using /base by default) as a fallback mechanism to automatically perform the mapping of properties when no mapper is passed to the execution methods.
    • ICommand: Added GetScript() method to ICommand interface. This allows users to embed SQL scripts inside command classes, making the extra .sql embedded resource file in projects an option (not a requirement). Modified basic command factory, script provider, and Visual Studio templates/Add-In to accommodate this change. When GetScript() returns null, the runtime calls the script provider associated to the connection. A CommandBase class was also added to the /Base folder with GetScript() returning null by default (useful to find/replace : ICommand to : CommandBase for a quick fix to existing commands).
  • Project organization: As noted in the previous point, the project was reorganized to hold the interfaces in the main folder, and the default base class implementations in the /Base sub-folder (All base classes now with the XXXBase suffix). There are also folders for the attributes (/Attributes), and for each concrete database provider (/Odbc, /OleDb, /SqlClient). Renamed ICommandFactory to IDataCommandFactory, and moved default command factory to /Base folder.
  • Helpers to simplify the API: New connection and dbprovider constructors added to all providers (SqlClient, Odbc, OleDb) using the default command and script factories.
  • New Add-In features: A new wizard was implemented to generate table adapters (CRUD commands) from Sql Server tables. As mentioned above, there is a new option to generete .sql embedded resource files or inline SQL returned from GetScript() when importing stored procedures and tables.

Here are some AdventureWorks commands I just imported with the SQL script embedded in the .cs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//-------------------------------------------------------
//-- uspGetEmployeeManagers.cs
//--
//-- Created by Roger with CoNatural Visual Studio AddIn 
//-- Created on 1/31/2010 1:38:42 PM
//-------------------------------------------------------
 
using System;
using System.Data;
using CoNatural.Data;
 
namespace ConsoleApplication1.Commands.dbo {
	public class uspGetEmployeeManagers : ICommand {
		public uspGetEmployeeManagers() {}
 
		public string GetScript() { 
			return 
@"
BEGIN
SET NOCOUNT ON;
WITH [EMP_cte]([BusinessEntityID], [OrganizationNode], [FirstName], [LastName], [JobTitle], [RecursionLevel]) -- CTE name and columns
AS (
SELECT e.[BusinessEntityID], e.[OrganizationNode], p.[FirstName], p.[LastName], e.[JobTitle], 0 -- Get the initial Employee
FROM [HumanResources].[Employee] e 
INNER JOIN [Person].[Person] as p
ON p.[BusinessEntityID] = e.[BusinessEntityID]
WHERE e.[BusinessEntityID] = @BusinessEntityID
UNION ALL
SELECT e.[BusinessEntityID], e.[OrganizationNode], p.[FirstName], p.[LastName], e.[JobTitle], [RecursionLevel] + 1 -- Join recursive member to anchor
FROM [HumanResources].[Employee] e 
INNER JOIN [EMP_cte]
ON e.[OrganizationNode] = [EMP_cte].[OrganizationNode].GetAncestor(1)
INNER JOIN [Person].[Person] p 
ON p.[BusinessEntityID] = e.[BusinessEntityID]
)
SELECT [EMP_cte].[RecursionLevel], [EMP_cte].[BusinessEntityID], [EMP_cte].[FirstName], [EMP_cte].[LastName], 
[EMP_cte].[OrganizationNode].ToString() AS [OrganizationNode], p.[FirstName] AS 'ManagerFirstName', p.[LastName] AS 'ManagerLastName'  -- Outer select from the CTE
FROM [EMP_cte] 
INNER JOIN [HumanResources].[Employee] e 
ON [EMP_cte].[OrganizationNode].GetAncestor(1) = e.[OrganizationNode]
INNER JOIN [Person].[Person] p 
ON p.[BusinessEntityID] = e.[BusinessEntityID]
ORDER BY [RecursionLevel], [EMP_cte].[OrganizationNode].ToString()
OPTION (MAXRECURSION 25) 
END;
"; 
		}
 
		public Int32? BusinessEntityID { get; set; }
	}
}

These are CRUD commands for the Department table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
//-------------------------------------------------------
//-- DepartmentInsert.cs
//--
//-- Created by Roger with CoNatural Visual Studio AddIn 
//-- Created on 1/31/2010 12:39:08 PM
//-------------------------------------------------------
 
using System;
using System.Data;
using CoNatural.Data;
 
namespace ConsoleApplication1.Commands.HumanResources {
	public class DepartmentInsert : ICommand {
		public DepartmentInsert() {}
 
		public string GetScript() { 
			return 
@"
INSERT INTO [HumanResources].[Department] (
   [Name],
   [GroupName],
   [ModifiedDate])
VALUES (
   @Name,
   @GroupName,
   @ModifiedDate)
SET @DepartmentID = SCOPE_IDENTITY();
"; 
		}
 
		[Parameter(50)] public string Name { get; set; }
		[Parameter(50)] public string GroupName { get; set; }
		public DateTime? ModifiedDate { get; set; }
		[Parameter(ParameterDirection.Output)] public Int16? DepartmentID { get; set; }
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
//-------------------------------------------------------
//-- DepartmentSelect.cs
//--
//-- Created by Roger with CoNatural Visual Studio AddIn 
//-- Created on 1/31/2010 12:39:08 PM
//-------------------------------------------------------
 
using System;
using System.Data;
using CoNatural.Data;
 
namespace ConsoleApplication1.Commands.HumanResources {
	public class DepartmentSelect : ICommand {
		public DepartmentSelect() {}
 
		public string GetScript() { 
			return 
@"
SELECT
   @DepartmentID = [DepartmentID],
   @Name = [Name],
   @GroupName = [GroupName],
   @ModifiedDate = [ModifiedDate]
FROM
   [HumanResources].[Department]
WHERE
   [DepartmentID] = @DepartmentID"; 
		}
 
		[Parameter(ParameterDirection.InputOutput)] public Int16? DepartmentID { get; set; }
		[Parameter(ParameterDirection.Output, 50)] public string Name { get; set; }
		[Parameter(ParameterDirection.Output, 50)] public string GroupName { get; set; }
		[Parameter(ParameterDirection.Output)] public DateTime? ModifiedDate { get; set; }
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 
//-------------------------------------------------------
//-- DepartmentUpdate.cs
//--
//-- Created by Roger with CoNatural Visual Studio AddIn 
//-- Created on 1/31/2010 12:39:08 PM
//-------------------------------------------------------
 
using System;
using System.Data;
using CoNatural.Data;
 
namespace ConsoleApplication1.Commands.HumanResources {
	public class DepartmentUpdate : ICommand {
		public DepartmentUpdate() {}
 
		public string GetScript() { 
			return 
@"
UPDATE [HumanResources].[Department] SET
   [Name] = @Name,
   [GroupName] = @GroupName,
   [ModifiedDate] = @ModifiedDate
WHERE
   [DepartmentID] = @DepartmentID"; 
		}
 
		public Int16? DepartmentID { get; set; }
		[Parameter(50)] public string Name { get; set; }
		[Parameter(50)] public string GroupName { get; set; }
		public DateTime? ModifiedDate { get; set; }
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
//-------------------------------------------------------
//-- DepartmentDelete.cs
//--
//-- Created by Roger with CoNatural Visual Studio AddIn 
//-- Created on 1/31/2010 12:39:08 PM
//-------------------------------------------------------
 
using System;
using System.Data;
using CoNatural.Data;
 
namespace ConsoleApplication1.Commands.HumanResources {
	public class DepartmentDelete : ICommand {
		public DepartmentDelete() {}
 
		public string GetScript() { 
			return 
@"
DELETE FROM [HumanResources].[Department] WHERE
   [DepartmentID] = @DepartmentID"; 
		}
 
		public Int16? DepartmentID { get; set; }
	}
}

Let’s read all the managers for business entities 1 to 4, and print the results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CoNatural.Data;
using CoNatural.Data.SqlClient;
 
namespace ConsoleApplication1 {
	class Program {
		static void Main(string[] args) {
			try {
				IConnection c = new SqlClientConnection(@"Data Source=PHENOM-II\PHENOM_II;Initial Catalog=AdventureWorks2008;Integrated Security=True");
				Commands.dbo.uspGetEmployeeManagers cmd = new Commands.dbo.uspGetEmployeeManagers();
 
				new int[] { 1, 2, 3, 4 }.All(i => {
					Console.WriteLine("BusinessEntityId = " + i);
					cmd.BusinessEntityID = i;
					c.ExecuteReader<Model.Employee>(cmd).All(e => {
						Console.WriteLine(e);
						return true;
					});
					return true;
				});
			}
			catch (Exception ex) {
				Console.WriteLine(ex.Message);
			}
			Console.ReadLine();
		}
	}
}
 
namespace ConsoleApplication1.Model {
	public class Employee {
		public string FirstName { get; set; }
		public string LastName { get; set; }
		public string ManagerFirstName { get; set; }
		public string ManagerLastName { get; set; }
 
		public override string ToString() {
			return string.Format("{0} {1} - managed by {2} {3}", FirstName, LastName, ManagerFirstName, ManagerLastName);
		}
	}
}

Can you see the default type materializer in action?

Conclusion

I believe this framework is now pretty stable, so I don’t anticipate any major design changes … but you never know what the next requirement is going to look like. I hope we will always find a good concrete implementation ;-)

Leave a comment