The Database Context

This post is part of a series on learning how to use Entity Framework. The rest of the posts in the series are linked below.

Basics of Entity Framework

Code First

Database First


DbContext is probably the single most important class in the EF API, as it directly represents a connection with the database from the application. A minimal context class inherits from DbContext and exposes DbSet instances for each entity type which has to be stored or retrieved from the database.

Querying for information retrieval and storage is done through the DbContext instance. If the developer has not overridden any configuration settings, Entity Framework attempts to connect to a database with the fully qualified name of the DbContext class itself.

namespace Notadesigner.Blog
{
    public class ContentContext : DbContext
    {
        public ContentContext()
        {
        }
    }
}

In the above example, Entity Framework automatically tries to connect to a database called Notadesigner.Blog.ContentContext. This can be overridden by setting the configuration in various ways.

namespace Notadesigner.Blog
{
    public class ContentContext : DbContext
    {
        public ContentContext("Notadesigner")
        {
        }
    }
}

Other ways to override the default connection name is to add a connection string in the application configuration file and supplying it as a parameter to the DbContext constructor.

Operating Entity Framework

This post is part of a series on learning how to use Entity Framework. The rest of the posts in the series are linked below.

Basics of Entity Framework

Code First

Database First


Define the Model

Entity Framework begins be preparing an in-memory model of the entity objects and their relationships, and the mapping of this model to the storage model. A default configuration can be derived by using conventional names for entities and their properties. Programmers can achieve finer control for exceptional cases by overriding configuration settings through various mechanisms.

If the database does not exist, or the configuration directs automatic modification or recreation of the database, then the database is modified or created to match the fields in the conceptual model.

Data Creation, Retrieval, Modification & Storage

An entity is instantiated from its defining class and added to the database context through the object service. Querying to retrieve records is also done through the same layer. Any modifications to the data are performed on the entity instances, followed by a call to the SaveChanges API to commit them to the database.

Components of Entity Framework

This post is part of a series on learning how to use Entity Framework. The rest of the posts in the series are linked below.

Basics of Entity Framework

Code First

Database First


Like most well-designed APIs, Entity Framework is composed of several discrete components which complement each other. There are three high-level components – the Entity Data Model, the Querying API and the Persistence API.

Collectively, these components allow application programmers to operate upon classes which are specific to their business domain rather than indulging in the low-level details of ADO.NET and SQL.

Entity Data Model

Conceptual Model

This layer of the data access layer is built by the application programmer. It contains class representations of the domain model for whatever business requirement that the application aims to achieve. The programmer identifies the objects which make up the given domain, then implements them as classes. The classes have public properties that correspond to properties of the domain object, and are also converted into database columns by the Storage Model. At runtime, these classes are instantiated as CLR objects and their properties are populated with values from corresponding database columns.

Storage Model

The database and its various elements – the tables, views, stored procedures, indexes and keys – make up this layer. This is a relational database such as SQL Server or PostgreSQL, although future versions of the Entity Framework are said to support NoSQL databases.

Mappings

Object-oriented programming can be mapped to relational schemas quite closely in most standard scenarios. A class can easily have the same properties as a table column and use equivalent data types to represent it in memory at runtime. Object references are represented as relationships between two tables with all accompanying aspects such as defining keys between tables.

Data constraints and default values can also be declared in this layer by using the appropriate configuration APIs. Any mismatch between the objects, tables and mapping results in a runtime exception which can be handled by the programmer.

Querying API

There are two APIs that programmers can use to interact with the database from Entity Framework.

LINQ to Entities

This is a query language that retrieves data from the storage model, and with the aid of mappings, converts it into object instances from the conceptual model.

Entity SQL

This is a dialect of SQL which operates upon conceptual models instead of relational tables. It is independent of the underlying SQL engine, and as a result, can be used unchanged between various database engines.

Both querying languages are operational through the Entity Client data provider, which manages connections, translates queries into data source-specific SQL syntax, and returns a data reader with records for conversion into entity instances. By dint of being an abstraction over the connection, Entity Client can also be used to breach the Entity Framework abstraction and interact with the database directly with ADO.NET APIs.

Persistence API

Object Service

The programmer interacts with the object service to access information from the database. It is the primary actor in the process of converting the records retrieved from the data provider into an entity object instance.

Entity Client Data Provider

The Entity Client Data Provider works the other way around, to translate queries written in LINQ-to-Entity into SQL queries that the database understands. This layer interacts with the ADO.NET data provider to fetch from or send data into the database.

ADO.NET Data Provider

This is the standard data access framework from Microsoft. Entity Framework is an entity-level abstraction over the APIs provided by this library.

Introduction to ORM & Entity Framework

This post is part of a series on learning how to use Entity Framework. The rest of the posts in the series are linked below.

Basics of Entity Framework

Code First

Database First


Having application programmers design, implement and maintain databases has often been a challenge. Maintainability and performance have taken a hit, resulting in less than stellar application performance and poor extensibility.

Object Relational Mapping libraries have been written since a long time to gloss over lack of proficiency in SQL and database design. However, they too have been cited as a major bottleneck to performance due to abstracting over the underlying relational model, and generating bloated and inefficient queries.

Newer generation ORM tools have solved performance and design problems to a large extent, resulting in frameworks which developers can use with confidence even for large-scale and high-availability applications.

What is Entity Framework?

Entity Framework is Microsoft’s offering in this space for use with the .NET framework. It is built upon the traditional ADO.NET framework and therefore, can be made to work with almost any relational database with bindings for the .NET framework.

Entity Framework is an open source ORM library which is published by Microsoft. It complements the .NET framework and is built upon the ADO.NET framework.

Entity Framework frees up the programmer from having to translate information between .NET instances and DataSet objects which are used by ADO.NET to retrieve, insert and update records in the data store.

Practical Design Patterns in C# – State

The purpose of the state design pattern is to allow an object to alter its behaviour when its internal state changes.

The example of a logging function below is a model candidate for conversion into a state implementation.

The requirements of this function are as follows.

  1. Write the log entry to the end of a text file by default.
  2. The file size is unlimited by default, denoted by setting the value of the maximum file size to 0.
  3. If the value of the maximum file size is greater than 0 then the log file is archived when it reaches this size, and the entry is written to a new empty log file.
  4. If the recycle attribute is enabled and the maximum file size is set to a value greater than 0, then the oldest entry in the log file is erased to make space for the new entry instead of archiving the entire log file.

The implementation of the actual mechanism for writing to disk is not relevant in this example. Rather, the focus is on creating a framework that enables selecting the correct mechanism based on the preferences set.

public void Log(string message)
{
    if (LimitSize)
    {
        if (file.Length < MAX_SIZE)
        {
            // Write to the end of the log file
        }
        else
        {
            if (Recycle)
            {
                // Clear room at the top of the log file
            }
            else
            {
                // Create a new log file with current time stamp
            }

            // Write to the end of the log file
        }
    }
    else
    {
        // Write to the end of the log file
    }
}

The implementation shown above is very naïve, tightly coupled, rigid and brittle. However, it is also a fairly common occurrence in most code bases due to its straightforward approach to addressing the problem.

The complicated logical structure is difficult to decipher, debug and extend. The tasks of determining file size limits, recovering disk space, creating a new file and writing to the file are all discrete from each other and should have no dependency between themselves. But they are all interwoven in this approach and add a huge cognitive overhead in order to understand the execution path.

There are multiple conditions being evaluated before the log entry is made. The programmer has to simulate the computer’s own execution path for each statement before even arriving at the important lines related to writing to disk. A couple of blocks are duplicated code. It would be nice if the execution path could be streamlined and duplicate code removed.

Restructured Code

An eager approach to selecting the preferred mechanism breaks up the conditional statements into manageable chunks. It is logically no different from having the same code in the Log method. But moving it into the mutator makes it easier to understand by adding context into the logic rather than handing it all in a single giant function.

public int MaxFileSize
{
    get
    {
        return _maxFileSize;
    }

    set
    {
        _maxFileSize = value;

        if (0 == MaxFileSize)
        {
            // Activate the unlimited logger
            return;
        }

        if (Recycle)
        {
            // Activate the recycling logger
        }
        else
        {
            // Activate the rotating logger
        }
    }
}

public bool Recycle
{
    get
    {
        return _recycle;
    }

    set
    {
        _recycle = value;

        if (0 == MaxFileSize)
        {
            // Recycling is not applicable when file sizes are unlimited
            return;
        }

        if (Recycle)
        {
            // Activate the recycling logger
        }
        else
        {
            // Activate the rotating logger
        }
    }
}

The comments indicate that you’re still missing some code that actually performs the operation. This is the spot where the behaviour is selected and applied based on the state of the object. We use C# delegates to alter the behaviour of the object at runtime.

public delegate void LogDelegate(Level level, string statement)

public LogDelegate Log
{
    get;
    private set;
}

The Logger class contains private methods that implement the same signature as the Log delegate.

private void AppendAndLog(Level level, string message)
{
}

private void RotateAndLog(Level level, string message)
{
}

private void RecycleAndLog(Level level, string message)
{
}

When the condition evaluation is completed and the logging method has to be selected, a new instance of the Log delegate is created which points to the correct logging method.

if (Recycle)
{
    Log = new LogDelegate(RecycleAndLog);
}
else
{
    Log = new LogDelegate(RotateAndLog);
}

This separates the selection of the correct logging technique to use from the implementation of the technique itself. The logging method implementations can be changed at will to fix bugs or extend their features without adding risk to breaking the rest of the code.