Purpose and Principles of the Data Layer

At its core, the Data Layer’s main purpose is:

Data Layer’s Main Purpose:

To abstract all interactions with the database so that business objects can be written to deal with business rules, not with database interaction.

For example, when promoting a standard Deal, the business logic comprises:

  1. validate that the Deal is in a promotable state
  2. promote the Deal
  3. generate the related Deal Transactions
  4. generate the related Confirmation

In addition to these items, there are interactions with the Deal Pricing, Deal Costing, Deal Transaction Pricing and a variety of other lookup tables. Records need to be saved, retrieved, updated and deleted to facilitate the persistence of the Deal’s new “Promoted” state. The Data Layer will encapsulate the code that interacts with the database, so the Deal Promoter class only needs to be concerned with the business rules of promoting a deal, and not with the mechanics of persisting those changes to the database.

ORM Principles

In order to effectively serve as an Object-Relational Mapping (ORM) tool, the Data Layer needs to implement the following principles.

Principle 1: The Data Layer should generate the code necessary to deal with different tables and should provide a common API for working with the resulting data objects.

A typical database interaction involves the following steps:

  1. Get the connection string
  2. Open the connection
  3. Create the command object
  4. Execute the command
  5. Close the connection

The only thing that changes from table to table is the names of the table and its columns. All of the database code is identical.

Principle 2: The Data Layer should be able to work with the entire Object Graph by saving and retrieving related objects as a set.

Some objects are more complex than others, for example, a Deal has Pricing, Charges and Transactions that are an integral part of it. It also has Confirmations that are related to it. When saving a Deal, the pieces and parts of the Deal should get saved too.

Principle 3: The Data Layer should handle failures within the context of a transaction and roll back the changes to a consistent, stable state.

Sometimes, when saving a complex object, an error may occur in one of the pieces. In this case, the Data Layer should gracefully handle the error and leave the object and the database in a stable, consistent state.

Principle 4: The Data Layer should intelligently map database tables to appropriate Business Objects.

In several cases, a business object will represent a concept differently than the database might. For example, the database table EMPLOYEE contains all the records for the Employees, Managers and Direct Reports business classes.

Principle 5: The Data Layer should handle concurrency properly.

When objects are saved to the database, concurrency problems arise because the data being saved is about to overwrite data that has already changed since it was last retrieved. Concurrency resolutions include: Overwrite, Merge and Discard. The Data Layer needs to support these options and allow developers to choose which resolution to employ.

Query Support

Principle 6: The Data Layer should support LINQ.

In order to provide data sources for drop downs and grids the Data Layer needs to be able to support querying, including sorting, grouping and summarizing. There are three choices to do this:

1) Oracle native SQL queries

Implementing this technique often pushes Business and UI logic all the way back into the database. It makes ORM a challenge as the query objects are not like table-based as they are not updateable and do not usually have the necessary key fields.

2) Custom querying support in the Data Layer

Implementing this technique is complex, non-standard and may have performance issues.

3) LINQ (The .Net framework’s built in query language)

Implementing LINQ provides powerful, sophisticated, query capabilities. LINQ to Entities also raises performance by taking advantage of the Entity Framework’s knowledge of the database objects.

Principle 7: The Data Layer should support asynchronous communication.

One of the worst aspects of application performance is the perceived lag while waiting for data to be retrieved from a database, transferred over the network and rendered in the UI. Asynchronous communication is the recommended way to prevent this lag by allowing the UI to be responsive while the data is retrieved, transferred, and even rendered, asynchronously. In Silverlight, all network communication is asynchronous, so the Data Layer must support asynchronous communication.

Oracle Support

Principle 8: The Data Layer should support Oracle specific features, such as Sequences, Packages and Oracle Data Types.

Most Oracle tables have a key field that is a numeric tied to a Sequence value. Much of the legacy code is embedded in the Database in Oracle Packages. There are also some Oracle specific Data Types (particularly LOB’s) that need to be translated to/from their .Net equivalents. The Data Layer needs to handle all three of these situations properly.

Trouble Shooting Support

Principle 9: The Data Layer should support granular logging for debugging and troubleshooting.

When debugging and troubleshooting, a detailed log of what is happening can be a very useful tool. Especially in asynchronous or Inversion of Control situations where the code cannot be easily stepped through, a log is critical to the discovery and elimination of bugs.

Performance Enhancement

Principle 10: The Data Layer should support server-side and client-side caching to improve performance.

Data Caching on the Server-side allows redundant calls for data from different clients to be served in a single database request. Data Caching on the Client-side allows redundant calls for data on the client to be served on the client without any network traffic at all.

Principle 11: The Data Layer should support validation at the client and at the server to improve performance.

Eliminating round trips by providing client-side validation will improve performance. Providing server-side validation will ensure data integrity at the server.

Moving Code from the Developer’s Integration Environment to the QA Environment

When we’ve completed coding (and, if we’re feeling especially adventurous, testing said code), we promote our locally developed code to the Dev server. This serves as our integration environment. CruiseControl.Net watches our SCM for any changes promoted to the DEV branch, then it:

  1. Updates the configuration files to point to the Dev server instead of the developer’s local workstation;
  2. Builds our solution in Debug mode (for more verbose logging during the unit test runs);
  3. Runs the unit tests;
  4. Builds the solution again in Release mode;
  5. Runs the unit tests again to ensure they work in Release mode too; and
  6. Finally, publishes the necessary files to IIS.

Today, I need to figure out how much has to change to get this project onto our QA server.

The Problem

Pointing the QA environment to the QA database.

The biggest difference between the process described above and the process to move to QA is going to be changing the back end database that the solution points to. I.e. The Developer’s local workstation and the Dev server both point to the Dev database. (It’s an Oracle back end, so we do not have a separate local and server database — even if we wanted to, but I’m not sure we would.)

ORACLISM

An aside about Oracle schema ownership. As a SQL Server developer, there are several Oraclisms that confound me. I’m not sure that this is one of them, but here’s my take on it: In SQL Server, most databases are created by dbo and most tables are created as dbo. Security is then applied to users and groups to grant access to various tables. If you need to create a Dev database and a QA database for the Production database named AccountingSystemDB, you might create a new database, again as dbo, called AccountingSystemDEV and another called AccountingSystemQA. In Oracle, this notion of separate databases is superseded by a notion of separate schemas WITHIN a single database. There is no obvious corollary to dbo. The Oracle system user serves a very different purpose, perhaps relating more to the notion of sa in SQL Server. Instead, an Oracle DBA will create a Schema Owner (i.e. a user?) and then create the Schema as that owner, granting rights to that Schema to users.

For example, the Oracle Server has a single Database instance, within which, you will see three schemas named ACC_SYS_DV, ACC_SYS_QA and ACC_SYS_P. Within each schema, you will find that schema’s version of the tables. I.e. ACC_SYS_DV.ACCOUNT, ACC_SYS_QA.ACCOUNT and ACC_SYS_P.ACCOUNT. Also, a lot of database apps, use a single Application User Id to access the database, relying on the Application to implement security. (Or at least, ours does.) For each of the three environments, this Application User logs in to one of the three Schemas.

I am concerned that Entity Framework knows the Database and Schema in the connectionstring in the app.config for the ModelEF project:

 ...
 <edmKeys>
  <edmKey
   name="DataSource"
   connection="metadata=res://ModelEF/ModelEF.csdl| res://ModelEF/ModelEF.ssdl| res://ModelEF/ModelEF.msl; provider=Devart.Data.Oracle; provider connection string=" User Id=APPDV; Password=*****; Server=devTns.world; Home=oraodac11g_home1;Persist Security Info=True""
   containerName="ModelEF.Entities">
   ...
  </edmKey>
  ...
 </edmKeys>
</ideablade.configuration>

Above we see the Oracle server instance devTns.world, and the Dev database schema owner APPDV. There is also a Home= entry pointing to the developer’s OraHome that needs to be removed.

EF then maps the Schema owner into the EDML (EDMX) file:

...<EntitySet
  Name="ACCOUNTs"
  EntityType="ModelEF.Store.ACCOUNT"
  store:Type="Tables"
  Schema="APPDV" <=NOTICE DV SCHEMA OWNER IS HERE
  Table="ACCOUNT" />...

If this was a SQL Server solution, the schema owner would be the same (whether it is dbo or not) in the AccountingSystemDEV and AccountingSystemQA databases. In Oracle, this is not the case, so I am curious if I will need to update the Schema tag in the EDML (EDMX) . If so, the promotion to QA will force a rebuild, not just a reconfigure.

The Ideablade configuration also knows the Database and Schema in the connection attribute of the edmKey tag in the Model project’s app.config and in the web host project’s web.config:

<configuration>
 <connectionStrings>
  <add name="Model.EFConnectionString"
   connectionString="metadata=res://*/ModelEF.csdl| res://*/ModelEF.ssdl| res://*/ModelEF.msl; provider=Devart.Data.Oracle; provider connection string=&quot; User Id=APPDV; Password=*****; Server=devTns.world; Home=oraodac11g_home1; Persist Security Info=True&quot;"
   providerName="System.Data.EntityClient" />
 </connectionStrings>
</configuration>

[It turns out, I do need to change the tags, change the connection strings, and regenerate the pregenerated views.]

A SOLUTION

Since we are using CruiseControl.Net (CCNet) for our automated build, and since we are using Nant for some of the less “build-ish” and more “configure-ish” tasks, I was able to add a Nant task in the CCNet project configuration that does a string replace in all the EDML (EDMX) and .config files, and then uses the T4 Template technique to recreate the Pregenerated Views.

In order to use the T4 Template attached to the ADO .Net blog, I had to make two changes because we are using Devart Entity Developer. First, the .tt file needs to be renamed from ModelEF.Views.tt to ModelEF.PregeneratedViews.tt. Second, the following line in the .tt file has to be changed:

string edmxFileName = Path.GetFileNameWithoutExtension(this.Host.TemplateFile).ToLowerInvariant().Replace(".views", "") + ".edmx";

Should be replaced with:

string edmxFileName = Path.GetFileNameWithoutExtension(this.Host.TemplateFile).ToLowerInvariant().Replace(".pregeneratedviews", "") + ".edml";

Here is the CCnet task in the CruiseControl.Net ccnet.config file to execute the Nant task:

<project name="PromoteToQA">
 ...
 <tasks>
  <nant>
   <buildFile>ChangesBeforeBuild-QA.build</buildFile>
   <executable>E:\Program Files\nant-0.86-beta1\bin\nant.exe</executable>
  </nant>
 </tasks>
 ...
</project>

And here is the Nant task itself:

<project name="ChangesBeforeBuild-QA" default="run">

 <target name="run">
  <call target="change Database" />
 </target> 

 <target name="change Database">
  <foreach item="File" property="filename">
   <in>
    <items>
     <include name="**/*.edml" />
     <include name="**/app.config" />
     <include name="**/web.config" />
     <include name="**/*EF.Designer.cs" />
     <include name="**/*EF.edps" />
    </items>
   </in>
   <do>
    <attrib file="${filename}" normal="true" />
    <loadfile file="${filename}"
     property="file.textContent">
     <filterchain>
      <replacestring from="APPDV" to="APPQA" ignorecase="true"/>
      <replacestring from="*****" to="*****" />
      <replacestring from="devTns.world" to="qaTns.world" ignorecase="true"/>
      <replacestring from="Home=oraodac11g_home1;" to="" />
     </filterchain>
    </loadfile>
    <echo message="Writing Database Change to: ${filename}" />
    <echo file="${filename}">${file.textContent}</echo>
   </do>
  </foreach>
 </target>
 
 <target name="Recreate Precompiled Views Files">
  <!-- unlock the view files -->
  <foreach item="File" property="filename">
   <in>
    <items>
     <include name="**/*.PregeneratedViews.cs" />
    </items>
   </in>
   <do>
    <attrib file="${filename}" normal="true" />
   </do>
  </foreach>
  <!-- transform the *.PregeneratedViews.tt files to recreate the *.PregeneratedViews.cs files -->
  <foreach item="File" property="filename">
   <in>
    <items>
     <include name="**/*.PregeneratedViews.tt" />
    </items>
   </in>
   <do>
    <echo message="Transforming: ${filename}" />
    <exec program="dependencies\TextTransform\TextTransform.exe">
     <arg value="${filename}"/>
    </exec>
   </do>
  </foreach>
 </target>
 
</project>

And that’s it. Before CruiseControl.net starts running the actual tasks, it runs this Nant task to prepare the source files for the QA environment.

Why does he keep saying EDML instead of EDMX?

In the Microsoft Entity Framework (EF), the GUI for the Entity Model is called the Entity Designer.  When you create an Entity Model, the model itself is stored in an XML file named model.EDMX.

The EDMX file is: 

a) an XML representation of the Entities and Relationships in your Database (the Storage Model, or SSDL), your application (the Conceptual Model, or CSDL), and the mapping from one to the other (the  Mapping Specification, or MSL); and

b) a repository for the metadata that defines the Entity Designer Diagram layout.

And this works out just fine for SQL Server.  Microsoft provides a nice little driver that is EF compatible, they provide a nice little GUI to create your Model with, and SQL Server tables — if named “normally”, i.e. Customer, Account, etc. — map beautifully to your object model.

This is NOT the case for Oracle.  Oracle still holds its nose whenever anyone says “Microsoft”, so they haven’t, and won’t, provide a driver for EF.  Heaven forbid they should make it easy to use Visual Studio and the .Net framework to work with their data.  Nope — Java, Eclipse, and the Oracle Application Framework are their preferred toolset.

So what is a .Net developer to do?  How can one create an EF model from an Oracle database?

ANSWER:  DevArt

DevArt has gone to the trouble of creating ADO.Net drivers for a variety of databases, including SQL Server and, da-da-da-da, Oracle.

I have been using DevArt’s dotConnect for Oracle drivers for EF purposes for about a year now.  In the early days, they were quite quirky.  I posted to their forums regularly.  Every single problem, suggestion, requirement or wish list item that I posted has been implemented, usually in their next release.  (They post quiet releases every 4 to 6 weeks.  Yay for Agile Development.)  Their latest version (5.35.79 as of this writing) works flawlessly for my purposes.

Anyway, included with their dotConnect drivers, is the DevArt Entity Developer.  This tool ROCKS!  Over the past year, it has matured to the point that I have completely abandoned using the MS Entity Designer for anything.  Instead, I create  and maintain my Entity Model’s using the DevArt Entity Developer. 

Anyway, in order to not step on anyone’s toes, DevArt saves the Entity Model as an EDML file, not an EDMX file.  (Yes, this is the “exact same” Entity Model XML that MS saves.)  They also store a bunch of metadata and project selections in a set of generated files behind the EDML file.  And, as of December, you can select to have the Entity Developer automatically generate the precompiled views file too.  (MS doesn’t do that and provides a 30-step process to accomplish it — A problem that has spawned many web posts.)

One of my favorite features of the DevArt Entity Developer over the MS one, has to do with an Oraclism that just bugs me. 

ORACLISM
Oracle DBA’s, (by tradition, by culture, or by requirement, I’m not sure which), often name database objects in all caps with underscore delimiters.  For example, a table named CustomerAccountType in SQL Server, will probably be named CUST_ACCT_TYP in Oracle.  (There’s an unreasonably low limit to how long the name can be in Oracle, so the DBAs often have to truncate or compress names to make them fit.) 

So, if you have a table defined thus:

CREATE TABLE  CUST_ACCT_TYP (
CUST_ACCT_TYP_ID NUMBER(10) NOT NULL,
TYPE_DESCRIPTION VARCHAR(20) NOT NULL)

Now, you drag and drop that table into the MS Entity Designer*, you will get an object defined thus:

Class:     CUST_ACCT_TYP
Property:  CUST_ACCT_TYP_ID, Double
Property:  TYPE_DESCRIPTION, String

Not a very “.Net-ish” way of defining a class, is it?  Also notice the NUMBER(10), an integral number type, is mapped to a Double.

DevArt’s Entity Developer, however, does a grand job of converting all those names, so you end up with an object defined thus:

Class:     CustAcctTyp
Property:  CustAcctTypId, Int64
Property:  TypeDescription, String

Notice how it even got the data type right for the Id. 

Now, if only I could UPDATE my model (instead of recreating the affected parts of it) when I change the Tables and Views in Oracle… 🙂

* By the way, you still need the DevArt drivers to do  this.