EditData Layer:
Using a layered approach when building out a framework gives you lots of flexibility especially when it comes to your data layer.
EditWhy a separate layer for the data access?
There are many valid reasons why you would want to separate out the data access layer from other layers. Keeping the data access layers separate give you the ability to create a data abstraction API. This means that you create an API for your data, which actually knows nothing about how to retrieve data from your database, web service, xml store, etc. Microsoft has really pushed the provider pattern, leading to excellent flexibility in your applications. Think of the provider pattern as a reverse plug-in pattern. A plug-in exists in only certain directories and the discovery is done by the client and is typically loaded in at application start. Whereas, a provider is entered through the configuration and loaded in at runtime. The benefit is that you can write several providers depending on your needs. Using a provider allows you to switch out your entire implementation just by changing the default provider name in your configured implementation. These layers, the abstraction data layer and the provider specific implementation layer, reference the Entities project as a dependancy. When working with data retrieved from the database, the entities and collections are actually created from these layers and returned to the consuming methods.
EditArchitecture Example:
Take for example, you are wanting to create a new project to revolutionize the CRM application marketplace. You have the idea of building this application as a smart client, and server application. So you sit requirement is to create an excellent client server application. This means, that you can generate a single codebase, and have access to your data API out of the box through web services for your client application. On the client you would use the WsClientProvider, while the server application hosting the WebService endpoint would use the SqlClientProvider.
EditSupported Databases:
Currently .netTiers supports Sql Server 2000, Sql Server 2005, & Sql Server Express. Several other database providers like SqlLite, Firebird, and Oracle have limited support with the available Generic Client.
EditWhat is a DataRepository?
The DataRepository is the entry point into your data access API using the default configured data provider. It is the central mechanism for instantiating and loading the individual providers at runtime, and more importantly, retrieving and saving data for your data API. The DataRepository itself is essentially a singleton facade object into your API by using the Decorator Pattern of the individual TableModule Entity Providers. This class lives in the abstract Data Access Layer as mentioned earlier so that it does not reference any particular provider implementation, which only knows about all of your entities, their access methods.
Some typical method call looks like:
Example:
1
DataRepository.OrdersProvider.GetAll();
2
DataRepository.OrdersProvider.GetByOrderDate(DateTime.Today);
3
DataRepository.OrdersProvider.Insert(transactionManager, order);
EditHow does the DataRepository know which provider to use?
When the DataRepository is first called, it will check the current configuration in your app/web.config in the netTiersService configSection to determine the current default provider and possibly any other provider configurations that have been configured. Configuration was covered in the Getting Started Portion of the documentation. During this process, the data repository will load the default NetTiersProvider, by default the .netTiers provider will be the SqlNetTiersProvider. This class lives in the DataAccessLayer.SqlClient layer of the default generation process.
EditWhat are access methods?
Access Methods are the methods that comprise the Data API. Depending on how .netTiers is configured it will identify and create a logical set of data access methods utilizing the data model's relationships and how their behavior affects your normalized relationships. That statement is a bit deep, so let's clarify. During the .netTiers generation process, the .netTiers templates will identify and create your initial API methods through based on indexes, keys, stored procedures, and a few other mechanisms all discussed below.
EditRead Methods:
- Get All
- Get By Primary Key Id
- Get By composite foreign key columns
- Get By Composite index column
- Get By Dynamic WhereClause (Paged Result)
- Get By Custom Stored Procedure
EditWrite Methods:
- Insert
- Update
- Delete
- Save
- Custom - using Custom Stored Procedure
EditCustom Enterprise Library Data Access:
For extended circumstances where you want to roll your own data access calls inside the API, you can simply use the same methods .netTiers autocreates for you, or you can even tap all of the DataAccess methods that the Enterprise LIbrary Data Application Block provides. It will automatically obtain the current connection information and you can pass it existing open transactions using the TransactionManager.
Generic Data Access:
- ExecuteReader
- ExecuteScalar
- ExecuteDataSet
- ExecuteNonQuery
Example:
1
string sqlCommand = "GetEmployeeName";
2
// Retrieve EmployeeName ExecuteScalar returns an object, so
3
// we cast to the correct type (string).
4
string employeeName = (string)DataRepository.Provider.ExecuteScalar(CommandType.StoredProcedure, sqlCommand);
How can I use Dynamic where clauses in my API?
There are several classes to assist in building dynamic search conditions in a parameterized and secure fashion.
- SqlExpressionParser - a search term parser
- SqlStringBuilder - a filter expression builder (uses SqlExpressionParser internally)
- SqlFilterBuilder - a generic filter expression builder (uses entity column enumerations)
- EntityFilterBuilder - a strongly typed filter expression builder
- ParameterizedSqlExpressionParser - parses search terms into parameterized expressions
- ParameterizedSqlFilterBuilder - a generic parameterized expression builder (uses entity column enumerations)
- EntityParameterBuilder - a strongly typed parameterized expression builder
- SqlFilterParameter - represents information needed for a database command parameter
- SqlFilterParameterCollection - a parameterized query string and a collection of SqlFilterParameter objects
- SqlParameter - a subclass of System.Web.UI.WebControls.Parameter that will allow an ASP.NET developer to use the ParameterizedSqlFilterBuilder (default) or SqlFilterBuilder along with any data source control.
- EntityFilter - used along with the SqlParameter to bind filter input controls to a data source control.
Examples:
1
CustomersParameterBuilder query1 = new CustomersParameterBuilder();
2
query1.Append(CustomersColumn.CustomerID, "A%");
3
query1.Append(CustomersColumn.City, "London, Berlin");
4
5
TList<Customers> list1 = DataRepository.CustomersProvider.Find(query1.GetParameters());
6
Console.WriteLine("Query1 = {0}", query1);
7
Console.WriteLine("Count1 = {0}", list1.Count);
Results
Query1
1
(CustomerID LIKE @Param0) AND (City = @Param1 OR City = @Param2)
2
-- Count1 = 2
Supposing you had more advanced requirements though. For instance, suppose you wanted to find all customers who's CustomerID began with "A" and lived in London OR who's CustomerID began with "B" and lived in Berlin. In this case you would need to apply a little more elbow grease:
More Advanced Example
1
CustomersParameterBuilder query1 = new CustomersParameterBuilder();
2
query1.Clear();
3
query1.Junction = string.Empty; // This prevents the ParameterBuilder from throwing an "AND" before next line's output
4
query1.BeginGroup();
5
query1.Append(string.Empty, CustomersColumn.CustomerID, "A%", true);
6
query1.Append("AND", CustomersColumn.City, "London", true);
7
query1.EndGroup();
8
query1.BeginGroup("OR");
9
query1.Append(string.Empty, CustomersColumn.CustomerID, "B%", true);
10
query1.Append("AND", CustomersColumn.City, "Berlin", true);
11
query1.EndGroup();
12
13
TList<Customers> list1 = DataRepository.CustomersProvider.Find(query1.GetParameters());
14
Console.WriteLine("Query1 = {0}", query1);
Results
Query1
1
(CustomerID LIKE @Param0 AND City = @Param1) OR (CustomerID LIKE @Param2 AND City = @Param3)
Along with the filter expression, query1 also has a collection of SqlFilterParameter objects that hold the name, type and value of each named parameter. This
resume help collection, which is returned by calling query1.GetParameters(), is passed into a new Find method overload that dynamically generates a paramaterized SQL statement, applies the necessary command parameters, then executes the query.
The SqlStringBuilder class, along with all of its sub-classes, contain several variations to the Append method to allow you to define simple or complex queries. Also, notice that the Append method used in this example accepts the use of wild card characters.
Create a non-parameterized query for those times when one is not necessary.
Example:
1
CustomersFilterBuilder query2 = new CustomersFilterBuilder();
2
query2.Append(CustomersColumn.CustomerID, "A*");
3
query2.Append(CustomersColumn.City, "London, Berlin");
4
int count = 0;
5
TList<Customers> list2 = DataRepository.CustomersProvider.GetPaged(
6
query2.ToString(), null, 0, 100, out count);
7
8
Console.WriteLine("Query2 = {0}", query2);
9
Console.WriteLine("Count2 = {0}", list2.Count);
Results:
1
Query2 = (CustomerID LIKE 'A%') AND (City = 'London' OR City = 'Berlin')
2
--Count2 = 2
EditCustom Stored Procedures:
EditDiscovery
By default netTiers pre-populates the CustomProcedureStartsWith property with a formattable string “_{0}_”, where {0}= Current Table Name, meaning it will look through all of the stored procedures in the database, and if your procedure starts with that string it will be eligible for inclusion as a custom stored procedure.
EditUser Defined Custom Procedure Naming Convention:
An example would be:
CustomProcedureStartsWith = '{1}cust_{0}_'
ProcedurePrefix = "usp_"
This would match any procedures that begin with usp_cust_TableName_GetByAnyMethod;
{1}cust_{0}_GetByAnyMethod
The appropriate methods will be generated for your stored procedure.
Example of a matching procedure:
1
create procedure _Employee_GetByBirthdate @birthDate dateTime As
2
Select * from Employee where birthDate = @birthdate GO
So when you start you generation you will get the appropriate methods created for you in your DAL’s EmployeeProvider.
1
DateTime today = DateTime.Today;
2
TList<Employee> todaysBirthdayList = DataRepository.EmployeeProvider.GetByBirthdate(today);
EditValidating Data Returned from Custom Stored Procedures.
When a custom stored procedure is found for inclusion, we will check the command and see what type of result sets you’re bringing back with you. You have the option of returning one of the following 3 types, a TList of the Entity, a DataSet, or an IDataReader.
Rules for checking Data Returned.
- If the custom procedure returns all the same columns that the table you are wanting to include it has, then a collection of entities is returned.
NOTE: Every column of the entity must be included in the returned result set.
-Should you return back only a few columns, or possibly join with a few other tables, then the configured CustomNonMatchingReturnType type will be returned. So this will either be a Dataset or IDataReader.
-When using an IDataReader, the consumer is responsible for closing the reader. The IDataReader is useful when returning multiple result sets in a custom stored procedure.
EditAdvanced Topics:
EditDynamic Connection String
I have a database for every client or user with the same table structure, etc. How can I change my connection string at runtime in the DataRepository?
Example:
1
DataRepository.AddConnection("Vendor1DynamicCS", "Data Source=(local);Initial Catalog=Vendor1Northwind;Integrated Security=true;");
2
3
TList<Info> list = DataRepository.Connections["Vendor1DynamicCS"].Provider.InfoProvider.GetAll()
EditMultiple NetTiers Service Sections
How can I configure multiple netTiersService configSections for multiple databases within the sameapp/web.config?
You must set the name of the configSections to the name of the assembly involved in section.
Example:
1
<configSections>
2
<section name="Orders.Data"
3
type="Orders.Data.Bases.NetTiersServiceSection, Orders.Data"
4
allowDefinition="MachineToApplication"
5
restartOnExternalChanges="true" />
6
7
<section name="Inventory.Data"
8
type="Inventory.Data.Bases.NetTiersServiceSection, Inventory.Data"
9
allowDefinition="MachineToApplication"
10
restartOnExternalChanges="true" />
11
12
</configSections>
13
14
<connectionStrings>
15
<add name="connectionStringOrders"
16
connectionString="database=Orders;Integrated Security=true;Connection Timeout=1;server=.;" />
17
18
<add name="connectionStringInventory"
19
connectionString="database=Inventory;Integrated Security=true;Connection Timeout=1;server=.;" />
20
21
</connectionStrings>
22
23
<Orders.Data defaultProvider="SqlNetTiersProvider">
24
<providers>
25
<add name="SqlNetTiersProvider"
26
type="Orders.Data.SqlClient.SqlNetTiersProvider, Orders.Data.SqlClient"
27
connectionStringName="connectionStringOrders"
28
useStoredProcedure="false"
29
providerInvariantName="System.Data.SqlClient" />
30
</providers>
31
</Orders.Data>
32
33
<Inventory.Data defaultProvider="SqlNetTiersProvider2">
34
<providers>
35
<add name="SqlNetTiersProvider2"
36
type="Inventory.Data.SqlClient.SqlNetTiersProvider, Inventory.Data.SqlClient"
37
connectionStringName="connectionStringInventory"
38
useStoredProcedure="false"
39
providerInvariantName="System.Data.SqlClient" />
40
</providers>
41
</Inventory.Data>
EditHow can I dynamically create a NetTiersProvider at runtime without having to use the configuration?
In cases where you can't access the configuration you can use the public LoadProvider method of the DataRepository. Here's an example of how you would load the provider at runtime.
Example:
1
SqlNetTiersProvider provider = new SqlNetTiersProvider();
2
NameValueCollection collection = new NameValueCollection();
3
collection.Add("UseStoredProcedure", "false");
4
collection.Add("EnableEntityTracking", "true");
5
collection.Add("EntityCreationalFactoryType", "Northwind.Entities.EntityFactory");
6
collection.Add("EnableMethodAuthorization", "false");
7
collection.Add("ConnectionString", "server=.\\Sql2000;database=Northwind;Integrated Security=true;");
8
collection.Add("ConnectionStringName", "MyDynamicConnectionString");
9
collection.Add("ProviderInvariantName", "System.Data.SqlClient");
10
11
provider.Initialize("DynamicSqlNetTiersProvider", collection);
12
13
DataRepository.LoadProvider(provider, true);
14
15
TList<Orders> list = DataRepository.OrdersProvider.GetAll();
16
Response.Write(list.Count.ToString());
EditHow can I set a another provider as the default provider at runtime?
1
NetTiersProvider provider = DataRepository.Providers["MyDynamicProvider"];
2
DataRepository.LoadProvider(provider, true);