VITA Quick Start Guide


  1. Introduction
  2. Creating a project
  3. Defining Entities
  4. Defining entity module, entity area, entity app
  5. Application setup
  6. Entity operations - CRUD, Linq
  7. Notes


This article provides a quick introduction to VITA Framework. We will outline the process of building a simple data-connected application - a simplified book catalog with books, publishers and authors. The sample we build in this guide is a simplified version of BookStore sample project included with VITA source code, so we do not provide it as a separate c# project.
Full source code for this guide is listed here.

Creating a project

We start by creating a project that will contain all our books catalog definitions and business logic. It will be UI- and DB- agnostic - not linked to any specific UI technology like ASP.NET or WinForms, and no association with a particular database server.
Open Visual Studio 2013 and create a new solution BookStoreAll.sln. Then create a new empty class library project BookStore. Remove the automatically created empty class.
Open Nuget package manager, search for Vita package online, download and install it into your project. This results in a new reference to Vita.dll assembly in 'packages' folder.
That's it, we are ready to start defining our books catalog.

Defining entities

Entities are c# representations of data rows in your database tables. By defining entities, we in fact define tables and other objects (keys, indexes) in the database. VITA's schema upgrade facility will create database objects at application startup - no separate SQL scripts necessary.
Entity definitions in VITA are interfaces. The actual objects you will manipulate in your code will be instances of classes automatically generated by VITA, but you will access properties (columns) only through the entity interfaces you defined. If you feel confused, just hold on, it will become clear soon.
We start by creating a cs file that will contain entity definitions. Create a new c# class named BookEntities.cs. Delete the default class BookEntities created by Visual Studio, and add reference to Vita.Entities namespace:
using System;
using System.Collections.Generic;

using Vita.Entities; 

namespace BookStore {


Let's create our first entity IBook, representing a book in our catalog:
  [Entity, ClusteredIndex("CreatedOn,Id")]
  public interface IBook {

    [PrimaryKey, Auto]
    Guid Id { get; set; }
    DateTime CreatedOn {get; set;}

    [Size(100), Index]
    string Title { get; set; } 

    [Size(Sizes.Description), Nullable]
    string Description { get; set; }

    [Unlimited, Nullable]
    string Abstract { get; set; }
    BookCategory Category { get; set; }
    DateTime? PublishedOn { get; set; }
  public enum BookCategory {
Our initial version has some basic book properties (Title, Description, etc) plus surrogate key ID. We don't have publisher and authors yet, but just wait, we will add them soon.
IBook interface and its properties are decorated with a few attributes defined by VITA framework, to provide some information that will be used when creating the database and specifically the table that will hold book rows.
  • Entity attribute - marks the interface as an entity. The schema maintenance code in VITA will create a database table with a matching name 'Book' ('I' prefix will be cut-off automatically).
  • ClusteredIndex attribute - specifies a clustered index for the table. The clustered index is an index that directs the actual physical order of rows in the table data pages. We use an automatically generated CreatedOn value to specify that book rows should be laid in the order they are created - this will make it easier for the IO subsystem of the server. The Id reference in the index makes the index unique - recommended but not required.
  • PrimaryKey attribute - any table in the relational database needs a primary key - a set of columns that uniquely identifies the data row. Some servers are lenient in this regard, but in VITA primary key is required to be specified explicitly for an entity. In our IBook entity we identify the Id column as a primary key. VITA allows using composite primary keys - we will see an example later. For a discussion of identity columns see the Notes section
  • Auto attribute - marks automatically-generated value. For the Id column, this attribute instructs the system to automatically generate a new Guid value for any new row. Note that the value is generated in c# code (on the client side), not in the database. The Auto attribute without parameters identifies 'new-guid' kind of value, but you can specify other kinds (Identity, CreatedOn, etc) using the optional parameter.
  • Size attribute - specifies a maximum size(length) of string or binary property. String- and binary- type columns in database tables need a specification of maximum size, unless they are defined as MEMO/BLOB with unlimited or very-very large limit. You can either specify a value directly using an integer value, or use a string code like we did for the Description property. Sizes.Description is a string constant identifying a size specified in a special Sizes dictionary that exists at application level. You reference this value through size code, and you can assign the actual value 'later', at application startup time.
  • Unlimited attribute - marks a string or byte array property as a large-blob/memo type, with corresponding Memo column in the database. For MS SQL Server it will be 'nvarchar(MAX)' db type.
  • Nullable attribute - marks the database column as nullable. String properties are not nullable by default (even though .NET String type is a reference type and is compatible with null value). Nullable attribute makes the corresponding column in the database Nullable. Note that PublishedOn property of type DateTime? is nullable in the database.
  • Index attribute - instructs the database engine to create an index on the corresponding column. We expect to query the table searching for books by title, so the index will improve the performance of such queries. For more on indexes see Notes section.
The Category property is of enum type BookCategory. Vita supports enums out-of-the-box, including bit-type enums (sets of bits, usually marked with Flags attribute). PublishedOn property is of nullable value type 'DateTime?' - the corresponding column in the database is a nullable DateTime column. The semantics is obvious and natural - .NET null/DB NULL means that the book is not published.
Now let's add publishers. Each book has a publisher, and multiple books might have the same publisher, so following normalization rules we define a separate entity/table for publishers:

  public interface IPublisher {
    [PrimaryKey, Auto]
    Guid Id { get; set; }
    [Size(Sizes.Name), Unique]
    string Name { get; set; }
Unique attribute specifies a unique index on the corresponding column - so we enforce publishers' uniqueness at database level. The Publisher table has no clustered index - we expect it to be a small table, so it would not gain from specific ordering in the database.
Each book must have a publisher, and Books table in the database must have a referential integrity link to the publisher table. We accomplish this simply by adding a Publisher property to the IBook interface:
    . . . 
    IPublisher Publisher {get; set;}
    . . .	
This takes care of everything at database level. VITA will create a Publisher_Id column in the Book table, and add a referential integrity constraint from this foreign key column the Id column of Publisher table. More than that, VITA will create an index on foreign key column Publisher_id - MS SQL Server does not do this by default. You have to assign the Publisher value when you create a book - it is non-nullable property. When you read a book entity from the database, you don't need to explicitly load the publisher - it will be loaded automatically (in lazy mode, on first read), and the property will return a Publisher object. See more on lazy loading in Notes.
What is more interesting is that we can now add a Books property to the IPublisher interface:

  public interface IPublisher {
    [PrimaryKey, Auto]
    Guid Id { get; set; }
    [Size(Sizes.Name), Unique]
    string Name { get; set; }
    IList<IBook> Books { get; }
Books property is automatic property - it will return a list of books from the publisher, automatically. VITA will detect that this is a list of entities, and the target entity (IBook) has a reference link to IPublisher entity. So it will figure out the proper meaning of the Books property, and the way to load it - automatically and lazily. It is lazy-loaded on the first read of the property value. Creating SQL, running it behind the scene - all will be handled automatically. You can also define an explicit ordering in this list.
Now lets turn to authors. Most books have authors (except folk tales), and some books have multiple authors. Authors, on the other hand, might have multiple books published. Classic many-to-many relationship case. VITA fully supports many-to-many.
We simply define IAuthor entity and a link entity/table IBookAuthor implementing many-to-many link.
  public interface IAuthor {
    [PrimaryKey, Auto]
    Guid Id { get; set; }

    string FirstName { get; set; }
    string LastName { get; set; }

    [Unlimited, Nullable]
    string Bio { get; set; }
  [Entity, PrimaryKey("Book,Author"), ClusteredIndex("Book,Author")]
  public interface IBookAuthor {
    IBook Book { get; set; }
    IAuthor Author { get; set; }

A few new things here. We have composite clustered index and primary key for IBookAuthor. For composite versions, you put the attribute on entity and list the properties that are part of the key. The CascadeDelete attribute denotes that whenever we delete a book, all book-author links are deleted automatically - this happens in the database. For a reference to authors, it is a somewhat different situation - we would rarely, if ever delete an author - books don't change authors list. So to prevent mistakes, we do not make it easier to do so and do not use cascading delete on Author property.
Now, at this moment, we have all needed tables to construct books with a list of authors, and we can manage information by directly inserting/deleting IBookAuthor entities. To retrieve authors list for a book, we would make a join (using Linq) of IBookAuthor to Author table. That's possible, but VITA provides direct support for operations like this making it much easier.
Just like we introduced publisher.Books automatic property, we can add book.Authors and author.Books properties - we just need to add one special attribute:
    // in IBook interface:
    IList<IAuthor> Authors {get;}
    . . . 
    // in IAuthor interface:
    IList<IBook> Books {get;}

Notice that automatic properties are read-only (only getter is defined). We use ManyToMany attribute to inform the framework that the relationship is many-to-many, and that the link entity/table is IBookAuthor. The framework will supply the code behind these properties that will do 2 things - first, whenever we read the property, it will automatically run the appropriate query and return exactly what is expected - list of target entities related to 'holder' through many-to-many link. Secondly, you do not need to explicitly create IBookAuthor entities to add an author to a book - you just add an author to book.Authors list, and the link entity/row will be created automatically. Same goes for removing authors from book's authors list.
That completes the entity definitions part. The full source code is listed here.

Defining entity module, entity areas, entity app

We defined our entities that will soon turn into database tables, but for now, they are just .NET interfaces - we need to register them with VITA framework and make it build a data-connected solution. We must first organize entity definitions into an EntityApp - a component representing the data model for our solution. The entity app is a layered structure: first, the entities are grouped into modules - .NET components that are self-contained pieces of functionality (somewhat like UI controls) that can be shared/reused between applications. Next, entity modules are organized into an EntityApp - a top-level class representing the entire application. In the middle, there are also EntityAreas - representations for database schemas, logical parts of database model. You use areas to group entities from different modules into schemas.
We start with defining BooksModule. We add a new class file 'BooksModule.cs' to the project, and add the module definition:
using System;
using System.Collections.Generic;
using Vita.Entities; 

namespace BookStore {
  public class BooksModule : EntityModule {
    public BooksModule(EntityArea area) : base(area, "BooksModule") {
      RegisterEntities(typeof(IBook), typeof(IPublisher), 
                            typeof(IAuthor), typeof(IBookAuthor)); 
BooksModule is a sub-class of EntityModule. The constructor receives a 'primary' entity area as a parameter, and uses RegisterEntities method to register all entities we defined previously. The base EntityModule constructor requires entity area as a parameter - this will become the default area for the module.
There are a few more things you can do in the constructor. If your module provides an application-wide service, you can register the service with 'area.App.RegisterService(type, service);' call. However, if you need to get the service provided by other module, you need to wait until later, to give the chance to all modules to register their services in their constructors. You should override the Init method - it is called when the app is assembled and completes the initialization. At the time of Init call, all services should be registered in the service container.
Entity module (its subclass) is a place to put related code that implements some specific features related to the functional area.
At this point, we have a self-contained component BooksModule that can be included into a bigger app (ex: amazon clone) and provide the implementation of a book catalog. This component lives in a standalone class library, which is database-type agnostic, and can be used in any app that needs the book catalog - whether it is running on MS SQL Server, MySql or Postgres.
We now turn to assembling an application from entity modules. Usually this entity app code should live in a separate project that references our books module and other modules it needs. But for simplification, we will add the entity app class directly to our project containing entities and module.
We add a new file 'BooksEntityApp' to our project, and enter the following code:
using System;
using System.Collections.Generic;
using Vita.Entities; 

namespace BookStore {
  public class BooksEntityApp : EntityApp {
    public BooksModule MainModule; 
    public BooksEntityApp() { 
      var booksArea = this.AddArea("Books", "books");
      MainModule = new BooksModule(booksArea);
That is all we need, to assemble the application. Just for illustration purposes, let's see how we can 'import' more modules into our app - we will add error log module. This will allow our code to log exceptions in the database. VITA comes with Vita.Modules assembly - a set of pre-built modules that you can use in your apps. One of the modules is ErrorLogModule.
We need to a reference to Vita.Modules assembly/project to our project references. Then we can add error log module to our app:
using System;
using System.Collections.Generic;
using Vita.Entities; 
using Vita.Modules.Logging;

namespace BookStore {
  public class BooksEntityApp : EntityApp {
    public BooksModule MainModule;
    public BooksEntityApp() { 
      var booksArea = this.AddArea("Books", "books");
      MainModule = new BooksModule(booksArea);
      var logArea = this.AddArea("Log", "log"); //creating separate schema
      var logModule = new ErrorLogModule(logArea);
We could register error log module in the booksArea, but we choose to create a separate 'log' schema for log tables. This will make it easier to manage at database level. One convenience is that VITA allows placing areas/schemas and all containing tables into different databases - which makes sense for information like logs. Logs are highly dynamic, write-only data stores, with information aging quickly - which might be regularly backed up and purged to release disk space.
Notice we do not save created instance of log module in a class-level field. That is not needed, as functionality of error log is exposed through IErrorLogService, which it registers automatically. We can get the service in any place of our application:
    //anywhere in your code
    var errorLog = BooksApp.GetService<IErrorLogService>(); 
BooksApp store is a global singleton of BooksEntityApp class representing a 'connected entity application' - see next below.
At this point we have our entity application, a full data model with accompanying functionality. It is still database-agnostic, so our entity app can run against MS SQL Server, MySql or Postgres (other will be added soon). We are not committed to any specifics of the database access - whether we will use stored procedures or dynamic SQL for CRUD operations; use Batching mode for multiple updates or one-by-one calls; use or not the data cache and which entities to keep in cache, etc. All these things we decide 'later', when we initialize and connect the EntityApp.
We will show the initialization of the entity app in the next section.

Application setup

We are going to use MS SQL Server in our example. Using other server types is just as easy, you have to change only the type of database driver you create (but you have to install Vita.Drivers Nuget package which contains these other drivers).
Our goal is to initialize and connect our BooksEntityApp. We will use static helper methods. Connected BooksEntityApp instance will be saved as a global singleton; how to manage such objects is a subject of dispute (singletons and globals are baaad - some people say), but for simplicity we will use a static singleton.
The initialization code usually 'lives' in the top-level project hosting the app - like WebApi application.
The project must references some extra VITA's assemblies:
  • Vita
  • Vita.Data.MsSql (or other database driver assembly)
  • Vita.Modules (if you use standard modules like ErrorLog - like we do)
For hosting the init code and global singleton let's create a static BooksStoreConfig.cs class:
using System;
using System.Collections.Generic;
using Vita.Entities;
using Vita.Data;
using Vita.Data.MsSql;

namespace BookStore {
  public static class BookStoreConfig {
    public static BooksEntityApp App {get; private set;}
    public static void Configure (string connectionString) {
      App = new BooksEntityApp();
      var driver = new MsSqlDriver(MsSqlVersion.V2012);
      var dbSettings = new DbSettings(driver, MsSqlDriver.DefaultMsSqlDbOptions, 
          connectionString, upgradeMode: DbUpgradeMode.Always);
Note: In VITA download package, for Books sample the host of entity app is a unit test project, and entity app singleton is in static property of SetupHelper.cs class.
The Configure method accepts a single connection string parameter - we assume it comes from app config file. The method is supposed to be called from app-init method, like Application_Start in Global.asax file in ASP.NET app. We instantiate our app object, create MS SQL Driver for Vita, and create DbSettings object with proper parameters (we use mostly defaults here); finally we connect the app to the database.
You must create an empty target database manually, before you start the application. Once the Configure method is executed, the database schema is updated - you will see tables matching entity definitions, with keys, indexes and referential integrity constraints. The framework will also create CRUD stored procedures for all entities. Note that schema update functionality is 'difference'-based - if you start the app again, the system will compare the database schema in the database to what it should be according to current state of the entity model, and if there are any differences, it will update the database while preserving the existing data.
We are now ready to execute the database access operations - create publishers, books, authors, and execute CRUD and LINQ operations against the database.

Entity operations - CRUD, LINQ

We can now perform data access operations anywhere in our app code using a global singleton BookStoreConfig.App. The file where we perform the data access must have a few 'using' references:
using System;
using System.Collections.Generic;
using System.Linq;
using Vita.Entities;

Let's create a few objects and submit them to the database. We start with opening an entity session - an object that represents a connection to the database:
    var booksApp = BooksStoreConfig.App;
    var session = booksApp.OpenSession(); 
The session object is responsible for all data operations from the client code. Let's create a publisher, authors and a book:

  var msPub = session.NewEntity<IPublisher>();
  msPub.Name = "MS Publishing";
  var john = session.NewEntity<IAuthor>();
  john.FirstName = "John";
  john.LastName = "Sharp";
  var jack = session.NewEntity<IAuthor>();
  jack.FirstName = "Jack";
  jack.LastName = "Hacker";
  var csBook = session.NewEntity<IBook>();
  csBook.Title = "c# Programming";
  csBook.Description = "Expert programming guide to c#.";
  csBook.Publisher = msPub;
  csBook.Category = BookCategory.Programming; 

One of the entity session's responsibilities is to track all entities loaded or updated through it, so you don't have to keep track of all created/modified/deleted entities - you just call session.SaveChanges() - as we do here, and all entities we just created will be sumbitted to the database. All the entities are submitted in one transaction. As we know, with referential integrity in database the insert order of multiple records is important - parent entity should be inserted before any child entities referencing it. The SaveChanges call does this proper ordering automatically, so it does not matter in what order you create entities in your code - the system will do it right.
Let's now see how to retrieve entities.
    var authors = session.GetEntities<IAuthor>(take: 10); 
Note that you have to use 'take' parameter - VITA tries to protect you from accidentally loading too much.
It often happens that SelectAll works OK for test dataset, with small data, but suddently freezes in production when you hit real big-size data; to avoid this, VITA requires you to always specify 'take' argument. With one exception - you can mark an entity with SmallTable attribute, and this signals that it's OK to query all. We did not use this attribute, so we have to provide 'take' everywhere.
The 'authors' variable should now contain 2 entities that we just created. These are exactly the same object instances as those in our local variables - the session provides an 'object identity' transparency. No matter how you get entities for the same row in the database, the objects will be the same in the .NET object-reference sense. Session also works as a first-level cache - if you load an entity using its ID, then all consequitive similar calls will be served from session's cache without executing any SQLs.
Note that session's cache is protected against overflow (known issue in Hibernate which caches references to all loaded records, and runs into 'out-of-memory' when you load 50K+ entities). In VITA's session, loaded records are held through weak references which are valid as long as you reference the entity in your client code. This weak reference list is purged regularly. The weak reference is switched into strong reference as soon as you modify an entity - so it never loses an entity that has changes to be submitted.
We can now get an author by ID and see his books. Just for illustration purpose, we want to ensure that the entity is coming from the database (not from session cache), so we open a new session:
    var johnId = john.Id; 
    //reload john in another session
    session = booksApp.OpenSession(); 
    john = session.GetEntity<IAuthor>(johnId);
    Debug.WriteLine("John published " + john.Books.Count + " books."); 	
We did not have to do anything extra with an old session - just drop it, and it will be garbage-collected. When we read john.Books property, the framework ran the SQL query that selected books filtered by author ID, instantiated book entities and added them to the returned list. The same goes for the book.Authors property. The list is cached inside the entity, so consecutive reads will not result in new queries.
Let's now get the first john's book, modify its description and save it:
    csBook = john.Books[0]; //this must be c# book
    csBook.Description += " Covers c# 4.5";
The session object (IEntitySession interface) allows either select multiple entities (within certain skip/take page), or retrieve an entity by Id. To select entities using more complex filters, we can use LINQ:
    session = booksApp.OpenSession(); 
    var queryCsBooks = from b in session.EntitySet<IBook>()
                       where b.Title == "c# Programming"
                       select b;
    var csBooks = queryCsBooks.ToList(); //should be one book
The session.EntitySet<TEntity>() method returns an IQueryable<TEntity> instance that can be used in Linq queries. You can use all types of queries you used to in Linq2Sql library and other Linq providers. And you can use automatic list properties (like book.Authors) with expressions like 'where book.Authors.Contains(john)...' - VITA's Linq translation engine knows how to handle these properties.
See unit tests project for BooksSample in VITA download for many examples of different Linq queries. By the way, you can always retrieve the SQL executed for the Linq query using session.GetLastCommand() extension method - call it right after you execute 'query.ToList()' and it will return the IDbCommand instance, and you can access the SQL (cmd.CommandText) and parameters of the data command.
A few notes about Linq functionality. VITA's Linq engine is extremely efficient - it caches the translated query (in parameterized form), so whenever you execute the same query (in the same place or in another place) - it will reuse the previously translated query with new parameters. In the previous example, we used a literal as book title we search - in this case the constant is part of query definition, and translated query will not be reused if we search for another title. To make it reusable, all we have to do is assign the title to a local variable (or use method parameter) and use this variable in the query. This query caching happens automatically - unlike in other frameworks (Entity Framework, Linq2Sql) where you have to explicitly use a CompiledQuery or alike object to make use of query definition caching.
Another nice feature of VITA's Linq engine - it works transparently with entity cache (data cache). With entity cache, you can instruct the system to keep some tables fully cached in memory. Whenever you run a query that involves cached entities, the query is rewritten into Linq-to-objects query and is executed against cached tables. Again, fully automatic and transparent, without an extra effort by the programmer!
This completes our Quick Start Guide. For more examples, explore the Books sample app and its unit test projects.


Primary keys - GUIDs vs Identity columns
We use GUIDs as surrogate primary keys in our sample application. The alternative is Identity columns. The Identity concept had been popular in database design in the past - the database server is a convenient point for generating unique values, but the trend in recent years had been to move away from it to GUIDs. Identity columns have problems when it comes to merging databases, replication, and when you need to generate a complex data structures remotely and submit it as a whole to the database. That is why we encourage to use GUIDs for primary keys. At the same time, VITA fully supports Identity columns (use Identity attribute on int or long property), to allow solutions running against pre-existing databases. More than that, VITA allows generating parent/child entity sets and submitting them in one batch - the newly generated identity values will be propagated from primary key to foreign key columns automatically.
Login and Logging modules coming with VITA fully support integer primary keys for custom User entity defined by an application. UserInfo object available as a property of OperationContext contains two fields: UserId (Guid user id), and AltUserId (Int64 user id). Both are set automatically on successful login from ILogin entity.

Clustered indexes and heap tables
If you do not specify a ClusteredIndex attribute on an entity, it becomes a Heap table. It used to be considered a good practice to have clustered index on every table in the database. However the direction seems to be shifting - due to latest advancements in database engines in many cases heap table (without clustering) is a better choice. To learn more about using heap tables and explicit clustered indexes read the documentation for your database server.

Size codes
The main purpose of size codes is a convenience of changing sizes of all columns of certain kind (like Descriptions) in one place. You can set the value for the code at application assembly/setup time, and it will set the size value for all columns in the model marked with the size code. Another use is ability to customize columns in entities of pre-built and imported modules, without modifying their source code. You are not limited to codes defined in the Sizes class - you can define your own codes and add corresponding values to EntityApp.SizeTable table.

Nullabe/non-nullable columns
For non-nullable string columns, your code should assign non-null and non-empty (!) string value to the property before submitting the entity to the database. In fact, VITA's validation code will stop an attempt to save changes if it detects a null or empty value in non-nullable property, and database operation will not be even attempted. Strings are not nullable by default. The Title column in IBook entity requries non-emtpy value. Value type columns (int, DateTime, double, decimal) are non-nullable. Nullable value types (DateTime?) are nullable in the database. You can also mark a value-type property as Nullable - in this case type-default value in entity property(ex: 0 for int) identifies NULL value in the database - the conversion is done automatically.

You can define composite (multi-column) indexes using the Index attribute, specify explicit column ordering, specify Included columns (additional columns included in the index to create so-called query-covering indexes), and specify index filter - for those database servers that support these facilities (MS SQL Server). By default the index is non-unique; use Unique attribute to create unique indexes.

Entity references and lazy loading
Entity references are loaded in lazy manner. When you load an IBook entity from the database, its Publisher property is empty. The referenced publisher is instantiated automatically on the first read of book.Publisher property. But it is not fully-loaded yet. What is returned first time is a stub - an empty Publisher object with only primary key value filled in (from the foreign key value in the parent book object). The publisher will be fully loaded only when you try to read it's property other than the primary key. This arrangement allows to avoid unnecessary loads when you try to use the entity reference to copy the foreign key only. For example:
  book2.Publisher = book1.Publisher; 
If book1.Publisher is not fully-loaded before this statement, it will not be loaded after executing this statement. The net result is that the foreign key book->Publisher in book2 is copied from book1, but no publishers are loaded.

Last edited Feb 23, 2016 at 8:48 PM by rivantsov, version 42


No comments yet.