VITA Tutorial

Part 1. Basics: entities, models, setup, CRUD and LINQ over entities

Outline

We will outline the basic workflow of building applications with VITA: define an entity, create an entity model, setup an application, update the database schema to match the model, and finally show how to perform some basic read/write operations with entities.

Simple entity model - just a book

Our sample application will be about books, authors and publishers. We will store these data objects in the database, in corresponding tables. In c# code, they will exist as 'entities' - strongly-typed data containers, each matching a single data row in the database tables.

VITA Entity is a .NET interface decorated with an [Entity] attribute
Here is a simple IBook entity:

  using Vita.Entities;

  [Entity]
  public interface IBook {
    [PrimaryKey]
    Guid Id { get; set; }
    string Author { get; set; }
    string Title { get; set; } 
    DateTime PublishedOn {get; set;}
    double Price { get; set; }
  }


Nothing special here, a book has a number of properties like Title, Author and Price. For now, our books can have just one author - we will take care of multiple authors later.
We use two attributes defined by VITA framework: EntityAttribute and PrimaryKeyAttribute. These are the only two attribute required to define a "minimal" entity. Their meaning is obvious: [Entity] marks an interface as an entity, and [PrimaryKey] identifies a property/column that will be used as a primary key in the "Book" table in the database.

Adding a publisher

Let's expand the model a bit. Every book has a publisher, so let's define an IPublisher entity and link it with a book:

  using Vita.Entities;

  [Entity]
  public interface IBook {
    [PrimaryKey]
    Guid Id { get; set; }
    string Author { get; set; }
    string Title { get; set; } 
    DateTime PublishedOn {get; set;}
    double Price { get; set; }
    IPublisher Publisher { get; set; }
  }

  [Entity]
  public interface IPublisher {
    [PrimaryKey]
    Guid Id { get; set; }
    string Name { get; set; }
  }

It is quite easy to make one entity reference another - just define a property of data type matching the other entity.
At some point these entity interfaces will be translated into database tables - here are some simple rules regarding this translation:
  • Table names will be derived from the interface names by truncating the "I" prefix. Alternatively you can specify the table name as a parameter in Entity attribute.
  • Column names will match the property names for value types and strings.
  • String properties will be mapped to 'nvarchar(50)' columns. '50' is the default specified in VITA's EntityModel class - you can either change this default when you setup the application, or set the explicit size for the column with the Size attribute.
  • IBook.Publisher property will be mapped into foreign key column Publisher_Id (Guid). The column name is constructed using the pattern: property-name + _ + target-PK-name.
  • The values for GUID primary keys are not automatically generated, we have to set them explicitly when we create new instances in code. You can use [Auto] attribute to generate new ID values automatically for new entities.
  • VITA also supports identity columns, if this is your preference for primary keys - just mark an integer property with the [Identity] attribute.

Constructing an entity model

Now, having defined two entities, we need some way to pass these definitions to VITA engine, asking it to construct some kind of data access layer, so we can actually start working with actual data in the database. To accomplish this, we need to construct an entity model - an overall set of entities used by the application.
The entity model structure is layered:
  • First, there are entities - interface definitions marked with Entity attribute. We have already defined two entities.
  • Entities are grouped into entity modules - groups of related entities belonging to some functional area.
  • Entity modules are grouped into Areas. Areas are simple aggregates, that will be translated into 'schemas' in the database, like 'dbo' schema.
  • Entity areas are aggregated into an entity model - an instance of the EntityModel class. The entity model represents an entire logical data model of the application. Note, that we mean 'logical' model - it is not tied in any way to specific database provider or database server.
  • The entity model is combined with a data store (provider-specific database model) to form an Entity Store - a connected-to-database virtual representation of the data model. Constructing an entity store is covered in the next section.
The following code shows definitions for a single entity module and an entity model for our simple application:

  public class MainBooksModule : EntityModule {
    public MainBooksModule(EntityArea area) : base(area, "Main") {
      RegisterEntities(typeof(IBook), typeof(IPublisher));
    }
  }

  public class Part1EntityModel : EntityModel {
    public MainBooksModule Main; 
    public Part1EntityModel() : base("Part1-Model") {
      var mainArea = AddArea("Part1", "part1");
      Main = new MainBooksModule(mainArea);
    }
  }

"part1" string is the name of the database schema that will contain the tables for our entities. We expose the entity module as a property (field in fact) of the entity model class - this is a recommended practice.

Application setup: constructing an entity store

Now having defined the entity model, we can instantiate it at application startup, and then use it to construct an entity store - an in-memory interface to the database. An entity store is represented by an instance of the IEntityStore interface. Simply explained, the entity store is a combination of an entity model and a data store - a low-level, provider-bound interface to the physical database. We will not go into internal of the data store, but will use a helper extension method CreateEntityStore to construct an entity store:

    using Vita.Data; 
    . . . . 
      // execute at application startup
      var model = new Part1EntityModel();
      var entityStore = model.CreateEntityStore("(connection string)", new Vita.Data.MsSql.MsSqlDriver());

You will normally create the entity store instance at startup and save it in a global static singleton. We provide two parameters to the CreateEntityStore method: a connection string to the database, and an instance of the driver - a specialized VITA-defined object providing low-level database access operations. The method has more optional parameters, but we are fine for now with default values. The CreateEntityStore call does the following: it connects to the database, reads the meta data, compares the objects found in the database with the model described by the entity model, and updates the database to match the entity model. So once the code above is executed, there will be two new tables in the database, one for each of our entities. The setup is finished, and we are now ready to work with the actual data.

Opening entity sessions, creating entities

To start working with the underlying data store, we need to open an entity session:

      var session = entityStore.OpenSession();

The entity session (of type IEntitySession) is a light-weight object that represents a connection to the database created for a set of operations on entities. It tracks all the entities you loaded through it (and provides 'single instance' effect), and also tracks all modified/added/deleted entities - so when you call session.SaveChanges(), it knows about all entities involved, and submits all of the changes to the database at once.
Let's create a publisher and 2 books:

      var msPub = session.NewEntity<IPublisher>();
      msPub.Id = Guid.NewGuid(); 
      msPub.Name = "MS Publishing";
      //Create a book
      var bk = session.NewEntity<IBook>();
      bk.Id = Guid.NewGuid(); 
      bk.Author = "John Sharp";
      bk.Title = "c# Programming";
      bk.PublishedOn = DateTime.Today;
      bk.Publisher = msPub;
      bk.Price = 25.0;
      //Create another book
      bk = session.NewEntity<IBook>();
      bk.Id = Guid.NewGuid();
      bk.Author = "Jack Pound";
      bk.Title = "VB Programming";
      bk.PublishedOn = DateTime.Today;
      bk.Publisher = msPub;
      bk.Price = 20.0;
      session.SaveChanges(); //Submit to database

The code is self-explanatory. Before we proceed, let's save some entity IDs, so we can use them later:

      var msPubId = msPub.Id; 
      var bk2Id = bk.Id; 

Reading entities

Let's load the list of books and print their titles:

      session = entityStore.OpenSession();
      var books = session.GetEntities<IBook>();
      foreach (var b in books)
        Console.WriteLine("  Book: " + b.Title);

Do we really need to open a new session? No. But the entity session object that we used for creating entities kept them in memory after the SaveChanges call. So just for demo purposes, to make sure we read the real entities from the database, we opened a new session.
Now let's load a book by its primary key:

      var bk2 = session.GetEntity<IBook>(bk2Id);
      Console.WriteLine("  Success - loaded book '" + bk2.Title + "' by " + bk2.Author);

Lazy loading - book's publisher

What about book.Publisher ? Do we need to do anything to access it? Like load it explicitly from the database? The answer is no, we can access it directly:

      Console.WriteLine("  Book publisher: " + bk2.Publisher.Name);

When we loaded the book, its publisher object was not loaded. But as soon as we tried to read the Publisher property, the framework loaded the publisher object behind the scene and pushed it into the book's property. All the subsequent reads of the property will not fire any SQLs, but will use the already loaded Publisher instance. It is called 'lazy' loading - the data gets loaded on first use automatically.
Lazy loading also works for 'related' lists. We will see in the next part of the tutorial that we can define a Publisher.Books property - and books for a publisher will be loaded automatically, in a lazy fashion.
Another important feature is a "single instance" rule. We created two books from a single publisher. Now if we try to load the other book through the same session and check its Publisher property, we'll get the same publisher object. The same in the sense "same .NET instance". And the system will NOT run another SQL query to retrieve the publisher of the second book - it will find it in session's internal cache of loaded objects using entity type and primary key value.
Now you probably start to see that this seemingly simple entity interface IBook has some quite sophisticated object behind it, which can do some smart things for you - automatically. In fact, the entity interface ISOLATES your code from all the complexities of the data access, tracking state, tracking identities, etc. You manipulate the entity as if it is a dumb data container, but it is quite smart in fact, and it has a lot of tricks to help you do what you need without worrying about annoying details.

LINQ queries

LINQ (Language Integrated Query) and its Linq2Sql incarnation is a marvelous set of technologies. Admittedly, I am a big fan of the idea of writing c# code and have it automatically translated into SQL query. VITA framework uses LINQ and Linq2Sql a lot, and not only for direct queries. We will see in later parts of this tutorial how you can create custom SQL stored procedures from LINQ expressions written in c#. In this section we will show direct LINQ capabilities available for querying your entities.
All VITA entities are LINQ-enabled. Automatically. You don't need to create any extra maps or visual models in fancy designers - VITA does this for you behind the scene.
Here is a LINQ query that loads all books by John Sharp:

      var qBooks = from b in session.EntitySet<IBook>()
              where b.Author == "John Sharp"
              select b; 
      foreach(var b in qBooks)
        Console.WriteLine("  Book: '" + b.Title + "' by " + b.Author);

Note that book entities loaded using LINQ are no different from entities loaded with other methods we used before like session.GetEntity<IBook>(bookId) . These LINQ-produced entities are tracked by current session, support object identity and lazy loading of properties.
Another interesting fact: VITA framework provides very efficient LINQ implementation. All dynamic (ad-hoc) queries like the one we just showed are compiled on first use, and saved in the Compiled Query Cache. If you execute this or structurally identical query again, maybe with different parameters, the system will reuse the previously compiled query, saving the performance cost of SQL translation. Unlike other LINQ-enabled frameworks, you don't have to use CompiledQuery class explicitly - VITA does the work automatically behind the scene.

Conclusion

We went over some very basic concepts of VITA framework. Here is a screenshot of the sample application after executing the code for this section:

tut_part1_output.jpg

Appendix: More things to try with the sample code

After you execute the sample code as is, you can play with it a bit to see some extra effects.
Try to alter the IBook or IPublisher entities, and see the effects - particularly, in the database schema. For example, add ISBN string property to IBook entity, and see how a new column "automagically" appears in the database table.

Tutorial Home

Last edited Mar 30, 2013 at 7:02 AM by rivantsov, version 22

Comments

No comments yet.