EditDatabase Model and Sql:
How does .netTiers figure out what to generate?
.netTiers uses the rich meta data CodeSmith provides that is found from database in order to create a complete object domain based on your data relationships. The SchemaExplorer provides information on Database, Table, View, Indexes, StoredProcedures, and more. There are several important aspects to keep in mind when creating or using an existing database. In a perfect world, you would try and utilize these best practices.
EditBest Practices:
- Table names are singular and Pascal Case. ex. Order, Product, File
- Fields are Pascal Case, ex. FirstName, LastName, MiddleInitial
- Description is provided for Tables, Columns, and Keys as an Extended Attribute.
- For MSSQL, it is "MS_Description" key for the extended attribute.
- Using the ParseDbColDefaultVal option it is possible to get .netTiers to default your entity properties using your database defaults. Bear in mind that only simple constants and a few functions are supported (getdate(), getutcdate()). User defined schema defaults are not supported
- .netTiers can not assume relationships, it's imperative that you actually create Foreign keys associated to the parent table's primary key.
EditRelationships
Using Composition in your Model.
Given the following Tables, we will create a One to One relationship, meaning for every person there can be a single unique Contact record for that Person.
1
-- Create the Base Relationship, has a unique primary key amongst both tables.
2
CREATE TABLE [dbo].[Person](
3
[PersonId] [int] NOT NULL,
4
[Name] [varchar](50) NOT NULL,
5
CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED
6
(
7
[PersonId] ASC
8
) WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
9
) ON [PRIMARY]
10
11
12
--Create the other side of the 1:1 relationship
13
CREATE TABLE [dbo].[Contact](
14
[ContactPersonId] [int] NOT NULL,
15
[ContactNumber] [varchar](30) ,
16
CONSTRAINT [PK_Contact] PRIMARY KEY CLUSTERED
17
(
18
[ContactPersonId] ASC
19
) WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
20
) ON [PRIMARY]
21
22
23
-- Example of 1:1 relationship
24
-- The contact primary key is the is also a foreign key relationship to the PersonId.
25
-- Therefore creating a 1:1 relationship.
26
27
ALTER TABLE [dbo].[Contact] WITH CHECK ADD CONSTRAINT [FK_Contact_Person]
28
FOREIGN KEY([ContactPersonId])
29
REFERENCES [dbo].[Person] ([PersonId])
30
GO
Your generated entities would look like this.
For every Person entity, you would have a composite property to the Contact entity.
1
/**//// <summary>
2
/// Holds a Contact object
3
/// which is related to this object through the relation Contact
4
/// </summary>
5
[BindableAttribute()]
6
public Contact Contact
7
...{
8
get ...{ return entityData.Contact; }
9
set ...{ entityData.Contact = value; }
10
}
For every Contact entity, since you are working with the actual foreign key, that is your primary key, you would get a PersonId and a PersonIdSource of type Parent.
1
private Person _contactPersonIdSource = null;
2
3
/**//// <summary>
4
/// Gets or sets the source <see cref="Person"/>.
5
/// </summary>
6
/// <value>The source Person for ContactPersonId.</value>
7
[Browsable(false), BindableAttribute()]
8
public virtual Person ContactPersonIdSource
9
...{
10
get ...{ return this._contactPersonIdSource; }
11
set ...{ this._contactPersonIdSource = value; }
12
}
EditUsing Collection Relationships.
Given a modified for brevity version of some of the familiar Northwind Database.
1
-- ORDER
2
-- A familiar pattern in database design, which is the Header/Details pattern in which
3
-- one table contains the the overall summary of all the
4
-- detail items contained within a particular detail Table.
5
-- Think of a single order always *could* potentially have many order items.
6
-- Just like on your grocery receipt.
7
8
-- the Header Table
9
CREATE TABLE [dbo].[Orders](
10
[OrderID] [int] IDENTITY(1,1) NOT NULL,
11
[CustomerID] int NOT NULL,
12
[EmployeeID] [int] NULL,
13
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
14
(
15
[OrderID] ASC
16
) ON [PRIMARY]
17
) ON [PRIMARY]
18
19
20
-- the Details Table
21
CREATE TABLE [dbo].[Order Details](
22
[OrderID] [int] NOT NULL,
23
[ProductID] [int] NOT NULL,
24
CONSTRAINT [PK_Order_Details] PRIMARY KEY CLUSTERED
25
(
26
[OrderID] ASC,
27
[ProductID] ASC
28
) ON [PRIMARY]
29
) ON [PRIMARY]
30
31
-- the Customer Table
32
CREATE TABLE [dbo].[Customer](
33
[CustomerID] [int] NOT NULL,
34
[ContactName] [nvarchar](30) NULL,
35
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
36
(
37
[CustomerID] ASC
38
) ON [PRIMARY]
39
) ON [PRIMARY]
40
41
42
Example of Many To One relationship:
43
-- This creates a simple relationship between the Orders Table and the Customer, so that in your
44
-- for every OrderEntity entity, you will have a CustomerId foreign key integer, and a
45
-- CustomerIdSource entity of type Customer nested composite entity in your Order class.
46
47
ALTER TABLE [dbo].[Order] WITH NOCHECK ADD CONSTRAINT [FK_Order_Customer]
48
FOREIGN KEY([CustomerID])
49
REFERENCES [dbo].[Customer] ([CustomerID])
50
GO
51
52
Example of One to Many Relationship:
53
-- This creates a one to many relationship between the Order and the Order Detail tables.
54
-- Meaning, in your generated code,
55
-- for every Order entity, you will have an OrderDetailCollection of type TList<OrderDetail>.
56
-- This means you can have an entire
57
-- list of OrderDetail entities within your Order entity.
58
59
ALTER TABLE [dbo].[Order Details] WITH NOCHECK ADD CONSTRAINT [FK_Order_Details_Orders] FOREIGN KEY([OrderID])
60
REFERENCES [dbo].[Orders] ([OrderID])
61
GO
EditMany To Many Relationship
The last of the intrinsic relationships are the Many to Many relationships. The m:m relationship consists of 3 tables, the left table, the junction table and the right table and is essentially a flexible 1:m relationship but with two tables using a junction to facilitate the relationship. An example of this, is the Orders, Order Details, and the Products table. Where the order can have many Order Details that all have Products, while at the same time any one Product can be in any Order Detail which is associated with many orders. This junction table holds both references to the left and right tables. The nice thing about this relationship is that we can infer from the Order entity that the Order Entity will have a collection of type TList
and similarly will see an Orders collection for every Product because of the junction table of Order Datails.
1
ALTER TABLE [dbo].[Order Details] WITH NOCHECK ADD CONSTRAINT [FK_Order_Details_Orders] FOREIGN KEY([OrderID])
2
REFERENCES [dbo].[Orders] ([OrderID])
3
GO
4
5
Alter TABLE [dbo].[Order Details] WITH NOCHECK ADD CONSTRAINT [FK_Order_Details_Products] FOREIGN KEY([ProductID])
6
REFERENCES [dbo].[Products] ([ProductID])
7
GO
1
// Order Entity:
2
3
/**//// <summary>
4
/// Holds a collection of ProductsFromOrderDetails objects
5
/// which are related to this object with junction table OrderDetails
6
/// </summary>
7
[BindableAttribute()]
8
public TList<Products> ProductsCollection_From_OrderDetails
9
...{
10
get ...{ return entityData.ProductsCollection_From_OrderDetails; }
11
set ...{ entityData.ProductsCollection_From_OrderDetails = value; }
12
}
13
14
15
//Product Entity:
16
17
/**//// <summary>
18
/// Holds a collection of OrdersFromOrderDetails objects
19
/// which are related to this object through junction table OrderDetails
20
/// </summary>
21
[BindableAttribute()]
22
public TList<Orders> OrdersCollection_From_OrderDetails
23
...{
24
get ...{ return entityData.OrdersCollection_From_OrderDetails; }
25
set ...{ entityData.OrdersCollection_From_OrderDetails = value; }
26
}
EditIndexes
Indexes are not only a powerful feature to help improve your query execution become more performant, it's also used to create handy data access API methods based on those indexes. The important features of an index is that it can contain 1-n columns and has has the ability to be set as a UNIQUE index.
For example, in the Person Table we created above, if we added an index to the Name column, and said that column was Unique. Then in my data access API I would get a method called Person GetByName(string name). If i had not added that the index was unique, then the method would return a collection of type TList.
SQL:
1
CREATE UNIQUE NONCLUSTERED INDEX [IX_Person] ON [dbo].[Person]
2
(
3
[Name] ASC
4
)WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
OUTPUT:
1
/**//// <summary>
2
/// Gets rows from the datasource based on IX_Person index.
3
/// </summary>
4
/// <param name="name"></param>
5
/// <returns>Returns an instance of the <see cref="Person"/></returns>
6
public Person GetByName(System.String name)
7
...{
8
int count = -1;
9
//Calls Provider specific Implementation
10
11
return GetByName(null,name, 0, int.MaxValue, out count);
12
}
EditCustom Stored Procedures
There are several times when you want to extend the data access API, but you still want to leverage much of the generated approach to the data layer. .netTiers offeres the ability to write your own procedures and be able to do things that .netTiers can not do out of the box or are specialized to your application. An easy example would be if you wanted to create a custom stored procedure to handle getting all products below a certain inventory. When you begin the generation process, the CodeSmith SchemaExplorer will attempt to discover all of the rich meta data provided to determine of this procedure returns a resultset, which parameters it takes (input & output).
1
-- Get the products that have less units in stock than the @UnitsInStock parameter.
2
CREATE PROCEDURE dbo._Products_GetWithStockBelow
3
@UnitsInStock smallint
4
AS
5
6
SELECT
7
[ProductID],
8
[ProductName],
9
[SupplierID],
10
[CategoryID],
11
[QuantityPerUnit],
12
[UnitPrice],
13
[UnitsInStock],
14
[UnitsOnOrder],
15
[ReorderLevel],
16
[Discontinued]
17
FROM
18
[dbo].[Products]
19
WHERE
20
[UnitsInStock] < @UnitsInStock
21
GO
Output:
1
public TList<Product> GetWithStockBelow(System.Int16 unitsInStock)
2
...{
3
4
int count = -1;
5
//Calls Provider specific Implementation
6
7
return GetWithStockBelow(
8
null, unitsInStock, 0, int.MaxValue, out count);
9
10
}
NOTE:
There are some situations where the procedure will not return results as expected. The Custom Stored procedures don't work when using temp tables within the custom stored procedure. This is because when CodeSmith's SchemaExplorer is discovering this information, it doesn't have necessary priveldges to create a temp table. One workaround is to use a table variable in the stored procedure instead of a temp table. Warning: Table variables are held in memory on the server so do not load too many rows in the table variable or the servers performance will be impacted.
Important
In order for .netTiers to return an entity that maps to the table you've created the Custom Stored Procedure for, all of the columns being returned must match type and be in the correct order.
If you notice that the generated code for your csp starts returning "void" instead of what you expect check that you csp is still valid
EditEnum Tables
.netTiers will create enums based on table data for the tables that you designate. This is useful for fairly static data or type tables.
Rules:
The tables you would like to generate as enums must meet the following rules.
1. The first column must be a primary key (typically this would be an int Identity column),
2. The second column must have a unique column constraint index, the optional third column will be the description of the generated enum.
3. You must also select this table in the SourceTable as well in order to generate the enum.
Example:
1
CREATE TABLE [dbo].[BankAccountType](
2
[BankAccountTypeId] [int] IDENTITY(1,1) NOT NULL,
3
[BankAccountTypeName] [varchar](50) NOT NULL,
4
[BankAccountTypeDescription] [varchar](250)NULL,
5
CONSTRAINT [BankAccountType_PK] PRIMARY KEY CLUSTERED
6
(
7
[BankAccountTypeId] ASC
8
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY],
9
CONSTRAINT [BankAccountType_UC1] UNIQUE NONCLUSTERED
10
(
11
[BankAccountTypeName] ASC
12
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
13
) ON [PRIMARY]
14
15
-- Add Table Description
16
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'All allowable Checking Account types for my ABC System' ,@level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'BankAccountType'
17
18
19
Some Enum Items:
20
INSERT INTO [dbo].[BankAccountType] VALUES ('Checking', 'A Valid Checking Account')
21
INSERT INTO [dbo].[BankAccountType] VALUES ('Savings', 'A Valid Savings Account')
Generated Enumeration:
1
/**//// <summary>
2
/// All allowable Checking Account types for my ABC System
3
/// </summary>
4
/// <remark>Enum that contains the items in BankAccountType</remark>
5
[Serializable]
6
public enum BankAccountTypeList
7
...{
8
9
/**//// <summary>
10
/// A Valid Checking Account
11
/// </summary>
12
[EnumTextValue("A Valid Checking Account")]
13
Checking = 1,
14
15
16
17
/**//// <summary>
18
/// A Valid Savings Account
19
/// </summary>
20
[EnumTextValue("A Valid Savings Account")]
21
Savings = 2
22
}