Entity Framework Data Access Layer

The new EFDAL is a generated project on top of Entity Framework but allow you to leverage the nHydrate model driven development platform. You get all the functionality of standard Entity Framework with some new features.

Automatic row auditing
Just like the existing NHDAL, you can add audit fields to a table automatically. These track the created and modified dates and people as well as timestamp concurrency checking of database rows. These fields are managed by the framework and there is no reason to add any code to handle these fields. Since they are maintained by the framework there is no need to add these fields to your model manually either.

Table auditing
In addition to row audits, you can define full table audits as well. When a table is marked for auditing, a shadow table is created and maintained by the framework. Each add, update and delete is logged to this shadow table. The generated API provides an interface to pull back history information from the shadow table.

Same query syntax for descendant table
EF has a strange syntax for pulling back data from an inherited table. You must query the base table and use the "OfType" keyword to access the descendant object. This means that you have to remember which tables are derived and which are not. We thought that it would be a lot simpler to query all tables the same way. So we added a piece of logic that allows you to do just this now. The following code snippet show the syntax of both models.

//EF way
var list = from x in context.Parent.OfType<Child>() select x;

//nHydrate way
var list = from x in context.Child select x;
Expression based context eager load "Include" syntax
Once of the biggest issues with EF is the magic strings that it requires you to write to eager load data. This is error prone and almost impossible to find all occurances if you change your table structure or names. Out philosophy about code is that there should be no magic strings or numbers. A develop should be able to easily see what the code does. More importantly as much code as possible should be complile-time checked. Considering the way EF does eager loads, it is just not possible to have the compiler check you loading syntax. We have overloaded the context "Include" method to take a LINQ expression instead of a string. This allows you to define in code, not strings, how to load objects. If you change a table name in your model and re-generate. The code will throw a compile-time error, not a run-time error. This is a major improvement, The code below shows the EF way to eager load and the nHydrate way.
//EF LOAD: Load the Person with which Assignments are related in one database call
var assignmentNotes = context.AssignmentNotes.Include("Person").Where(a => a.Assignment.assignmentId == 37 && a.isActive);

//nHydrate Load: Same database query, but with a lambda syntax
var assignmentNotes = context.AssignmentNote.Include(x => x.Person).Where(a => a.Assignment.assignmentId == 37 && a.isActive);
Also when loading many joins there is no need to have a very long and cumbersome syntax. The code below shows how to load 5 tables in one query.
//EF Syntax
var clientList = context.Client.
	Include("OrganizationAddress").
	Include("OrganizationAddress.Address")
	Include("OrganizationAddress.Address.Employer").
	Include("OrganizationAddress.Address.Employer.EmploymentEpisode");

//nHydrate Syntax
var clientList = context.Client.Include(x => x.OrganizationAddressList.Address.EmployerList.EmploymentEpisodeList);
Better "Add" Syntax
Standard Entity Framework create a new method for each Add method of an object. This is cumbersome in that your Intellisense now has many more stubs than is really necessary. It is also one more text block to think about when adding objects to the context.
//EFDAL - Add any type in one method
context.AddItem(item);

//Standard EF - Different method for each type
context.AddToCUSTOMER(item);
context.AddToREGION(item);
Support for lazy loading
Another complaint about Entity Framework is that there is no lazy loading. You can walk object heirarchies but you must explicitly call the "Load" method of objects as you walk. This is find if you want to explicit setup the loads. However there are people who want to walk relations and have the data load automatically. Using nHydrate allows you to do this if you wish. As you walk relations and if a table has not been eager loaded, it will be loaded when you use it.

Support for "Type" tables
We all have static tables that contain type information. This is usually user types, product types, etc. This information does not change and we use it all over out code via magic numbers and strings. nHydrate allows you to mark a table as a type table and define static data to fill it. An enumeration will be generated that allows you reference the data points instead of using the database ID (magic number). Related entities do not have a "UserTypeID" integer field for example. They would have a UserType enumeration property that you would set or check with the generated enumeration. The code below shows how to use the enumeration where normally you would make a comparison to some integer.
Parent parent = ...;
if (parent.ParentType == ParentTypeConstants.Client)
{
	...
}
Database tracking and automatic database upgrades
As always, the installer project tracks differences in model changes and generates upgrade scripts. This is great for development as models (and by extension databases) change quite frequently. There is no need to buy third-party database comparison tools simply to upgrade production databases. As you code all changes are tracked automatically. This feature has been in the NHDAL from the beginning but now you can use this functionality with the EFDAL as well.

Bulk Operations
Entity Framework does not provide a way to delete or update in bulk without loading objects into memory. Now you can delete any number of rows without loading the information in memory or creating stubs. Each entity in the generated framework has static methods on it to perform this functionality. The following code will delete all "Parent" objects that meet a condition, in this case a date check. Also you can update a field in a set of rows based on a condition as well.
//Delete
Parent.DeleteData(x=>x.CreatedDate < DateTime.Now);

//Update the 'Name' field of all 'Parent' objects to value 'John' based on same where condition above
Parent.UpdateData(x=>x.Name, x=>x.CreatedDate < DateTime.Now, "John");
Notice that all of these statements are compile-time checked. If you change a column name or table name, the code will not compile, forcing you to correct the issue before you ship it.


Aggregations
With Entity Framework you can always use the context object to perform aggregations like Min, Max, and Count; however sometimes you want a short-cut. The EFDAL provides static methods you can use to get aggregated data without explicitly declaring a context, while still using LINQ. The following examples demonstrate how to aggregate data from a generated EF object type.
//Supply predicate only
var c = Customer.GetCount(x => x.Cost > 100);

//Supply field to aggregate and predicate criteria
var n = Customer.GetMin(x=>x.Cost, x => x.CreatedDate < DateTime.Now);

Paging
Entity Framework of course allows you to page through result sets with the Skip and Take syntax but again, sometimes you might want a little easier way to perform this action. The GetPagedResults method of any generated EF object will not only page the results in one statement but also return the total pages and number of records for the search. You can use this information to build pagers on screen with pagination information.
var paging = new Paging(2, 10);
var list = context.Customer.GetPagedResults(
	x => x.CreatedDate < DateTime.Now,
	x => x.CreatedBy,
	paging);
				
Console.WriteLine("Total Records: " + paging.RecordCount);
Console.WriteLine("Toal Pages: " + paging.PageCount);

GetValue/SetValue
All objects have properties you can use to get and set properties. However there are times when you wish to dynamically address properties via the generated field enumeration. Each field of each object has an associated enumeration value that can be used to get and set values with the provided extension methods. For strings there is also another caveat. String properties will throw an error if they are set to a value greater than the max size defined in the model. This is great for getting exact run-time errors at the time of setting and not a generic errors at the time of saving. However there are times when you wish to just take the maximum allowed string and ignore the rest. The SetValue method allows you to override this safety valve as well.
The field enumeration comes in handy when saving types to a URL string or some other form of serialization. You can use the enumeration to load and save property types without having a large switch statement to map strings to real code that needs to be kept in sync with your model.
//Select a customer item and set its first name
var customer = context.Customer.First();

//Normal getter
var name = customer.FirstName;
//Get value by lambda as specified type
name = customer.GetValue<string>(x => x.FirstName, string.Empty);
//Get value with enumeration as object and cast
name = (string)customer.GetValue(Customer.FieldNameConstants.FirstName, string.Empty);
//Get value type with enumeration as specified type
name = customer.GetValue<string>(Customer.FieldNameConstants.FirstName, string.Empty);

//Normal setter, if value is too long error is thrown on set
customer.FirstName = "Chris";
//Set the value by lambda and override error handling
customer.SetValue<string>(x => x.FirstName, "Chris", true);
//Set the value with enumeration as object and override error handling
customer.SetValue(Customer.FieldNameConstants.FirstName, "Chris", true);

Select by primary key
If you need to select an object by primary key, you can do so with one line of code. There is no need to declare a context. Simply use the static method on any generated entity: SelectByPK. This method takes the primary key values as parameters and returns a single object, if it is found. Keep in mind that the context used to select the object is disposed, so you cannot walk relations nor can you make changes and persist. You may simply access the properties of the object.
var customer = Customer.SelectByPK(3);

Last edited May 19, 2012 at 2:50 PM by codetools, version 10

Comments

No comments yet.