An overview of Entity Framework

First released in 2008, EF has improved heavily during the four major releases deployed over the last 5 years. At the time of writing, the stable release is the Version 6.x and has the ability to connect to any relational database with three main connection architectures: database-first, model-first, and code-first. The first two are similar, except for the need to connect to an already-existing database or creating one with EF itself. Code-first, instead, is a completely new approach to access and maintain a database structure during application's lifetime. This relies on classes and a lot of decorator pattern (attributes) to specify physical names, constraints, and types. This design, together with the classical validation and UI-formatting oriented decorations from the DataAnnotation namespace, definitely makes it an easy-to-write persistence layer class that can flow up to the UI (MVC has great advantages due to approach) to create simple data-driven applications.

Oddly, when using super decoupled architectures such as domain-driven design (DDD), it offers great advantages when all application layers (database itself) are developed in the same way and coded in the same language (without any SQL).

An overview of Entity Framework

EF in diagram view of a (very) simple invoicing application

When using an O/RM, making a SELECT query in a database means using items from the collection that represent the table. It is quite impossible to make a one-to-one comparative between classical ADO.NET and EF because of the completely different approach in database iteration. What definitely is important to understand is that until materialized, anything is still a query when dealing with an O/RM; otherwise, a large number of performance issues may arise.

The materialization happens when we actually iterate with an enumerator by using the for-each statement, or when we flow the result in a finite collection, such as an Array or List<T> by using the LINQ extensions methods such as ToArray, ToList, ToDictionary, or anything similar. Here's an example:

using (var context = new InvoicingDBContainer())
{
    var query = context.Invoice;
    Console.WriteLine("query: {0}", query.ToString());

    var data = context.Invoice.ToArray();
    Console.WriteLine("data: {0}", data.ToString());
}

Luckily and easily, if we invoke the .ToString() method on a query, we would have the real SQL output as proof of being a simple query (similar to a command in classical ADO.NET object hierarchy). On the other hand, if we invoke the same method on the materialized data stored in an array, we obviously take the class name of the array itself. Here is an example:

query: SELECT
    [Extent1].[InvoiceID] AS [InvoiceID],
    [Extent1].[CustomerCustomerID] AS [CustomerCustomerID],
    [Extent1].[Number] AS [Number],
    [Extent1].[Date] AS [Date]
    FROM [dbo].[Invoice] AS [Extent1]
data: EntityFrameworkWorkbanch.Data.Invoice[]

In Entity Framework, the context is definitely not a simple data proxy or container. It never caches data or contains data. Its main job is to implement the Unit of Work pattern. The goal of this pattern is tracking changes to any entity (data item) within the context so that it is capable of persisting changes that have occurred in entities autonomously. The following is an example of a Unit of Work pattern:

//the unit of work context
using (var context = new InvoicingDBContainer())
{
    //an invoice
    var invoice = context.Invoice.FirstOrDefault();
    //editing the Date property
    invoice.Date = invoice.Date.AddDays(-1);
    //ask the context for persist detected changes
    context.SaveChanges();
}

//the unit of work context
using (var context = new InvoicingDBContainer())
{
    //add a new invoice
    context.Invoice.Add(new Data.Invoice
        {
            Date = DateTime.Now.Date,
            Number = "NUMBER",
            CustomerCustomerID = 1,
        });
    //ask the context for persist detected changes
    context.SaveChanges();
}

//the unit of work context
using (var context = new InvoicingDBContainer())
{
    //an invoice
    var invoice = context.Invoice.FirstOrDefault();
    //register for removal
    context.Invoice.Remove(invoice);
    //ask the context for persist detected changes
    context.SaveChanges();
}

The bigger difference when using those two frameworks is that when we use connected ADO.NET, we deal with a remote server speaking SQL language, while when we use EF, we work on objects that map data. This means that if we need to edit any data, we have to deal with the local object states, not with the remote server itself.

Advanced querying

The best of EF is seen when querying data. Although the comfort of editing data with objects definitely offers some great advantages in terms of data quality and issue prevention, data querying with EF and LINQ raises the level of quality and the maximum level of complexity of such queries, without actually increasing the complexity in code. This is because of the great power of querying with LINQ.

A big advantage when working with objects is the ability to produce a dynamic object-oriented query with all related benefits, such as type safe values, constraint validation, and so on.

using (var context = new InvoicingDBContainer())
{
    //a simple user filter
    string filter = null;

    //base query
    var query = context.Invoice
        .Where(x => x.Date >= new DateTime(2015, 1, 1))
        .Where(x => string.IsNullOrEmpty(filter) || x.Number.Contains(filter));

    if (true)//some dynamic logic
        query = query.OrderBy(x => x.Date);
    else
        query = query.OrderByDescending(x => x.Date);

    //query materialization
    var data = query.ToArray();
}

The same example executed in connected ADO.NET would need concatenated SQL strings with obvious increased attack surface for SQL injection and a general reduction in design quality of the whole code.

Another great advantage when using LINQ to Entities (LINQ when applied to an EF data provider) as a querying language is the ability to easily make a nested query with full support from IntelliSense, and the auto-completion of Visual Studio with the full list of querying features of LINQ, such as aggregations, computations, interactions, filters, and so on. Here's an example on advanced querying:

using (var context = new InvoicingDBContainer())
{
    string filter = null;

    var query = context.Invoice
        //an hard-coded filter
        .Where(x => x.Date >= new DateTime(2015, 1, 1))
        //a dynamic filter from user
        .Where(x => string.IsNullOrEmpty(filter) || x.Number.Contains(filter))
        //it is time to shape data with needed aggregations
        .Select(x => new //new anonymous type
        {
            CustomerID = x.CustomerCustomerID,
            x.Number,
            x.Date,
            //InvoiceElement is the navigation property to
            //the InvoiceElement table containing invoice elements
            //the navigation makes associated table data available like if we had done a JOIN on the child table
            ElementCount = x.InvoiceElement.Count(),
            BaseAmount = x.InvoiceElement.Sum(e => e.Amount * e.UnitPrice),
            VATAmount = x.InvoiceElement.Sum(e => e.Amount * e.UnitPrice * (e.VatMultiplier - 1d)),
            //instead of writing here the TotalAmount duplicating calculations we will write it in next step
        })
        .Select(x => new
        {
            x.CustomerID,
            x.Number,
            x.Date,
            x.ElementCount,
            x.BaseAmount,
            x.VATAmount,
            TotalAmount = x.BaseAmount + x.VATAmount,
        })
        .OrderBy(x => x.Date)
        .ThenBy(x => x.Number);

    var data = query.ToArray();
}

This ability to create new objects within any query is called query shaping. By using anonymous types, we can create as many query levels as we need to achieve our result with object querying. Never use old interaction logic (for/for-each on locally materialized objects) with EF because it has no pros; it only seems too familiar to someone who is used to LINQ to entities queries.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.12.207