Click here to Skip to main content
15,895,799 members
Articles / Programming Languages / C# 4.0

VITA Application Framework

Rate me:
Please Sign up or sign in to vote.
4.95/5 (17 votes)
25 Mar 2012MIT 15K   160   34  
Introduction to VITA - an application framework for building complex .NET applications working with databases and communicating over Web.
This is an old version of the currently published article.

Introduction 

Our earth is degenerate in these latter days; bribery and corruption are common; children no longer obey their parents; every man wants to write a book, and the end of the world is evidently approaching.

Assyrian clay tablet, circa 2800 BC

Just like this Assyrian dude 5K years ago, I'm sitting here, a grumpy old man, looking around at our development landscape, and grumbling: "... ain't getting any beta; NoSQLry and data corruption are common; new script kiddies no longer obey good ol' types and mock everything; every man wants to write a framework; even Visual Studio turns dark and gloomy, the end of the world as we know it is evidently approaching, and it's called Metro..."

Background

Yeah, that was a sad introduction. I'm kidding of course, but I'm really overwhelmed with frameworks and libraries. Every man wants to write a framework. ORMs, MVC and MVVM implementations, REST/services frameworks. I am a server-side programmer, so I'm primarily concerned with ORM and service-building tools. The really sad thing is that none of the existing frameworks in this area seem good enough - at least to my old-fashioned taste. Something is always missing, and in many cases there's simply too much stuff. Here is a list of my troubles... But wait - who wants to hear more of old man grumbling? Nobody I guess, so let's put it off - see my list of issues at the end of the article. Now let's get to the real subject of this article.

Out of frustration with existing stuff, and tired of waiting for the right thing, I decided to write my own framework - VITA. Yeah, every man wants to write a framework, myself included. This article is an introduction of what came out. We start with highlights - to give you the taste of what is this about.

VITA Framework Highlights

VITA is a framework for building complex .NET applications working with databases and communicating using REST over the Web.

  • Entities are defined as .NET interfaces - minimum coding required. Implementation classes are created at runtime using IL code generation.
  • Entities are self-tracking - they maintain original and modified property values, automatically register for update.
  • Database schema is automatically created/updated from entity model. Existing data is preserved. Database schema follows c# code.
  • Database keys and indexes are defined through entity attributes in c# - you can define most of the database artifacts in c# code, including custom stored procedures using LINQ syntax.
  • Automatically generated CRUD stored procedures. Alternatively, application can use direct SQL queries - automatically generated as well.
  • Support for one-to-many and many-to-many relationships. Foreign keys are automatically inferred from c# entity references. Properties which are lists of related entities are automatically "filled-up" on first read.
  • Automatic sequencing of update operations to satisfy referential constraints.
  • Full support for identity columns and auto-generated GUIDs.
  • Computed properties.
  • Automatic validation on submit. You can define a custom validation method for an entity - it will be automatically invoked before submitting changes to database.
  • Full LINQ support: you can run direct LINQ queries against entities.
  • Custom Stored Procedures - you can define custom stored procedures for SELECT, INSERT, UPDATE, DELETE operations in pure c# code, using LINQ syntax. The code will be compiled into stored procedures in database.
  • Top Performance - LINQ queries are compiled at startup into stored procedures.
  • Component Packaging Technology - with VITA we have a way to pack a set of entities/tables with the surrounding code into a self-contained component - entity module. Entity modules can cover specific areas of functionality, can be independently developed, tested and distributed, just like Windows Control libraries. The application is then assembled from independent modules, each covering specific part of functionality. More details here.
  • RESTful Web services - expose your model and operations as a RESTful Web service with minimal coding. Service help pages are generated automatically.
  • RESTful Web clients - create Web clients with no effort and use strongly-typed entity model on the client. The client-side code manipulating with entities is identical to the server-side code - the underlying Web protocol is transparent.
  • Standard modules - use pre-packaged modules of functionality coming with VITA. Currently available is an Error logging service, coming with log viewer. More modules to come soon.
  • Entities support INotifyPropertyChanged interface - readily usable in data binding and MVVM solutions.
  • Did I mention - full support for enums?

Interested? The let's go into details. Going step-by-step, from simple to more advanced would be too long exercise for this introductory article. Instead, we will do a crash course and run through the framework demo, sometimes leaving out details. The Tutorial on the project site provides a step-by-step introduction to the framework.

Using the source code

All source code that we show and discuss is available in the attached zip. Download and unzip the code, open the VitaAll.sln solution in Visual Studio. Try to compile the code. If the solution does not compile, you do not have some components installed - but you actually do not need them to run the samples. Simply disable the compilation the following projects (or remove them from the solution) :

  1. Vita.Data.SqlCe
  2. Vita.Samples.MultiStoreSample
  3. Vita.UnitTests.NUnit
  4. Vita.UnitTests.VsTest (if you use Visual Studio Express Edition)

Read the "Exploring the source code" article on the CodePlex site for more detailed instructions.

Locate the Vita.Samples.BooksModel project - it contains the books model. The code manipulating the books that we discuss in this article is implemented as a unit test in the BooksModuleTest.cs file in Vita.UnitTests.VsTest project. Before you run the code, create a local SQL Server database VitaTest. Now Run the Vita.UnitTests.VsTest project - this will execute the BooksModelTest that contains all the code we discuss here.

Building the Entity Model

Let's start with building a simple entity model - a books catalog. As I already mentioned in the Highlights section, entities are defined as interfaces:

C#
[Entity, Paged, OrderBy("Title"), Validate(typeof(BooksModule), "ValidateBook")]
public interface IBook {
    [PrimaryKey, Auto]
    Guid Id { get; set; }
    IPublisher Publisher { get; set; }
    string Title { get; set; } //uses default length
    [Size(250), Nullable]
    string Description { get; set; }
    DateTime PublishedOn { get; set; }
    [Memo, Nullable]
    string Abstract { get; set; }
    BookCategory Category { get; set; }
    BookEdition Editions { get; set; }
    double Price { get; set; }
    [ManyToMany(typeof(IBookAuthor))]
    IList<IAuthor> Authors { get; }
}
[Entity, OrderBy("Name")]
public interface IPublisher {
    [PrimaryKey, Auto]
    Guid Id { get; set; }
    string Name { get; set; }
    IList<IBook> Books { get; }
}

[Entity, Paged, OrderBy("LastName,FirstName")]
public interface IAuthor {
    [PrimaryKey, Auto]
    Guid Id { get; set; }
    [Size(30)]
    string FirstName { get; set; }
    [Size(30)]
    string LastName { get; set; }
    [Memo, Nullable]
    string Bio { get; set; }
    [Computed(typeof(BooksModule), "GetFullName"), DependsOn("FirstName,LastName")] 
    string FullName { get; }
    [ManyToMany(typeof(IBookAuthor))]
    IList<IBook> Books { get; }
}
[Entity, PrimaryKey("Book,Author")]
public interface IBookAuthor {
    IBook Book { get; set; }
    IAuthor Author { get; set; }
}

Quite simple model: a book has a publisher (another entity) and a list of Authors (another entity). I removed some attributes (descriptions) for brevity. BookEdition and BookCategory are enums - did I mention enums are supported? For saving space I do not list them here, nothing special, just a few values. BookEdition is a flag set (decorated with Flags attribute) - this is supported as well, including representation in XML in RESTful APIs.

Entity interfaces and properties are decorated with various attributes defined by VITA. Each interface is decorated with Entity attribute - identifies it as a special interface representing an "Entity". Paged attribute tells the system to create a SQL/stored procedure to perform paging in the database. PrimaryKey attribute identifies the primary key columns. OrderBy specifies the default sorting order for queries. The Auto attribute tells the system to automatically generate the ID value for new entities. Our model uses GUIDs as primary keys, but you can use Identity columns - they are fully supported. Validate attribute specifies method that performs entity validation before it is submitted to the database.

Notice the Comp<code>uted attribute on the Author.Full name property - it tells the system that this value is not stored in database but dynamically computed using the static GetFullName method. The DependsOn attribute instructs the entity to fire the PropertyChanged event for FullName property whenever FirstName or LastName are changed - in addition to firing the event for the properties themselves.

Now about entity references and foreign keys. The IBook interface has a Publisher property - that's the way to establish a relationship. The framework will infer the column and foreign key Publisher_Id (GUID) in the Book database table. You do not see this hidden field in code, and do not set it explicitly - you just assign the publisher, and the Id value is copied automatically.

On the other side of relation, there is a IPublisher.Books property, which is not decorated with anything. The system would infer that this list contains books by this publisher (by finding Publisher property on the book referencing back the IPublisher entity), understand that this is one-to-many relation, and will arrange things so that the property will in fact contain publisher's books at runtime, loaded in lazy fashion.

The other two list properties represent many-to-many relationships between books and authors: IBook.Authors and IAuthor.Books. In this case we have to identify the link entity (IBookAuthor) in the ManyToMany attributes on both properties. This link entity is not "guessed" automatically, we have to specify it explicitly.

Creating entities

Now let's start the application and create a few books. Setting up and starting the VITA-based application requires some initial code which is not important for us now. Don't worry, I'm not hiding anything substantial here - no extra XML mapping files, or Fluent API mappings - just some boring plumbing, not specific to our entities. The result of the setup is the EntityStore - a singleton object managing the entities in your model, and the gateway to the database. You work with the entity store by opening sessions. Let's do it and create a publisher, assign its properties and save it to the database:

C#
var session = entityStore.OpenSession();
var pub = session.NewEntity<IPublisher>();
pub.Name = "SomePublisher";
session.SaveChanges();

The publisher entity returned by the NewEntity method is tracked by the session that created it, so when you call session.SaveChanges(), it knows which entities to save. We did not need to assign the Id of the publisher - the property is decorated with Auto attribute, so system generated the value automatically. (Note that VITA generates sequential GUIDs using corresponding Windows kernel function).

Before we proceed with creating other entities, let's do some labor-saving enhancements. It is convenient to define extension methods for creating new entities to make creating them a one-line method call. So we create a static class BookExtensions and define the following methods:

C#
public static IBook NewBook(this IEntitySession session, BookEdition editions,
         BookCategory category, string title, string description,
         IPublisher publisher, DateTime publishedOn, double price) {
      var book = session.NewEntity<IBook>();
      book.Editions = editions;
      book.Category = category; 
      book.Title = title;
      book.Description = description;
      book.Publisher = publisher;
      book.PublishedOn = publishedOn;
      book.Price = price;
      return book;
} 
public static IPublisher NewPublisher(this IEntitySession session, string name) {
      var pub = session.NewEntity<IPublisher>();
      pub.Name = name;
      return pub;
}
public static IAuthor NewAuthor(this IEntitySession session, 
        string firstName, string lastName, string bio = null) {
      var auth = session.NewEntity<IAuthor>();
      auth.FirstName = firstName;
      auth.LastName = lastName;
      auth.Bio = bio; 
      return auth;
}

Note that the "this" parameter is IEntitySession interface - that's what is returned by the EntityStore.OpenSession() method. The methods do not save the created entity, just create the object, leaving it to caller code to save all entities at once. We can now create a few publishers, authors and books:

C#
var session = entityStore.OpenSession(); 
var msPub = session.NewPublisher("MS Books"); //we are using extension method here
var kidPub = session.NewPublisher("Kids Books");
var john = session.NewAuthor("John", "Sharp");
var jack = session.NewAuthor("Jack", "Pound");
var csBook = session.NewBook(BookEdition.Paperback | BookEdition.EBook, 
  BookCategory.Programming,  "c# Programming", "Expert programming in c#", 
   msPub, DateTime.Today, 20.0);
csBook.Authors.Add(john); //this is many-to-many
csBook.Authors.Add(jack);
var vbBook = session.NewBook(BookEdition.Paperback | BookEdition.Hardcover, 
   BookCategory.Programming, "VB Programming", "Expert programming in VB", msPub, 
   DateTime.Today, 25.0);
vbBook.Authors.Add(jack);
var kidBook = session.NewBook(BookEdition.Hardcover, BookCategory.Kids,
   "Three little pigs", "Folk tale", kidPub, pubDate, 10.0);
session.SaveChanges();  //Save all

I simplified this snippet a bit compared to the sources in the zip, just removed some unimportant extras. A few things to point out. Once we created a publisher, we associate a book with a publisher by simply assigning the book.Publisher property - look at the NewBook method. With authors, it's a bit different - it is a many-to-many relationship. We associate an author with a book by adding an author to book.Authors property (the result is a new link record in BookAuthor table when we save the changes).

Let's now run the code and look in details at what we get. Run the unit test project and look at the VitaTest database in SQL Management Studio. You will see 4 tables in [books] schema. (There are bunch of other tables in other schemas created by other unit tests in the project - just ignore them).

Note that table names match the entity names in our model, except they don't have the "I" prefix, conventional for .NET interfaces - VITA removes them automatically. Inspect the column lists of the tables - they match the properties of the entities. You will also find proper primary and foreign keys. Use the "Select Top 1000 Rows" context menu command (right-click on the table in SMS) to query the table and see that entities are actually created.

Next, expand the Programmability/Stored Procedures node - you will see a whole bunch of stored procedures for CRUD operations on books entities. All this stuff was created at application startup (test app in our case). The setup process specified a flag to update the schema, so VITA did just that - created tables, keys, stored procedures. Note that use of stored procedures is optional - there's another flag in setup that turns this on/off. With this flag off, the system would use parameterized SQLs (pre-built at startup) for CRUD operations. Just that easy - with one single flag, and the whole access method changes!

Now expand the Indexes node under the books.Publisher table. You will see something strange - a Unique index on the Name property. Where this is coming from? The fact is, VITA allows you to specify indexes that you want to define on the database tables using special attributes: Index, Unique, and ClusteredIndex. We could add a [Unique] attribute on the IPublisher.Name property - this will result in the index created on the table. We did not do it in our sample code above - but test code in zip does. Only it uses a facility in VITA called Companion types - you specify attributes not on entities themselves but on different companion interfaces and then associate the companions with the target entities. This way you can put all companions with all database indexes in a separate file, and let a dedicated member of the team with database expertise manage it. See the BookKeys.cs file for details.

Querying and updating entities

Let's move on. We created a few books, now let's query them. You can retrieve a single entity using its primary key:

C#
session = entityStore.OpenSession();
var csBook = session.GetEntity<IBook>(csBookId);

We can easily update the entity(ies):

C#
csBook.Price += 10;
session.SaveChanges();   

You can retrieve all books, or some books using paging:

C#
var allBooks = session.GetEntities<IBook>();
var books23 = session.GetEntities<IBook>(skip: 1, take: 2);

Let's print out all books:

C#
foreach(var bk in allBooks) {
    Console.WriteLine("Book: " + bk.Title + " from " + bk.Publisher.Name);
    foreach(var a in bk.Authors)
      Console.WriteLine("  author: " + a.FullName);
}

There are a few interesting things about this code. First, we directly access bk.Publisher object, and then it's Name property - the framework automatically and lazily loads the referenced Publisher object. The same happens with the list of authors - the system queries the database and delivers the list on the fly. We also use the FullName property of Author - it is a computed property, calculated on-the-fly using the GetFullName method:

C#
public static string GetFullName(IAuthor author) {
      return author.FirstName + " " + author.LastName;
}

This method was referenced in the Computed attribute attached to the FullName property in IAuthor declaration.

Using LINQ

Let's now use LINQ to find all books by publisher name:

C#
session = entityStore.OpenSession();
var msbooks = from b in session.EntitySet<IBook>()
            where b.Publisher.Name == "MS Books"
            orderby b.Title
            select b;
var msBookList = msbooks.ToList(); //execute the query
Assert.IsTrue(2 == msBookList.Count); 

Entities retrieved using LINQ are automatically attached to the session, no extra action required. You can modify entities and call session.SaveChanges() - the changes are tracked and applied automatically.

Custom queries and stored procedures

The LINQ query we just used is built, translated into SQL and then executed at runtime. This is not the way I recommended to query the database. Instead, I suggest to pre-compile the queries into stored procedures at application startup, and then use these stored procedures at runtime - VITA provides a robust way to do this. The good news is that you can do this not only for queries (SELECT statements), but also for UPDATE, DELETE, and INSERT SQL operations.

Pre-compiling custom queries is done using the Entity Modules - VITA's facility for packaging entities and related logic into components that can be "imported" into the application - more on this in later section. Now I'll just show how to pre-compile a simple UPDATE command. Let's create a command/stored procedure that changes the price of all books in a given category using a fixed factor (multiplier).

The compilation is performed in the CompileCommands method of a custom entity module BooksModule:

C#
public override void CompileCommands(IEntityCommandCompiler compiler) { ...

For a custom UPDATE, we need to create a query that returns a list of auto-types, each containing two kinds of fields: primary key to identify the record, and fields to be updated in the record with the new values.

C#
var factor = 1.1d;
var bkCat = BookCategory.Kids; //does not matter, will turn into parameter
var changePriceQuery = from bk in books
                         where bk.Category == bkCat
                         select new { Id = bk.Id, Price = bk.Price * factor };
_changePriceCommand = compiler.CompileCommand<IBook>(CommandKind.Update, Area, "BooksChangePrice", changePriceQuery);
_changePriceCommand.AddParameter("category", typeof(BookCategory));
_changePriceCommand.AddParameter("priceFactor", typeof(double));

We save the resulting command in the class-level field _changePriceCommand. Now we will be able to invoke this command/stored procedure using a wrapper c# method:

C#
public void ChangePrice(IEntitySession session, BookCategory category, double changePerc) {
  var factor = (100.0 + changePerc) / 100.0d;
  session.ExecuteNonQuery(_changePriceCommand, category, factor);
}

Here is a sample use - reducing prices on all Kids books by 20 percent:

C#
booksModule.ChangePrice(session, BookCategory.Kids, -20);

Entity validation and validation exceptions

Validation is a good example of a "feature" that is often left out of the core framework (like ORM) and left to the application to handle. However, it turns out that implementation of validation faults (and non-fatal, soft errors in general) becomes quite challenging if the core framework does not support the concept from the very beginning.

VITA implements the concept of a Non-Fatal exception - a soft error not caused by a bug but by a user mistake - as a core, built-in facility. Validation capabilities are built around this concept. The ValidationException is a container for multiple errors that can be detected by the framework or your custom code, and then thrown together to reach the UI layer and to be shown to the user. Each error contains extensive information about the fault - not only the message, but also the entity type and ID to identify the exact object that has problems, when you have more than one in a grid or a list.

VITA by default performs validation of all entities submitted for update (insert/delete) in session.SaveChanges call. It uses the meta-information derived from the entity attributes to validate each property. Additionally, you can implement a custom validation method that will be invoked automatically. The following code shows an attempt to submit an invalid book and author entities.

C#
session = entityStore.OpenSession();
var invalidAuthor = session.NewAuthor(null, 
             "VeryLoooooooooooooooooooooooooooongLastName");
var invalidBook = session.NewBook(BookEdition.EBook, BookCategory.Fiction, 
             "Not valid book", "Some invalid book", null, DateTime.Now, -5.0);
try { 
        session.SaveChanges();
} catch (ValidationException vex) { 
foreach (var err in vex.Errors) 
          Console.WriteLine(" Error: " + err.Message); 
}

If we run this code, we get 4 errors: Author's first name should not be null; Author's last name is too long; Publisher cannot be null; Price must be greater than 1 cent. The first 3 errors are found by VITA's built-in validation; the last error, price check, is added by the custom validation method specified in the Validate attribute on the IBook interface:

C#
public static void ValidateBook(EntityValidator validator, IBook book) {
      validator.Check(book.Price > 0.01, book.Price, 
         "Book price must be greater than 1 cent.", null, "PriceNegative", "Price", book);
}

What is really great about ValidationException is that it works even across the network. We will discuss later how to build RESTful Web services and clients using VITA. The validation failures, when detected on the service side, are thrown as ValidationException, then serialized a BadRequest response from the Web service, and finally are resurrected on the client as a ValidationException. So the Web layer is completely transparent for validation errors. Additionally, the way they are sent is a Web response (as a BadRequest) is compatible with REST rules.

There is another interesting case that is similar to validation failures that I would like to briefly mention. The ValidationException is a sub-class of more generic NonFatalException class, reserved for any exception that does not result from a program bug, but more likely from the user mistake. The other non-fatal sub-class is UniqueIndexViolation exception. It is thrown when user submits some duplicate that violates a unique index in the database. When this happens, you application should show some friendly message to the user "Sorry, this is a duplicate" and not treat it as serious failure, writing the exception log and sending an email to the programmer. The problem is separating this case from all other possible server errors.

When the duplicate attempt is rejected by the database server, it results in an exception thrown by the ADO.NET provider. What is really cool is that VITA catches the exception internally, detects that this is in fact about unique index violation, not some other serious SQL error, and re-throws error as UniqueIndexViolation exception. Your application code does not need to guess what happened - VITA already does it for you.

There are other cases when similar behavior is needed. One example is a concurrent update conflict with optimistic concurrency. VITA currently does not support concurrency features, but this is coming.

Component packaging technology

Admittedly, the development of data-connected applications is riddled with problems. In the area of business and LOB (Line-Of-Business) applications the problems are everywhere. The applications are overly complex, expensive, buggy and unreliable. Users hate them. Our users hate us - developers. Once the first version is built, the ongoing development of new features is an extremely painful process.

One can site several explanations for this poor state of affairs. In my opinion, the biggest problem is the fact that we failed to develop a component-packaging technology for database applications, similar to UI control libraries.

Those who remember it, the GUI technology really took off in the late 90's only after we had UI control libraries developed in OOP languages like C++ or c# that can be easily reused in any application. However, no similar packaging technology was created for cases when you have a database objects involved. Any "typical" application running over relational database contains many standard pieces like user logins, error logs, audit, authorization data, addresses, "people" tables, etc. Ideally these standard pieces could, and should have been developed independently, packaged and imported as components. That never happened. Some mini-solutions are shared only as verbal recipes in books and web forums. The trouble for packaging is the database-side objects - tables, relations, stored procedures. It is easy to package the c# code, but for database all we have is "edit this script and run it."

VITA framework provides a component packaging solution for data-connected applications. VITA implements a robust method for importing the database schema - it is automatically created from entities definition. Schema follows the c# code. So you share c# library - and database objects will follow.

The packaging containers for entities are Entity modules. Each entity module is independently developed and tested. The application is assembled from a number of modules. There is one extra - modules can be customized and integrated together when assembled in the application. Some extra customization you may need to do:

  • Extending the entities and adding some properties/columns that we need because of some custom requirements in our application
  • The entities in one module may need to reference entities in other modules. At database level, it means we want some foreign key relationships between tables coming from different modules.

VITA uses a specially designed capability of entity modules - entity replacement. Once you instantiate the entity module, you can replace some entities defined inside with different entity types - extended original types or with entities from other modules. When the VITA activation creates an integrated model, it uses the replaced entities instead of the original ones everywhere. As a result, the database will contain tables for these new entities. Additionally, the programmer who assembles the application has some control over the way the database objects are constructed. The host application has the ability to control the style and aspects of ALL tables and objects created in the database - such as naming style, conventions, etc. So the tables coming from the imported module look and feel like native to the application - because they will follow the same conventions as the rest of the tables.

For source code sample, see ModulesTest.cs file in Vita.UnitTests.VsTest project (also listed here). This is a sample of multiple entity modules integrated into one application, packed as a unit test.

Creating RESTful Web Services and Clients

VITA is not limited to ORM-like features. Most modern applications use Web as a communication protocol in one way or another. Very popular scenario is providing a RESTful API and have a Web client(s) to talk to remote server. VITA makes it really easy to implement a RESTful service and expose your entity model and its operations over entities as a RESTful API. It provides a number of classes that implement the service - all you need to do is configure it and give it your entity model - VITA does the REST, including generating help pages for your service:

Service Help Page

Figure 1: VITA Web service help page

You can even ask VITA to expose "live" URLs for GET queries on entities, so you can execute them right from the browser (obviously you should do this in development only). Here is the result of executing a GET query in the browser:

GET all books

Figure 2: GET all books results

As for the RESTful Web client, things are easy as well. You just need to create a specially configured Entity Store - the one that uses the Web-connected client as a background persistent storage. This entity store exposes the same strongly-typed entity model as the entity store connected to the database directly (with one exception - it does not support direct LINQ queries. But custom pre-compiled queries are supported over the Web). Therefore, your application code manipulating with entities is the same, whether it is running on the server or on the client. See the samples and Web client unit tests in the sources for code samples showing how this magic works.

Another great thing about Web client is that validation errors and unique index violations we discussed previously work seemlessly over the Web - they travel across the wire and are resurrected on the client as if there is no Web at all!

Multistore capabilities

There is one more feature that I want to briefly mention - see Tutorial on the project site and sample code for more details.

Modern applications seem to deviate more and more to configurations when there are multiple databases for data storage - either with the same structure in replication scenario, or with different structure when entities are split between databases. VITA supports both scenarios using a MultiStore facility - a split connector combining multiple data stores into a single one. There is even more - the databases behind the MultiStore might be even of different types. The MultiStore sample included in download shows a sample application working with SQL CE local database and several MS SQL databases on the server connected over RESTful API.

Final thoughts

Admittedly, I am not experienced framework writer - built just one before, Irony. VITA is certainly not perfect, in its current state. It is really in the very early phase of development. There are lot of things to add in the future.

But I would like to make one important point. There are 2 kind of things that are "not there":

  1. Functionality that is useful for many scenarios, for real - will be definitely coming.
  2. Functionality that nobody cares about - or at least I don't care about. These are things that appear here and there in existing frameworks, but in my opinion are not useful in the real world. These are wrong goals to target - and this stuff I don't care about.

One example of #2: implementation of class-level inheritance mapped to tables in the database. This is a classic case of chasing the wrong goal - to provide a "perfect" mapping from the OOP world of .NET classes to the set-based world of databases, to completely eliminate the "impedance mismatch". Good luck with that. The problem is that classic OOP does not really work well in business applications anyway, at least in complex real-world applications. See more in "About business logic" post in my blog.

Other than that - there are many more things to come. I just want to ask you to look closely at the basic architectural approach - entities as interfaces, with smart and flexible objects behind them. I think this arrangement opens a world of opportunities. As one hint - what about a NoSQL store behind the entities?

My troubles with existing frameworks

  • I don't like compromises - like "you can have this good thing or this other thing, but not both". Either fresh meat or fresh bread in your sandwich, but not both. Why?! Most modern frameworks force me to choose one bad thing over the other worse thing. Either ease of use or performance. It is called "using appropriate framework for your specific requirements". The result is usually a pain to use and so-so performance. What I want is something easy to use and without loss of performance compared to hand-written ADO.NET code. Period. No compromises here.
  • I make mistakes. I forget things. Especially when I refactor my code - which I do all the time. I want my tools to tell me about the mistake as soon as possible. Squigly line in the editor - perfect! Compiler error - good too. If it is something that compiler cannot do, then I want it at application startup - verify my SQLs, mappings, check for all missing or suspicious pieces! Verify everything at startup, so I don't have to go to some distant screen and try the thing to find out about mistake. Existing frameworks have a lot of problems with such early verification, especially those that use dynamically-generated SQL.
  • I don't work alone, I'm a member of the team. I want an easy way to share changes with other developers (and get theirs); quickly push to testers, analysts, users - and sharing should include binaries, database schema and even fixed data in the database.
  • I do not like to repeat myself. Especially in code. But existing frameworks seem to be no better than my five year olds - I have to repeat things many times. If I said once that a thing has a property named "Description" - why should I repeat that a column in the database is named "Description", and repeat it many times in CRUD SQLs? And then in mapping files or some "fluent" API repeat again that "Description" maps to "Description"?
  • I want to reuse things. I want to reuse more than simple utilities. I want to create pieces of functionality that cover business logic areas, test them independently and then construct the application from sizable pieces - which must be self-contained and include database objects. We have a very good technology for reusing UI elements - control libraries, but no robust way to package database-bound components.
  • I want a robust way to build long-lasting things. Any useful code lives longer than expected. It has to be maintained and extended for years, adapt to technology shifts and new business requirements. I'm not interested in "create your application in minutes!" - a prevailing theme in promotions and presentations. I don't care how long it takes to start. I'm ready to wait and to learn - provided it's worth it. The reality is usually the opposite - no wait and not worth it.

I have started VITA to finally have what I really need, without all these limitations and deficiencies.

Links

History

  • 03/18/2012 - First revision.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) Microsoft
United States United States
25 years of professional experience. .NET/c#, databases, security.
Currently Senior Security Engineer, Cloud Security, Microsoft

Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.