Click here to Skip to main content
15,861,168 members
Articles / Web Development / ASP.NET

Signum Framework Principles

Rate me:
Please Sign up or sign in to vote.
4.74/5 (27 votes)
25 Jul 2011CPOL18 min read 98.4K   1.1K   86   35
Explains the philosophy behind Signum Framework, an ORM with a full LINQ Provider that encourages an entities-first approach.

Signum Framework Tutorials 

Introduction

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, still in progress).

I’ve developed this Framework and it’s hard for me to be objective. However, in this first tutorial, I’ll try to explain the principles that guide us to design Signum Framework, the pros and cons of the solution and, in concrete terms, how your development experience will get improved if you start using it.

Excuse me for the lack of a working source code in this article. I promise the next ones will be more practical, but in the first one I wanted to focus on the philosophy and the design principles.

DB-First vs Entities-First

Why learn a new ORM now, in 2009, with so many ORMs for .NET on the market? Isn’t everything already invented? Mmm…. Not really. We could categorize ORMs in three different groups:

DB-First:
Using store procedures (LLBLGen), drag-and-drop proxy classes (Linq to Sql) or by generating code (Entity Spaces, SubSonic, LLBLGen Pro), the bottom line is that your database defines the shape of your data, and the entities just follow the rules.

Flexible:
Technologies like NHibernate allow you to have a flexible mapping between your entities and your database, but then you have three things to maintain: the database schema, the entities, and the mapping in between. Castle Project ActiveRecord simplifies this mapping by using convention over configuration principle, but still flexible to map different relational models.

Entities-First:
There are very few ORMs that encourage a pure entity-first approach. Django is an example but it works in Python, not in the .NET.

Signum Framework fits into this category.

The main idea behind Signum Framework is that you write the entities first (in C#) and then the database is automatically generated. This radical change means that you won’t be able to use your legacy database. But if you have the opportunity to work on a greenfield project, or you are brave enough to change your database, Signum Framework will make your life easier, let’s see why.

Signum Framework Principles

The most important principle that has influenced every single part of the design of Signum Framework is simplifying composability. There’s no reason why applications have to be so different and so hard to integrate, and this difference comes fundamentally from the database.

That’s why you can easily buy powerful Grids and Chart controls for any UI technology (WinForms, WPF, ASP.NET…) but you won’t find the tables and entities for your authorization, stock or payments modules, because nobody knows how to integrate them with your database.

What’s so fundamentally wrong about this idea of integrating vertical modules that deal with the UI, and the Database in your application? The answer is the lack of solid foundations.

Signum.Entities provides a small set of base classes (IdentifiablelEntity, EmbeddedEntity…) and primitives (MList<T>, Lazy<T>, Custom attributes…) to model your business entities in such a way that can be easily integrated with other modules. Just like Lego bricks: Small, reusable but not flexible at all.

Since these entities are just C# classes, and we follow an entities-first approach, Signum Framework is able to follow other nice principles:

Signum Framework encourages

Signum Framework tries to avoid

Clean and Simple code, by exposing static classes, and using the ScopePattern, Attributes… we try to create self explanatory code that does what is expected, removing the clutter.

Complicated architectures that make your code base 300% larger and harder to understand in order to protect you from changes that will occur 10% of the time.

Remove redundancy at any cost, because saying something once, and only once, is the best way of be prepared for changes.

Code generation of your entities, making it hard to add behavior to your entities (like validation), keeping you without control of the most important part of the application.

Compile-time checked C#, so common operations (adding, removing or renaming fields or classes) are less painful thanks to refactoring. That’s why we use LINQ queries instead of SQL, and we use strong typed reflection to configure the application.

XML configurations, that are pure bloat, error-prone, not very expressive and have to be kept in sync (in case of the ORM mapping). You are building a solution over a framework, not configuring a framework to be your solution.

Functional Programming, because lambdas, object initializers and LINQ queries, once understood, make your code shorter, easier to read and more homogeneous.

Imperative programming, often you have to fallback to imperative programming, but if you have any chance to use declarative/functional programming, your code will be cleaner and with fewer bugs.

POCO Support

POCO stands for Plain Old C# Object. An ORM with POCO support is one that can deal with classes that have no previous knowledge of the ORM. This is called Persistence Ignorance. It's explained here.

We know that POCO is a feature required by a lot of people 'disappointed' with the current LINQ Providers (no with LINQ per se), like DDD guys. Unfortunately POCO works badly with any other aspect of an ORM. For example, in order to avoid DataContext and make SOAP Serialization possible in a simple way, we embed change tracking inside of the entities. Our entities also have integrated support for validation by implementing IDataErrorInfo and implement INotifyPropertyChanged in order to simplify data binding (in WPF scenarios for example).

So, if the reason you like POCO is because it enables a clean separation of entities and logic, our architecture clearly promotes this: The typical architecture usually has two different assemblies and the entities only have to reference Signum.Entities, not Signum.Engine, where the DB stuff is placed.

Also, if you like POCO because your entities are clean and simple, sooner or later you will need to implement IDataErrorInfo or INotifyPropertyChanged and the clutter will appear. In Signum Framework, we remove all this clutter by moving it to the base class, keeping your entities simple even with validation.

However, if you like POCO because this way you can write an ORM-independent model, then you will be disappointed by Signum Framework on this point. Since changing the ORM is as common as having a heart transplant, and enabling pure POCO means that a lot of magic has to be added at runtime (i.e., lazy loading, that plays badly with Serialization), and a lot of external mapping has to added, increasing duplication, we choose not to support POCO.

But what's more important, Signum Framework imposes every single IdentifiableEntity (and Entity) to have an Id and a ToStr field. These fields also create columns in the database. So it's not only that Signum Framework imposes your classes to inherit from some certain base classes, it also imposes your tables to do it. That's why you need a new Database. In that sense, Signum Framework has a lesser support to POCO than most of the frameworks.

These requirements, however, pay for themselves because they enable huge levels of code re-utilization:

Think of System.Object: Thanks to having ToString, GetHashCode and GetType on every single object then you can add them to a List, a Dictionary, a PropertyGrid or a ComboBox... all these things are much more complex in a language like C++.

Thanks to having a solid foundation for our entities, with Id and ToStr, we can use Lazy<T> to solve lazy loading in a neat and powerful way, or EntityControls that simplify user interface removing almost all code behind for typical scenarios. These requirements also enable the integration of application modules. The most important feature of Signum Framework.

Schema Generation

In order to remove duplication, the db schema is automatically generated from your entities using a simple 1-1 mapping from entities to tables:

  • Every IdentifiableEntity has its’ own table and every field is a column.
  • EmbeddedEntity has no table, instead adds columns to the ‘host entity’ table.
  • A MList<T> field has no column; instead it produces a relational table with a back-reference to the ‘host entity’ id and the necessary columns to represent T. So you can make many-to-many relationships when T is an IdentifiableEntity, one-to-many when T is an EmbeddedEntity, or even create MList of value types.
  • Using Lazy<T> (T is a IdentifiableEntity) is no different to using T for the database schema.

In the early stages of your application development, when changes are more likely to happen, you can create a new database each time using Administrator class. When the application is on production, and you can’t afford to lose the data, you can generate a synchronization script that will modify the schema according to the changes in your entity model.

Validation

Signum.Entities base class ModifiableEntity (from where EmbeddedEntity and IdentifiableEntity inherit) adds validation support by implementing IDataErrorInfo.

There are two ways of implementing validation logic in your entities:

  • Declarative: When only one value is necessary (70% of the cases), you can just place custom attributes over your properties. This is something hard to do in other frameworks, since the properties have been automatically generated.
  • Imperative: When a custom logic needs the value of many properties to test for validation, you can fallback on a more imperative approach by overriding IDataErrorInfo indexer.

These validations are checked before saving an object, avoiding corruption in the database, and also in the WPF user interface (ASP.NET MVC coming soon) in a very responsive way.

You could also use database-wide validations by subscribing to Signum.Engine events (kind of triggers) in the next version.

Change Tracking & Serialization

Change Tracking is embedded inside of the entities, so there’s no dependency on a global context of the ORM, simplifying serialization and making it easy to send the entities to the client side of the application.

Inheritance Support

Since the relational databases have no concept of inheritance, one of the most important features of an ORM is the way it handles inheritance.

For a hierarchy like this:

C#
public abstract class PersonDN : Entity
{
   string name;
   //(..)
}

public class SoldierDN : PersonDN
{
   WeaponDN weapon;
   //(..)
}

public class TeacherDN : PersonDN
{
   BookDN book; 
   //(..)
}

In NHibernate there are three ways of implementing inheritance in a db:

  • Table-Per -Hierarchy: Every Person and Teacher goes to the new table. The discriminator is used to differentiate between them.
    <code><code>PersonDN (Id, ToStr, Discriminator, Name, idWeapon, idBook)
  • Table-Per-Subclass: Here we have three tables, one with the common items and two with the differences.
    PersonDN(Id, ToStr, Name)
    SoldierDN(idPerson, idWeapon)<br />TeacherDN(idPerson, idBook)
  • Table-Per-Concrete-class: Two tables are created, one for each non-abstract concrete type.
    SoldierDN(id, ToStr, Name, idWeapon)
    TeacherDN(id, ToStr, Name, idBook)

When we designed inheritances in our framework, we went just for the third way because it is the simplest.

  • With the first and second options, you need to add a 'hierarchy concept' in the framework, something that embraces the three classes and puts them together in the same table (TPH) or in the same table hierarchy (TPS).
  • Since interfaces allow some kind of multiple inheritances, the same entity could potentially be part of different hierarchies, and this is a very bad situation for composability.
  • The algorithm to know where an entity actually resides becomes more complex with hierarchies. On TPH it is hard to make a FK to Soldiers only. On TPS you end up with two IDs for the same type, one as Person and one as a Teacher/Soldier.
  • You avoid the type mismatch of TPH (allowing null when the model doesn’t need it)

In order to enable inheritances scenarios, we move the responsibility to the FKs. We have two kinds of Polymorphic-FKs:

  • ImplementedBy: In this kind of FK you specify, using an attribute, the concrete classes that could appear in there, and a column for each type is created.
  • ImplementedByAll: Two columns are created, ID, with no FK, and Type, pointing to the global TypeDN table.

However, the main reason for going for the Polymorphic FK solution is that all these complex solutions solve only the problem of saving entity hierarchies, while PFK also allows referencing to ‘potentially every entity’ (using ImplementedByAll), very useful for general entities like Notes and Documents that can be attached to any other entity. PFKs also simplify overriding this information at runtime, making it easy to modify entities out of your control and, ultimately, empowering composability.

Laziness

Every ORM has to deal with laziness in some way. Many (Linq to SQL or NHibernate for example) do this in a ‘transparent’ way. The problem then is that then you create a hidden dependency from your model to the ORM.

Signum Framework is honest about laziness by using Lazy<T>. Whenever you need to point to a Person, but you don’t want to load it each time, you use Lazy<Person> instead. This object just knows about the Type, the Id and the ToStr, of the entity it’s pointing to.

Lazy<T> is integrated in every tier of Signum Framework: In the UI (EntityControls), the engine itself (Extension methods to retrieve Lazy, and to convert an IdentifiableEntity into a Lazy) and the Linq Provider.

Graphs

Signum Framework explores a graph of entities looking for cycles before saving. If a cycle is found, it creates the connection in a second phase, so you can save complex graphs of objects at once without worrying about the cycles.

Transactions

Our model for transactions is based on System.Transaction.TransactionScope, but makes every nested transaction silent by default. This is convenient in order to make the business logic easily composable.

User Interfaces (WPF and ASP.Net MVC)

Signum Engine is the powerfull ORM in the code of Signum Framework. But Signum Framework is not just an ORM, it is a full framework that helps you in making composable applications in every layer of your application. Signum.Windows is the assembly that radically simplifies building a WPF UI for your entities.

Since all the entities implement INotifyPropertyChanfe and IDataErrorInfo, data binding and validation comes for free on the user interface.

Also, by having Id and ToStr in every single entity, we can make EntityControls: Controls that can deal with any kind of entity so, when used for an specific type, they know how to retrieve a list of entities of this type (EntityCombo), enable AutoCompletion (EntityLine)… without writing any line of code-behind when default implementation is enough. If that’s not the case, events are exposed in order to modify the behavior.

Also, Signum.Windows encourages a declarative way of writing the UI, using UserControls instead of Windows. It also exposes a centralized way for Navigation, Security, etc… Which simplifies composability in the user interface.

WCF Services

Signum.Services is a very simple assembly that defines the WCF contracts between a WPF client application and the server. We break the rules again here, because we use NetDataContract instead of DataContract, that enables sharing types (instead of shared contract) between the two sides in order to share the entities.

We want to place the validation in the entities just once, and we are targeting scenarios where you have the control of both the server and the client application, so we have rejected the general SOA-Architecture, with DataContracts on every entity and a big WSDL between the client and the server, because we don’t need both sides (client and server) to evolve independently.

LINQ Support

Writing a full LINQ provider for a complex language like SQL is not an easy task. Many of them have tried but very few projects have achieved a proper one.

Many of the LINQ providers out there only implement a small subset of the LINQ operations, sometimes this is ok because the target language of the translation doesn’t support some given operation. But when the target is SQL, often they are just ‘pretending’ that they have a full Linq provider when they don’t. Take note of the following:

  • ORM LINQ Providers that use IEnumerable (Func<…>) instead of IQueryable (Expression<Func<…>>): because you will have HUGE performance penalties if you try to use it as a SQL query (you will have to retrieve loots of objects first). Remember than an IEnumerable LINQ provider is nothing other than a LINQ-friendly API.
  • ORM that uses LINQ to SQL instead of writing their own LINQ Provider: Because then you will have all your entities duplicated (the ORM entity and the LINQ to SQL entity) and you will spend lots of time transforming ones into the others.
  • ORM with an incomplete LINQ provider: Once you go the IQueryable approach, enabling each LINQ operation is hard work. If your LINQ provider doesn’t support some operation you will have to ‘fall back’ on SQL in those situations, losing a lot of productivity. Currently, this is a common problem for the ‘flexible’ ORMs, like NHibernate or Entity Framework, since doing a LINQ provider is harder the more distance there is between your model and the database.

Frans Bouma found the same problem when writing the LINQ Provider for LLBLGen PRO, and in order to “sift the wheat from the chaff” he posted a test for LINQ Providers to pass.

Signum Framework exposes the LINQ provider in the Database.Query<T> method. This is how we pass the exam:

  • Can it do joins? Yeap.
  • Can it handle GroupJoin and DefaultIfEmpty? Yeap. Some differences in the way DefaultIfEmpty works.
  • Does it support LINQ on all supported databases? SF only supports SQL 2005/8, so yeap.
  • Can it handle boolean values anywhere in the Linq query, also on other databases? Yeap.
  • Can it do Group By in C# and VB.NET? With multiple aggregates? Yeap.
  • Can it handle let? Yeap. I don’t find let really problematic. It’s a Query comprehension feature that gets translated to standard Select statements at the end.
  • Can it produce server-side paging queries? On all supported databases? We implement Take, and Skip, so Yeap. (Again, SQL 2005/8 only).
  • Does it handle in-memory object construction inside a query? And in-memory method calls? Yeap. As usual, only ‘at the end of the query’ (not in a SQL where statement)
  • Does it offer a flexible way to map .NET methods/properties onto DB functions/constructs? Yeap. We don’t encourage it because we follow an Entities-first approach, but you can use Sql Compatibility Extensions to use this in your queries.
  • Can it do hierarchical fetches? Of entities? Efficiently? You can do hierarchical fetches, but not efficiently (it does a sub query each time). Planned for a following version. Now supported in Signum Framework 2.0
  • Can it deal with Nullable types and implicit casts? Yeap. Not sure if we all (Signum Framework, LLBLGen, Linq to SQL & Linq to Entities) do this in exactly the same way, but yeap.
  • Does it handle type casts for inheritance scenarios? Can it handle inheritance types in LINQ queries? Yeap. Casting has to be used whenever ImplementedBy and ImplementedByAll attributes are taking effect in your entity model. In Signum Framework 2.0 GetType() is also allowed.   

We’ve found this test pretty complete, spotting many of the complex things that need to be solved by a proper LINQ provider in order to be useful. We would like to add some more that are also available in Linq to Signum

  • Does it support ‘Dot Join’? The ability to do joins just by navigating relationships. We implemented it as an outer join instead of an inner join.
  • Does it support Entity Equality? Emulating == operator on entities by comparing the Primary Key. In Signum Framework, it also works when ImplementedBy and ImplementedByAll are taking place.
  • Native emulation of Sql Functions? Typical String, DateTime, TimeSpan and Math functions are emulated using SQL Functions.
  • Does it support SelectMany? Always? On every DB supported? For us is easy, since we only support SQL 2005/8 but is not easy when CROSS APPLY / OUTER APPLY are not available.

Finally, there are some handy features of Linq to Signum that need to be mentioned, but that are not applicable to other ORMs, like Retriever Integration (the ability to retrieve full entities when they are part of the result, handy but, of course, slower), or retrieving Lazy objects by referencing a column of type Lazy<T> or by calling ToLazy() on a complete entity.

Conclusion

Signum Framework is a different animal. It uses the latest technologies from Microsoft (LINQ, WCF, WPF) but it’s brave enough to say NO to some of the of the current ‘establishment’ principles, like POCO, SOA, and complex XML configurations.

As many, the whole framework tries to leverage the developer from technical details, so it can be focused on your business entities and logic. But also it provides the solid foundations to make future application composable (from the database to the user interface) in the form that very few primitives are used to model business entities.

In the near future, the roadmap of Signum Software is to release Signum.Web, (applying the ideas behind Signum.Windows to ASP.NET MVC) and providing some general purpose modules to simplify common tasks like Authorization, Operations…

As time goes on, we expect third party developers selling business modules like Payments, Stock management, CRM, HR, Payroll, Task, To-Do list… easily to integrate in their application, and this is something that won’t happen with more flexible ORMs.

Who wants to be the first?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Signum Software
Spain Spain
I'm Computer Scientist, one of the founders of Signum Software, and the lead developer behind Signum Framework.

www.signumframework.com

I love programming in C#, Linq, Compilers, Algorithms, Functional Programming, Computer Graphics, Maths...

Comments and Discussions

 
QuestionNoSQL? Meet NoRM! Pin
jboarman1-Aug-11 12:36
jboarman1-Aug-11 12:36 
AnswerRe: NoSQL? Meet NoRM! Pin
Olmo del Corral2-Aug-11 0:09
Olmo del Corral2-Aug-11 0:09 
GeneralMy vote of 5 Pin
Razan Paul (Raju)12-Jul-11 15:58
Razan Paul (Raju)12-Jul-11 15:58 
QuestionWhich version of Signum is this? Pin
torial12-Jul-11 10:45
torial12-Jul-11 10:45 
AnswerRe: Which version of Signum is this? Pin
Olmo del Corral12-Jul-11 21:44
Olmo del Corral12-Jul-11 21:44 
GeneralMy vote of 5 Pin
Member 468393312-Nov-10 0:46
Member 468393312-Nov-10 0:46 
GeneralSecurity Key Pin
autenje17-Sep-10 4:43
autenje17-Sep-10 4:43 
GeneralRe: Security Key Pin
Olmo del Corral18-Sep-10 1:36
Olmo del Corral18-Sep-10 1:36 
QuestionASP.Net MVC- do we have an available date yet? Pin
SteveMets8-Dec-09 10:52
professionalSteveMets8-Dec-09 10:52 
AnswerRe: ASP.Net MVC- do we have an available date yet? Pin
Olmo del Corral2-Jan-10 5:04
Olmo del Corral2-Jan-10 5:04 
GeneralRe: ASP.Net MVC- do we have an available date yet? Pin
SteveMets2-Jan-10 6:56
professionalSteveMets2-Jan-10 6:56 
GeneralRe: ASP.Net MVC- do we have an available date yet? Pin
SteveMets28-Apr-10 8:52
professionalSteveMets28-Apr-10 8:52 
GeneralRe: ASP.Net MVC- do we have an available date yet? Pin
Antonio Herranz6-May-10 3:14
Antonio Herranz6-May-10 3:14 
GeneralRe: ASP.Net MVC- do we have an available date yet? Pin
SteveMets8-May-10 11:31
professionalSteveMets8-May-10 11:31 
GeneralRe: ASP.Net MVC- do we have an available date yet? Pin
Olmo del Corral12-Jul-11 9:48
Olmo del Corral12-Jul-11 9:48 
Generalcongrats, nice approach Pin
christoph brändle30-Mar-09 6:51
christoph brändle30-Mar-09 6:51 
GeneralRe: congrats, nice approach Pin
Olmo del Corral30-Mar-09 7:34
Olmo del Corral30-Mar-09 7:34 
GeneralRe: congrats, nice approach [modified] Pin
christoph brändle30-Mar-09 8:27
christoph brändle30-Mar-09 8:27 
GeneralRe: congrats, nice approach Pin
Olmo del Corral30-Mar-09 9:15
Olmo del Corral30-Mar-09 9:15 
GeneralRe: congrats, nice approach Pin
christoph brändle30-Mar-09 23:25
christoph brändle30-Mar-09 23:25 
GeneralRe: congrats, nice approach Pin
Olmo del Corral31-Mar-09 0:40
Olmo del Corral31-Mar-09 0:40 
GeneralRe: congrats, nice approach [modified] Pin
christoph brändle31-Mar-09 1:31
christoph brändle31-Mar-09 1:31 
GeneralRe: congrats, nice approach Pin
Olmo del Corral31-Mar-09 1:44
Olmo del Corral31-Mar-09 1:44 
GeneralRe: congrats, nice approach Pin
christoph brändle31-Mar-09 2:35
christoph brändle31-Mar-09 2:35 
GeneralRe: congrats, nice approach Pin
Olmo del Corral31-Mar-09 4:30
Olmo del Corral31-Mar-09 4:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.