Content
Signum Framework is a new open source framework for the development of entity-centric N-Layer applications. The core is Signum.Engine, an ORM with a full LINQ provider, which works nicely on both client-server (WPF & WCF) and web applications (ASP.Net MVC).
Signum Framework focus is to simplify the creation of composable vertical modules that can be used in many applications, and promotes clean and simple code via encouraging functional programming and the scope pattern.
If you want to know more about what makes Signum Framework different, take a look to the previous tutorial
We are pleased to announce that finally we have released Signum Framework 2.0.
The release took more than expected but, as a result, is much more ambitious. We have been using our framework internally in a daily basis and we think that this release is finished and will glad everyone brave enough to use it.
The new release is focused in different trends:
- Signum.Web: Based in ASP.Net MVC 3.0 and Razor, tries to keep the same feeling and productivity of Signum.Windows without constraining the possibilities of the web (jQuery, Ajax, control of the markup, friendly urls…)
- Keeping up to date with technology: The framework now runs only on .Net 4.0/ ASP.Net MVC 3.0 and the snippets and templates target Visual Studio 2010.
- Improving pretty much everything and fixing bugs: For a complete list look at the change log http://www.signumframework.com/ChangeLog2.0.ashx
In order to show the capabilities of the framework, and have a good understanding of the architecture, we’re preparing a series of tutorials in which we will work on a stable application: Southwind.
Southwind is the Signum version of Northwind, the well-known example database provided with Microsoft SQL Server.
In this series of tutorials we will create the whole application, including the entities, business logic, windows (WPF) and web (MVC) user interface, data loading and any other aspect worth to explain.
If you want to know more about the principles of Signum framework look at the previous tutorial
Time to get our hands dirty with the entities:
Signum framework is Open Source (LGPL) and can be downloaded for free in codeplex , the source code is also available in github.
The installer will copy the following:
- Assemblies: {Program files}\Signum Software\Signum Framework 2.0
- Code Snippets: {Documents}\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets
- Project template: {Documents}\Visual Studio 2010\Templates\ProjectTemplates\Visual C#
- WPF Control Template: {Documents}\Visual Studio 2010\Templates\ItemTemplates\Visual C#
- ASP.Net MVC 3 Razor Control Template: {Program Files}\Microsoft Visual Studio 10.0\Common7\IDE\ ItemTemplates\CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML
Once installed, open Visual Studio 2010, and create a new project. Under Visual C#/.Net Framework 4 there’s a new Signum Framework 2.0 Client – Server template. Create a new project called Southwind and a solution with 5 projects should be created:
- Southwind.Entities
- Southwind.Load
- Southwind.Logic
- Southwind.Web
- Southwind.Windows
Unload everything but Southwind.Entities for now, and open MyEntity.cs
Signum Framework promotes a code-first approach and the entities you write have a straightforward mapping to database tables. Also, since 100% of the SQL queries are produced by the framework, including schema modification, you almost forget about SQL Management Studio.
For teaching, however, will be easier to start by showing what we are trying to accomplish in a familiar diagram. Here is Northwind database:
Let’s start simple. In order to create the entity for Region we need to inherit from <a href="http://www.signumframework.com/BaseEntities.ashx">IdentifiableEntity </a>
class and create the description field.
We will call it Description
, not RegionDescription
, since this redundancy makes sense only to simplify writing SQL manually (not the case).
Also, we don’t have to worry about RegionID, since every IdentifiableEntity already has Id and ToStr field/property.
We already have the snippets installed so just remove MyEntityDN class and press:
entityWithName [Tab] [Tab] Region [Tab] description [Tab] Description [Enter]
After this, we should have something like this:
[Serializable]
public class RegionDN : Entity
{
[NotNullable, SqlDbType(Size = 100), UniqueIndex]
string description;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 100)]
public string Description
{
get { return description; }
set { SetToStr(ref description, value, () => Description); }
}
public override string ToString()
{
return description;
}
}
This is our entity class, let's analyze:
The class
The class is Serializable
, so you can send it though a web service or save it in a file.
Also, inherits from Entity
to enables concurrency support. Since this entity will be changed once in a while by admins, we can change to IdentifiableEntity and save the inherited Tick
column.
Finally, notice that we write the name of our entities in singular, as well as the name of the table.
The field
There’s a description
field. In Signum Framework entities, every field will generate a database column.
The column will try to match the CLR type whenever possible so in the case of strings it will be nullable and, by default, a length of 200 characters.
The attributes over the field override some of this defaults, in this case makes the column not nullable and with a length of 100.
Just by placing UniqueIndex
attribute, an index will be created over the column.
The property
Every property gives access to the underlying field for the user interface and business logic.
In order to user the property in queries, there have to be a field with the same name but lowercase. Unfortunately VS snippets are not smart enough so you have to repeat the name twice.
By decorating the properties with some ValidationAttributes
we enforce simple validation rules over the entities. More flexible validation options are available and the attributes can be overridden.
Also we can decorate our properties with other annotations to change the display name (DescriptionAttribute
), the format of numbers or dates (FormatAttribute
), the unit of the value (UnitAttribute
), etc…
Finally, we can see that the getter of the property is trivial, but the setter calls a protected <code>Set
method. This method enables change tracking does the following:
- Set the value of the field to the new value
- Marks the entity as modified (dirty) so is not skipped when saving
- Fires PropertyChanged event so the user interface (if any) gets updated
- Returns true if there was a change, false if the value was the same
Let’s continue now with Territory:
entityWithName [Tab] [Tab] Territory [Tab] description [Tab] Description [Enter]
We also change the base class to IdentifiableEntity
, so TerritoryID
comes for free but we have to create the RegionID column and the foreign key. Quite simple, just create a property of type Region:
field [Tab] [Tab] RegionDN [Tab] region [Tab] Region [Enter]
Territory property is mandatory, so let's add a NotNullValidator over the property. The result should be something like this:
[Serializable]
public class TerritoryDN : Entity
{
RegionDN region;
[NotNullValidator]
public RegionDN Region
{
get { return region; }
set { Set(ref region, value, () => Region); }
}
[NotNullable, SqlDbType(Size = 100), UniqueIndex]
string description;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 100)]
public string Description
{
get { return description; }
set { SetToStr(ref description, value, () => Description); }
}
public override string ToString()
{
return description;
}
}
Since RegionDN
is an IdentifiableEntity
, the framework already knows that has to create an idRegion
column and a foreign key for you. There's no easy way to access this underlying column (apart from doing territory.Region.Id
) but dealing with id's is unnecessary and error prone. For Signum Framework, foreign keys are an implementation detail.
The next step should be the EmployeeTerritories
relational table, but this table is not an ‘Entity
’ but a Many-to-Many relationship between Employees
and Territories
. In Signum Framework this is represented by a MList<TerritoryDN>
field on Employee
entity.
Before getting into Employee
table, we can see that some fields (Address
, City
, Region
, PostalCode
and Country
) are also repeated in Customers
, Orders
and Supplier
tables.
In order to create an address entity that ‘belongs’ to the parent table, instead of having their own, we just have to make it inherit from <a href="http://www.signumframework.com/BaseEntities.ashx#EmbeddedEntity">EmbeddedEntity</a>
:
entity [Tab] [Tab] Address [Enter]
fieldString [Tab] [Tab] address [Tab] Address [Enter]
fieldString [Tab] [Tab] city [Tab] City [Enter]
fieldString [Tab] [Tab] region [Tab] Region [Enter]
fieldString [Tab] [Tab] postalCode [Tab] PostalCode [Enter]
fieldString [Tab] [Tab] county [Tab] Country [Enter]
Now let’s change the base class to EmbeddedEntity
and make some changes to the size of the fields (and the corresponding validators) to mimic the Northwind database.
[Serializable]
public class AddressDN : EmbeddedEntity
{
[NotNullable, SqlDbType(Size = 60)]
string address;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 60)]
public string Address
{
get { return address; }
set { Set(ref address, value, () => Address); }
}
[NotNullable, SqlDbType(Size = 15)]
string city;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 15)]
public string City
{
get { return city; }
set { Set(ref city, value, () => City); }
}
[NotNullable, SqlDbType(Size = 15)]
string region;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 15)]
public string Region
{
get { return region; }
set { Set(ref region, value, () => Region); }
}
[NotNullable, SqlDbType(Size = 10)]
string postalCode;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 10)]
public string PostalCode
{
get { return postalCode; }
set { Set(ref postalCode, value, () => PostalCode); }
}
[NotNullable, SqlDbType(Size = 15)]
string country;
[StringLengthValidator(AllowNulls = false, Min = 3, Max = 15)]
public string Country
{
get { return country; }
set { Set(ref country, value, () => Country); }
}
}
Now things get interesting, Employee it’s one of the biggest tables on Northwind and has some new aspects:
- An
Address
field, of type AddressDN
, that contains the fields of our new EmbeddedEntity
. - An
MList<territory> </territory>
<territory>that will create the relational table. - A relationship to itself to represent the
Employee
hierarchy. - A bunch of new value types like
DateTime
, DateTime?
(nullable), byte[]
for the photo, and a infinite length string
(notes).
We should have the basis of the entity written in a few seconds with the following key strokes:
entity [Tab] [Tab] Employee [Enter]
fieldString [Tab] [Tab] lastName [Tab] LastName[Enter]
fieldString [Tab] [Tab] firstName [Tab] FirstNaame [Enter]
fieldString [Tab] [Tab] title [Tab] Title[Enter]
fieldString [Tab] [Tab] titleOfCourtesy [Tab] TitleOfCourtesy [Enter]
field [Tab] [Tab] DateTime? [Tab] birthDate [Tab] BirthDate [Enter]
field [Tab] [Tab] DateTime? [Tab] hireDate [Tab] HireDate [Enter]
field [Tab] [Tab] AddressDN [Tab] address [Tab] Address [Enter]
fieldString [Tab] [Tab] homePhone [Tab] HomePhone [Enter]
fieldString [Tab] [Tab] extension [Tab] Extension [Enter]
field [Tab] [Tab] byte[] [Tab] photo [Tab] Photo [Enter]
fieldString [Tab] [Tab] notes [Tab] Notes [Enter]
field [Tab] [Tab] Lite<EmployeeDN> [Tab] reportsTo [Tab] ReportsTo [Enter]
fieldString [Tab] [Tab] photoPath [Tab] PhotoPath [Enter]
field [Tab] [Tab] MList<TerritoryDN> [Tab] territories [Tab] Territories [Enter]
Now, this time inheriting from Entity is all right.
String fields
Let’s manually set the size of all the string fields, and relax nullability when necessary by removing NotNullable
over the field, and set AllowNulls = true
on the StringLengthValidator
.
NVarChar(MAX) fields
For long string fields, like ‘note’, we could override the type to NText type with the attribute
SqlDbType(SqlDbType=SqlDbType.NText)
But since NText is already deprecated in favor of NVarChar(MAX)
, we will place this attribute instead:
SqlDbType(Size = int.MaxValue)
The framework understand int.MaxValue
and replaces it for NVarChar(MAX)
.
Other string fields, like homePhone and extension, give us the opportunity to use a TelephoneValidator
DateTime fields
Notice that for DateTime
fields we just make the field type nullable when is not mandatory.
Also, we could place a convenient validator to constraining our dates:
[DateTimePrecissionValidator(DateTimePrecision.Minutes)]
This way we avoiding rounding errors due to seconds, milliseconds, etc..
Notice that all the ValidatorAttributes
, by convention, accept null values so in order to prevent null values you will need a NotNullValidator
as well.
VarBinary(MAX) fields
Just as we did with notes
field. The same is applicable to photo
field. By default byte[]
fields are translated to VarBinary
. Instead of using SqlDbType.Image
, (also deprecated) we will use Size = int.MaxValue
to make it VarBinary(MAX)
.
EmbeddedEntity fields
The address
field, of type AddressDN
, will include all the corresponding columns in the form Address_Address
, Address_City
, Address_Region
,....
Also, in order to express that the address entity itself is null (not some of the internal fields), a new Address_HasValue
field will be created, and the types of the internal fields will be overridden to support null values.
We can disable this feature just by placing [NotNullable]
over the address
field, but in this case this is all right.
Lite fields
Lite<T>
is a generic class that creates a lightweight reference to an entity. At runtime, it only contains Type
, Id
and ToStr
fields, and can be used in your entities to control lazy retrieving.
On the schema, however, a Lite<T>
field is exactly the same than a field of type T
: An id with a foreign key to the other entity.
Also Lite<T>
can be used in your business logic to pass it as a parameter, effectively making an strongly typed Id, and is much easier to debug since it contains the ToStr of the original entity.
Its also completely integrated with out LINQ provider so you can retrieve Lite<T>
, to populate a combo box for example. We will see Lite<T>
many times in the course of this tutorial.
In this case, by making ‘reportsTo’ a field of type Lite<EmployeeDN>
we stop the engine from retrieving every employee all the way up in the chain of command.
MList<TerritoryDN> fields
An <a href="http://www.signumframework.com/MList.ashx">MList</a>
is an self-change-tracking version of a List. It contains all the convenient methods of a List (RemoveAll
, Invert
, and Indexer...) and can be easily data-bound since it implements INotifyCollectionChanged
.
On the database, an MList field won’t create any column, but a table with all the items that belong to an entity associated to the entity using an idParent
foreign key.
In our case, the field territories
of type MList<TerritoryDN>
will create a Table with name EmployeeDNTerritories
that will look quite similar to the original one.
However, MList<T> is not limited to relational tables (collection of other entities) but can also be used to create collection of values (MList<int>
) or collections of embedded entities (MList<AddressDN>
).
After writing Employee
entity, writing the rest of the entities should be straightforward.
Maybe it gets a little boring, but this is only for teaching purposes. In a real application you will create the entities instead of the tables, not after.
We resist making a tool that generates the entities from a legacy database automatically. Signum Framework is quite strict with some conventions and would be hard to consider on any random legacy database.
But more important, the entities will be the core of your application, writing them by hand is a good opportunity to reconsider de design and fix legacy mistakes.
Some small notes:
- Shipper (straightforward)
- Customer
- Use our
AddressDN
EmbeddedEntity
. - Skip Customers demographics (the table is empty and adds no value)
- Supplier
- Use our
AddressDN
EmbeddedEntity
. - Use
URLValidators
on HomePage
. - Use
TelephoneValidator
on Phone
and Fax
.
- Product
- o Make the relationship to
CategoryDN
and SupplierDN
, both a Lite<T>
relationship.
- Category
- User a
byte[]
field (with SqlDbType(Size=int.MaxValue)
) for picture.
- Order
- Use our
AddressDN
EmbeddedEntity
- Make the relationship to Shipper and Customer a Lite<t> relationship
Finally, the only tricky point in Order entity is how to implement OrderDetails
. It’s a relational table but has some information attached to the relationship (UnitPrice
, Quantity
, Discount
).
As we have seen, we can implement it using an MList<T>
being T and EmbeddedEntity
, we will have to create an OrderDetailDN EmbeddedEntity
first:
entity [Tab] [Tab] OrderDetail [Enter]
field [Tab] [Tab] Lite<ProductDN> [Tab] product [Tab] Product [Enter]
field [Tab] [Tab] decimal [Tab] unitPrice [Tab] UnitPrice [Enter]
field [Tab] [Tab] int [Tab] quantity [Tab] Quantity [Enter]
field [Tab] [Tab] float [Tab] discount [Tab] Discount[Enter]
The result should be like this after adding a ValidationAttribute and changing the base type.
[Serializable]
public class OrderDetailsDN : EmbeddedEntity
{
Lite<ProductDN><productdn> product;
[NotNullValidator]
public Lite<ProductDN> Product
{
get { return product; }
set { Set(ref product, value, () => Product); }
}
decimal unitPrice;
public decimal UnitPrice
{
get { return unitPrice; }
set { Set(ref unitPrice, value, () => UnitPrice); }
}
int quantity;
public int Quantity
{
get { return quantity; }
set { Set(ref quantity, value, () => Quantity); }
}
float discount;
public float Discount
{
get { return discount; }
set { Set(ref discount, value, () => Discount); }
}
}
</productdn>
So far we have used ValidationAttributes
for all our validation. Simple but not that flexible.
Let’s push the validation system a little bit. Suppose that we want to be sure that discount is something like 5%, 10%... 25%, always a multiple of 5%.
We don’t have a Validator attribute that fits these requirements, but we could create one just by creating a class that inherits from ValidationAttribute
.
In this case, however, we will just override PropertyValidation
method in the entity itself:
protected override string PropertyValidation(PropertyInfo pi)
{
if (pi.Is(() => Discount))
{
if ((discount * 100) % 5 != 0)
return "Discount should be multiple of 5%";
}
return base.PropertyValidation(pi);
}
This method will be called for every property of the entity, and if it returns and string, the property value will be considered wrong. If everything is ok it should return null.
This technique has the advantage that we can take into account more than one property value to make our validation logic. In this case we will need to use Notify(()=>OtherProperty)
to force the re-evaluation of the validation logic for the affected properties after changing the value.
Finally, just by creating a field MList<orderdetailsdn> </orderdetailsdn>
<orderdetailsdn>in OrderDN
entity we will have the expected result.
MList<OrderDetailsDN> details;
public MList<OrderDetailsDN> Details
{
get { return details; }
set { Set(ref details, value, () => Details); }
}
At this moment, Southwind entities should be able to produce a database quite similar to Northwind, let’s try:
Reload the project Southwind.Logic
. This project contains the business logic that will run on the server (so we have database access) in different scenarios:
- The Web interface.
- The Windows interface through a WCF service.
- The Load application.
- The Unit Tests.
We will get into this topic more deep in the next tutorial, for now let’s start simple.
In order to create the database we first have to tell the engine witch entities will get into the schema.
Let’s just rename the example class MyEntityLogic
to OrdersLogic
, and change the code that includes MyEntityDN
to include OrderDN
instead.
The rest of the entities get automatically included by walking the dependencies of OrderDN
.
Next step is reloading Southwind.Load
. In this project we have already created a simple Console Application that we can use for manipulating the schema, loading data, or any other administration tasks.
The template already has a menu that allows us to create and synchronize the database. Let’s just go to SQL Management Studio, connect to localhost, and create a new database.
By convention the name of the database should be the same than the project, in this case ‘Southwind
’, but you can change it in the connection string.
Once created, just mark Southwind.Load
as startup project and run. Choose the first option, New Database
by pressing “N
” and… voilà! The schema get’s created according to the entities and should look like this:
In this tutorial we have seen how to create entities just by inheriting from some base classes
IdentifiableEntity
: for entities with an Id and a Table EmbeddedEntity:
for entities that live inside of some parent entity Entity:
for entities with an Id and a Table, and also control of concurrent modification.
In our entities we can create different field and properties:
- Values: Any standard value type (
string
, DateTime
, int
, float
, Guid, byte[]
..) will create the corresponding column. - References: We can create references to other entity just by creating a field of this entity type, or we can use
Lite<T>
to enable Lazy loading. We didn't explain it, but this references could be polymorphic using the inheritance support. - Collections: We can also use
MList<T>
to make collection tables that could be used to represent collections of entities, values or embedded entities. - Enums: We didn't explain it here, but you can also use enums in your entities.
We can validate our entities just by placing some ValidationAttribute
over the properties, or we can override PropertyValidation
method to have full control. In other tutorials we will see more convoluted validation strategies.
As you see Signum Framework is designed top-down to promote a code-first approach, and trying to make it work on a legacy database is a pain due to some strict conventions (like having Id
and ToStr
in every entity).
Nevertheless, we have seen how the generated schema is simple and predictable and can be exploited by third-party tools easily.
In the next tutorial we will get deep into how to write the business logic, load legacy data and create the user interfaces, and how these conventions will simplify our code on the long run.