Chapter 5. Advanced Modeling and Querying Techniques

In this chapter, you will learn how to use advanced modeling techniques to create the database structure. We will learn how to use complex types to create data structures that are reusable in multiple entity types. We will learn how to use enumerations to create a range of distinct values for a column or property. We will understand how to split an entity across multiple tables. We will learn how to support existing databases, while using names for classes and properties that do not match tables and columns in our database. We will also look at additional querying techniques, including aggregation, paging, grouping, and projections.

In this chapter, we will cover how to:

  • Create complex types, reusable in many entities
  • Define an enumeration and use it in a query
  • Create an entity that is stored in multiple tables
  • Use explicit column and table names in entity to table mappings
  • Create queries that use projections with anonymous and explicit types
  • Summarize data, using aggregate functions
  • Create windowed queries
  • Use explicit joins in queries
  • Use set operations

Advanced modeling techniques

So far, we have covered many straightforward scenarios that one can easily model with Entity Framework to create database structures. All of them are mapped to one table with scalar values to a class with a matching set of properties. There are use cases when this approach does not work quite as well, and we will walk through a functionality in Entity Framework that supports more complex modeling techniques.

Complex types

Complex types are classes that map to a subset of columns in a table in the database. They are similar to entity classes, except that they do not contain key fields and do not directly map to an entire table. Complex types are helpful when we have the same set of properties that are common to multiple entities. Another use case is when we want to group some properties, in order to provide a clear semantic meaning to such a group of properties. By introducing a complex type into our modeling workflow, we provide more consistency for database structures across multiple tables. This occurs because we define the common attributes for such tables in a single place, which will be our complex type. The prototypical example is address fields. Given the examples we have seen in prior chapters, let's add addresses to both person and company classes. Here is how the address class, referred to as complex type, looks:

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

We are looking at a simple class here with a set of properties that define an address. Here is the same code in VB.NET:

Public Class Address
    Public Property Street() As String
    Public Property City() As String
    Public Property State() As String
    Public Property Zip() As String
End Class

The second step is to make this class part of a larger picture by introducing the Address type property in both the Company and Person classes. Here is, for example, how the Company class looks after this change:

public class Company
{
    public Company()
    {
        Persons = new HashSet<Person>();
        Address = new Address();
    }
    public int CompanyId { get; set; }
    public string CompanyName { get; set; }
    public Address Address { get; set; }
    public ICollection<Person> Persons { get; set; }
}

There is an important step we need to take now. We need to initialize an instance of the Address class in the Company class's constructor. Without this simple step, we can easily encounter a null reference exception any time we create a new instance of Company and try to set a street on the address. When data is retrieved from the database via a query, Entity Framework automatically initializes the instance of the Address class during the materialization process. An additional initialization that we just added manually exists to cover the creation of new entity scenarios. Here is how the code looks in VB.NET:

Public Class Company
    Public Sub New()
        Persons = New HashSet(Of Person)
        Address = new Address()
    End Sub
    Property CompanyId() As Integer
    Property CompanyName() As String
    Property Address() As Address
    Overridable Property Persons() As ICollection(Of Person)
End Class

The next step is to provide the configuration for our complex type. We can do so in a way that is virtually identical to entity classes, by providing a configuration class for the complex type. The only difference is that we use a different base class, ComplexTypeConfiguration, not EntityTypeConfiguration. The code inside this configuration class is identical to the code in entity configuration classes, using the exact same property configuration methods.

For example, consider this code snippet:

public class AddressMap : ComplexTypeConfiguration<Address>
{
    public AddressMap()
    {
        Property(p => p.Street)
            .HasMaxLength(40)
            .IsRequired();
        
    }
}

The preceding example only shows one property being configured, but in the code provided with this book, all properties are configured in the same fashion as the Street property. Finally, we must remember to add an instance of AddressMap to the collection of configurations of the context. For example, consider this code snippet:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new CompanyMap());
    modelBuilder.Configurations.Add(new AddressMap());
}

Here is how the same code looks in VB.NET:

Public Class AddressMap
    Inherits ComplexTypeConfiguration(Of Address)
    Public Sub New()
        Me.Property(Function(p)p.Street).HasMaxLength(40).IsRequired()
    End Sub
End Class

Protected Overrides Sub OnModelCreating(ByVal modelBuilder As DbModelBuilder)
    modelBuilder.Configurations.Add(New CompanyMap)
    modelBuilder.Configurations.Add(New AddressMap)
End Sub

If we were to run this code and look at the created database structure, we would see that the Address columns names in the Company table are prefixed with the complex type's name. So, a column to store the name of the street is called Address_Street. This is typically not something that we want, which leads us to the next discussion about supporting explicit column and table names.

Using an explicit table and column mappings

There are many use cases that require us to explicitly specify a column or a table name. We just saw one, but there are more. For example, we can add Entity Framework on top of an existing database that uses a naming convention developers of data access layer do not like. Explicit names solve this problem.

In order to specify a column name that is different from a matching property name, we can use the HasColumnName method available for primitive property configurations, as shown in the following code snippet:

public class AddressMap : ComplexTypeConfiguration<Address>
{
    public AddressMap()
    {
        Property(p => p.Street)
            .HasMaxLength(40)
            .IsRequired()
            .HasColumnName("Street");

One can configure properties on entity types in the exact same way we just configured our complex type. We can see more examples in the code that accompanies this book. Here is how this looks in VB.NET:

Public Class AddressMap
    Inherits ComplexTypeConfiguration(Of Address)
    Public Sub New()
        Me.Property(Function(p) p.Street) _ 
            .HasMaxLength(40) _ 
            .IsRequired() _ 
            .HasColumnName("Street")

In order to specify the table name for an entity, we have to use the ToTable method of the EntityTypeConfiguration class. For example, here is how we can specify the table name for a person type entity:

public class PersonTypeMap : EntityTypeConfiguration<PersonType>
{
    public PersonTypeMap()
    {
        ToTable("TypeOfPerson");

This example is a bit contrived, as we could have just as easily changed the class name. Here is how we can specify a name for a table in VB.NET:

Public Class PersonTypeMap
    Inherits EntityTypeConfiguration(Of PersonType)
    Public Sub New()
        ToTable("TypeOfPerson")

Adding supporting columns

In addition to changing column names, sometimes we want to add a property to an entity that we do not want to store in the database. In other words, we want to add some business logic into an entity to help us work with it outside of Entity Framework. I am by no means advocating embedding business logic inside our entity classes, but merely providing an alternative to computed columns in SQL Server. For example, let's add the FullName property to our Person class and return a concatenation of LastName and FirstName:

public string FullName
{
    get
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
    set
    {
        var names = value.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
        FirstName = names[0];
        LastName = names[1];
    }
}

Here is how the same property looks in VB.NET:

Public Property FullName() As String
    Get
        Return String.Format("{0} {1}", FirstName, LastName)
    End Get
    Set(value As String)
        Dim names = value.Split(New String() {" "}, StringSplitOptions.RemoveEmptyEntries)
        FirstName = names(0)
        LastName = names(1)
    End Set
End Property

If we run this code, we will see that a new column called FullName was added to the People table. This is not what we want; there is no reason for us to persist the full name. To fix the problem, we just need to use the Ignore method of the EntityTypeConfiguration class. This is shown in the following example:

public class PersonMap : EntityTypeConfiguration<Person>
{
    public PersonMap()
    {
        Ignore(p => p.FullName);

This approach of ignoring certain properties in a persistence layer could prove useful when developers are dealing with legacy databases. One thing we must remember is that we cannot query based on ignored properties, since they do not exist in the backend. Here is how this code looks in VB.NET:

Public Class PersonMap
    Inherits EntityTypeConfiguration(Of Person)
    Public Sub New()
        Ignore(Function(p) p.FullName)

Enumerations

We are all familiar with the use of enumerations. They make our code much more readable, since we can use descriptive names instead of magic numbers. For example, let's say that each type Person can be in one of three states: Active, Inactive, or Unknown. This is a prototypical scenario that calls for the use of enumerations. Entity Framework now has full support for enumerations. First of all, we need to define enumeration itself. For example, consider this code snippet:

public enum PersonState
{
    Active,
    Inactive,
    Unknown
}

This can also be shown in VB.NET, like the following code:

Public Enum PersonState
    Active
    Inactive
    Unknown
End Enum

The next step is to simply add a property of the type PersonState to the Person class. For instance, consider this code fragment:

public class Person
{
    public PersonState PersonState { get; set; }

Here is how this new property is defined in VB.NET:

Public Class Person
    Property PersonState() As PersonState

Technically, there is nothing else we need to do. We can just run this code to create our database structure. Once this is done, queries like the following one would work:

var people = context.People
    .Where(p=>p.PersonState == PersonState.Inactive);

Here is the same query in VB.NET:

Dim people = context.People _ 
                .Where(Function(p) p.PersonState = PersonState.Inactive)

Writing readable, easy to understand code is very important, and native support for enumerations in Entity Framework is very useful for such situations.

Using multiple tables for a single entity

The ability to split an entity across multiple tables plays an important role in scenarios where we have to store Binary Large OBjects (BLOBs) in the database, which is a commonly occurring situation. Some database administrators like to see BLOBs in a separate table, especially if they are not frequently accessed, in order to optimize a database's physical storage. As we recall, we represent BLOBs (the varbinary(MAX) column type in the SQL Server case) as byte arrays in .NET. Entity Framework aims to abstract a developer from storage details, so ideally we do not want our entities to reflect storage specific details. This is where the entity splitting feature comes in, which allows us to store one entity in multiple tables, with a subset of properties persisted in each table. Let's say that we want to store a person's photo, which can be a large object, in a separate table. We can use the Map method of the EntityTypeConfiguration class in order to configure such properties. We will demonstrate how to configure multiple properties, because the syntax is slightly different for two or more properties versus just a single property.

First of all, here is how our Person class looks with new properties:

public class Person
{
    public byte[] Photo { get; set; }
    public byte[] FamilyPicture { get; set; }

The same code in VB.NET looks as follows:

Public Class Person
    Property Photo() As Byte()
    Property FamilyPicture() As Byte()

In order to split an entity, Person in our case, we need to specify the table for each subset of columns, using explicit table names, similarly to what we did previously in this chapter. We will use anonymous types in order to provide property groupings. This code belongs in the configuration class for the Person class; EntityTypeConfiguration of type Person in our case. For example, consider this code snippet:

public class PersonMap : EntityTypeConfiguration<Person>
{
    public PersonMap()
    {
        Map(p =>
        {
            p.Properties(ph =>
                new
                {
                    ph.Photo,
                    ph.FamilyPicture
                });
            p.ToTable("PersonBlob");
        });
        Map(p =>
        {
            p.Properties(ph =>
                new
                {
                    ph.Address,
                    ph.BirthDate,
                    ph.FirstName,
                    ph.HeightInFeet,
                    ph.IsActive,
                    ph.LastName,
                    ph.MiddleName,
                    ph.PersonState,
                    ph.PersonTypeId
                });
            p.ToTable("Person");
        });
    }
}

We need to omit the actual primary key property, person's identifier, from both mappings because it actually belongs in both tables. We move binary columns to the PersonBlob table, keeping the rest of the columns in the Person table. We also mapped the complex type property as part of the same approach. Here is how code looks in VB.NET:

Public Class PersonMap
    Inherits EntityTypeConfiguration(Of Person)
    Public Sub New()
        Map(Sub(p)
                p.Properties(Function(m) _
                                    New With {
                                        m.Photo,
                                        m.FamilyPicture})
                p.ToTable("PersonBlob")
            End Sub)
        Map(Sub(p)
                p.Properties(Function(m) _
                                New With {
                                m.Address,
                                m.BirthDate,
                                m.FirstName,
                                m.HeightInFeet,
                                m.IsActive,
                                m.LastName,
                                m.MiddleName,
                                m.PersonState,
                                m.PersonTypeId})
                p.ToTable("Person")
            End Sub)
    End Sub
End Class

We can also do the opposite, that is, map multiple entity types to a single table. This process is called table splitting versus entity splitting in the preceding example. The use case for this scenario is exactly the same; we want to separate infrequently accessed properties into its own class, but then relate the two classes together. The data is stored in a single table, but only frequently used properties will be accessed by the main entity. The code is exactly the same as we saw previously with any two related entities, except we map both of them to the same table, using the explicit table mapping for both entities with the exact same table name. As a result, we can retrieve one entity, but omit related entities with large column data from the query. When we need to load related large object data, simply use the Include method covered previously.

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

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