Entity Framework Architecture

Figure 10.1 shows the architecture used within the Entity Framework. As you can see from the diagram, the Entity Framework is composed of a number of logical layers. The lowest layer is related to the actual data access, and involves the familiar ADO.NET data providers. The Entity Framework is not an entirely new method of retrieving data, but is an extension of your existing knowledge of ADO.NET. The additional layers are intended to make your development experience easier and more flexible. At the data layer, Entity Framework does not differ from ADO.NET or LINQ to SQL, as it deals with the tables directly.

Figure 10.1 Entity Framework Layer Diagram

10.1

Above the actual data access layer is the storage layer. This is basically a representation of the structure of the database, using XML syntax. It includes any of the tables you've added to your model, as well as the relationships between them.

Above the storage layer is the mapping layer. This serves as a means of translating between the storage layer below and the conceptual layer above. You can think of it as a relatively thin layer, responsible for mapping the fields of the database tables to the appropriate properties of the classes used by your application.Next is the conceptual layer. This is the layer that provides the entities of your model, providing you with the classes you will work with in your applications — either the classes generated by the Entity Framework designer or your own classes, as you'll see later.

Finally, there is the object services layer. This serves to provide LINQ and Entity Query Language (Entity SQL) syntax over your conceptual model.

When you see a diagram like the one shown in Figure 10.1, your first instinct may be to worry about the performance penalties that these additional layers cause to your application. Of course, every mapping, abstraction, or communication adds to the query and/or update time; this is to be expected. However, the decision to use Entity Framework should not be entirely based on whether it is faster or slower than classic ADO.NET. Rather, it should depend on a combination of “Is it fast enough for my needs?” and “How much more productive does it make me?” Because Entity Framework uses the core ADO.NET objects, there is no way it can be faster than, or even as fast as, using those classes themselves. However, working with Entity Framework can be a much more natural development process, meaning you can be much more productive in creating — and, more important, maintaining — your data access code.

Conceptual Model

Your Entity Framework applications deal directly with the conceptual models you either generate or create. To see how these are constructed, create a simple console application (EFSimpleExample) and add an ADO.NET Entity Data Model to the application by selecting Project ⇒ Add New Item (see Figure 10.2).

Figure 10.2 Add ADO.NET Entity Data Model

10.2

When you add an Entity Framework model to your application, it starts a wizard to generate the classes. The first step is to decide if you will generate your classes initially from a database or from a blank slate. For now, select to generate the classes from the database (see Figure 10.3). Select the AdventureWorks database.

Figure 10.3 Generate Model from Database

10.3

The connection string generated at this stage (see Figure 10.4) can look a little foreboding to anyone used to the simpler SQL Server or Access connection strings.

Figure 10.4 Choose Data Connection

10.4
metadata=res://*/AWModel.csdl|res://*/AWModel.ssdl|res://*/AWModel.msl;
provider=System.Data.SqlClient;provider connection 
string="data source=WIN-VQ6H9M4A3VESQLEXPRESS;
initial catalog=AdventureWorks;integrated security=True;
MultipleActiveResultSets=True;App=EntityFramework"

Ignoring the first few sections, you can see the “normal” connection string contained within this connection. The reason it has the additional sections is because this connection string will be used by all three layers (storage, conceptual, and mapping), not just the connection to the database. The three additional parts of the connection string identify the files that will define the structure of each layer.

Next, just as with LINQ to SQL, you can choose the database objects you would like to include in your model. For now, just select the CreditCard, Customer, SalesOrderDetail, and SalesOrderHeader tables (see Figure 10.5) and click Finish.

Figure 10.5 Choose Database Objects

10.5

Figure 10.6 shows the resulting model in Visual Studio. Notice that it includes the relationships between the three tables in the model in addition to creating properties that represent the columns in the database.

Figure 10.6 New Data Model

10.6

Finally, the wizard has created a number of navigation properties that represent the foreign key relationships. You can explore your model within this designer window or use the Model Browser pane that opens in Visual Studio (see Figure 10.7) which you can view by selecting View ⇒ Other Windows ⇒ Entity Data Model Browser.

Figure 10.7 Model Browser

10.7

Build the application but don't run it yet. Once you have it built, select the Show All Files option in the Solution Explorer (see Figure 10.8). If you navigate into the generated obj folder, you will find the three generated XML files.

Figure 10.8 Generate XML Files

10.8

The following code shows a portion of the CSDL file, which is the conceptual model:

<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="AWModel" Alias="Self" p1:UseStrongSpatialTypes="false" 
  xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" 
  xmlns:p1="http://schemas.microsoft.com/ado/2009/02/edm/annotation" 
  xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
  <EntityContainer Name="AWEntities" p1:LazyLoadingEnabled="true">
    <EntitySet Name="CreditCards" EntityType="AWModel.CreditCard" />
    <EntitySet Name="Customers" EntityType="AWModel.Customer" />
    <EntitySet Name="SalesOrderDetails" EntityType="AWModel.SalesOrderDetail" />
    <EntitySet Name="SalesOrderHeaders" EntityType="AWModel.SalesOrderHeader" />
    <AssociationSet Name="FK_SalesOrderHeader_CreditCard_CreditCardID" 
      Association="AWModel.FK_SalesOrderHeader_CreditCard_CreditCardID">
      <End Role="CreditCard" EntitySet="CreditCards" />
      <End Role="SalesOrderHeader" EntitySet="SalesOrderHeaders" />
    </AssociationSet>
    <AssociationSet Name="FK_SalesOrderHeader_Customer_CustomerID" 
      Association="AWModel.FK_SalesOrderHeader_Customer_CustomerID">
      <End Role="Customer" EntitySet="Customers" />
      <End Role="SalesOrderHeader" EntitySet="SalesOrderHeaders" />
    </AssociationSet>
    <AssociationSet Name="FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID" 
      Association="AWModel.FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID">
      <End Role="SalesOrderHeader" EntitySet="SalesOrderHeaders" />
      <End Role="SalesOrderDetail" EntitySet="SalesOrderDetails" />
    </AssociationSet>
  </EntityContainer>
  <EntityType Name="CreditCard">
    <Key>
      <PropertyRef Name="CreditCardID" />
    </Key>
    <Property Name="CreditCardID" Type="Int32" Nullable="false" 
      p1:StoreGeneratedPattern="Identity" />
    <Property Name="CardType" Type="String" Nullable="false" MaxLength="50" 
      Unicode="true" FixedLength="false" />
    <Property Name="CardNumber" Type="String" Nullable="false" MaxLength="25" 
      Unicode="true" FixedLength="false" />
    <Property Name="ExpMonth" Type="Byte" Nullable="false" />
    <Property Name="ExpYear" Type="Int16" Nullable="false" />
    <Property Name="ModifiedDate" Type="DateTime" Nullable="false" Precision="3" />
    <NavigationProperty Name="SalesOrderHeaders" 
      Relationship="AWModel.FK_SalesOrderHeader_CreditCard_CreditCardID" 
      FromRole="CreditCard" ToRole="SalesOrderHeader" />
  </EntityType>
...

This snippet shows some of the main terms you will see repeatedly throughout your work with the Entity Framework. The EntityType defines one of your objects — in this case, the CreditCard class. The collection of credit cards is defined as an EntitySet. There are AssociationSets that define the relationships between the various EntityTypes. Finally, there is an EntityContainer that groups everything. One point to notice is that each of the Property elements in the XML file has a Type attribute. This attribute uses Visual Basic data types, rather than database-specific types. This XML file will be updated as you change your conceptual model.

If you look at one of the generated types in the Class View window (see Figure 10.9), you will see that it inherits from EntityObject. The EntityObject class in turn inherits from StructuralObject and implements three interfaces (IEntityWithChangeTracker, IEntityWithKey and IEntityWithRelationships). The names of these three interfaces give you some idea of what the generated classes are capable of:

Figure 10.9 Class View

10.9
1. They are able to identify one another via one or more key properties.
2. They are aware of changes to their properties; therefore, you will be able to identify changed objects and/or properties without requiring a trip back to the database.
3. They track their relationship to one or more other EntityObjects.

Storage Model

The storage model XML initially looks similar to the conceptual model XML (see the generated AWModel.ssdl file in the Solution Explorer View):

<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="AWModel.Store" Alias="Self" Provider="System.Data.SqlClient" 
  ProviderManifestToken="2008" 
  xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/
  EntityStoreSchemaGenerator" 
  xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
  <EntityContainer Name="AWModelStoreContainer">
    <EntitySet Name="CreditCard" EntityType="AWModel.Store.CreditCard" 
      store:Type="Tables" Schema="Sales" />
    <EntitySet Name="Customer" EntityType="AWModel.Store.Customer" 
      store:Type="Tables" Schema="Sales" />
    <EntitySet Name="SalesOrderDetail" EntityType="AWModel.Store.SalesOrderDetail" 
      store:Type="Tables" Schema="Sales" />
    <EntitySet Name="SalesOrderHeader" EntityType="AWModel.Store.SalesOrderHeader" 
      store:Type="Tables" Schema="Sales" />
    <AssociationSet Name="FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID" 
                          Association="AWModel.Store.
                          FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID">
      <End Role="SalesOrderHeader" EntitySet="SalesOrderHeader" />
      <End Role="SalesOrderDetail" EntitySet="SalesOrderDetail" />
    </AssociationSet>
    <AssociationSet Name="FK_SalesOrderHeader_CreditCard_CreditCardID" 
      Association="AWModel.Store.FK_SalesOrderHeader_CreditCard_CreditCardID">
      <End Role="CreditCard" EntitySet="CreditCard" />
      <End Role="SalesOrderHeader" EntitySet="SalesOrderHeader" />
    </AssociationSet>
    <AssociationSet Name="FK_SalesOrderHeader_Customer_CustomerID" 
      Association="AWModel.Store.FK_SalesOrderHeader_Customer_CustomerID">
      <End Role="Customer" EntitySet="Customer" />
      <End Role="SalesOrderHeader" EntitySet="SalesOrderHeader" />
    </AssociationSet>
  </EntityContainer>
  <EntityType Name="CreditCard">
    <Key>
      <PropertyRef Name="CreditCardID" />
    </Key>
    <Property Name="CreditCardID" Type="int" Nullable="false" 
      StoreGeneratedPattern="Identity" />
    <Property Name="CardType" Type="nvarchar" Nullable="false" MaxLength="50" />
    <Property Name="CardNumber" Type="nvarchar" Nullable="false" MaxLength="25" />
    <Property Name="ExpMonth" Type="tinyint" Nullable="false" />
    <Property Name="ExpYear" Type="smallint" Nullable="false" />
    <Property Name="ModifiedDate" Type="datetime" Nullable="false" />
  </EntityType>
...

One major difference between this file and the earlier conceptual model is that the types in the storage model are SQL Server data types. In addition, unlike the conceptual model file, this file will not change as you update your Entity Framework model, as it is tied to the database structure.

Mapping Model

Finally, you have the third XML file, the mapping schema language (MSL) file:

<?xml version="1.0" encoding="utf-8"?>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
  <EntityContainerMapping StorageEntityContainer="AWModelStoreContainer" 
      CdmEntityContainer="AWEntities">
    <EntitySetMapping Name="CreditCards">
      <EntityTypeMapping TypeName="AWModel.CreditCard">
        <MappingFragment StoreEntitySet="CreditCard">
          <ScalarProperty Name="CreditCardID" ColumnName="CreditCardID" />
          <ScalarProperty Name="CardType" ColumnName="CardType" />
          <ScalarProperty Name="CardNumber" ColumnName="CardNumber" />
          <ScalarProperty Name="ExpMonth" ColumnName="ExpMonth" />
          <ScalarProperty Name="ExpYear" ColumnName="ExpYear" />
          <ScalarProperty Name="ModifiedDate" ColumnName="ModifiedDate" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>
...

This may seem like a great deal of overhead, as it appears to map properties of the classes to the identical fields of tables. However, as you customize the conceptual model, the mapping model will change to reflect the new structure. As the Entity Framework supports mapping a single object to multiple tables, or vice versa, this mapping model increases in importance, and is the core benefit of using a framework such as Entity Framework.

LINQ to Entities

Just as there is LINQ to SQL, LINQ to XML, and LINQ to objects, there is also LINQ syntax for working with Entity Framework models. The syntax is very similar to that used by LINQ to SQL, in that a context object is used as your access point to the exposed classes. You first create an instance of that context object and then use it to access the entities in your model. The following shows a LINQ query to retrieve customers in territory 1:

  Sub Main()

    Dim ctx As New AWEntities

    Dim customers = From
                      c In ctx.Customers
                    Where
                      c.TerritoryID = 1
                    Order By
                      c.AccountNumber
                    Select
                      c

    For Each c In customers.ToList
      Console.WriteLine("# {0} Last Updated: {1}", c.AccountNumber, c.ModifiedDate)
    Next

    Console.ReadLine()

  End Sub

Here, the new context object (AWEntities) is defined within the routine, but you are more likely to create it once and use it throughout your application. The remainder of the query defines a restriction and a sort, and returns all the properties.

The ObjectContext

As you have seen from the preceding query, you use a context object as the root of all your queries. This context is the logical equivalent of the Connection object in ADO.NET, but it does much more. The context object is a class that inherits from ObjectContext. In addition to providing access to the database, the ObjectContext is also responsible for allowing you to retrieve metadata about the entities within your model and helping the objects track their changes.

Once you have made changes to one or more objects tracked by an object context, you can apply those changes back to the database using the ObjectContext. The SaveChanges method submits the changes you have made to the database. It iterates over all the added, updated, and deleted objects and submits these changes, and returns the number of records updated.

At this point, the objects do not know that they have been saved, so you must set them back to their unchanged state. There are two ways you can do this. First, you can call the SaveChanges method with a single Boolean parameter set to SaveOptions.AcceptAllChangesAfterSave. This automatically updates the changed objects. Alternately, you can call the AcceptAllChanges method of the ObjectContext. This also iterates through all the successful updates, resetting the change tracking. The following code shows these steps while adding and updating credit cards in the database:

Sub AddCreditCard()

    Dim ctx As New AWEntities

    ''Add New Credit Card
    Dim newCC = CreditCard.CreateCreditCard(
      0, "Visa", "1234567890123456789012123",
      10, 2015, DateTime.Now)

    ctx.CreditCards.AddObject(newCC)

    ''Edit Credit Card
    Dim editCC = (From
                    cc In ctx.CreditCards
                  Where
                     cc.CardNumber = "33335500655096"
                  Select
                    cc
                 ).Single

    editCC.ExpMonth = 12
    editCC.ModifiedDate = DateTime.Now

    ''Save Changes
    Dim changeCount = 
          ctx.SaveChanges(Objects.SaveOptions.AcceptAllChangesAfterSave)

    Console.WriteLine("{0} records changed.", changeCount)

    Console.WriteLine("Credit card state: {0} Date updated: {1}",
            editCC.EntityState.ToString(), editCC.ModifiedDate.ToString)

    Console.ReadLine()

  End Sub

The output of this routine should be as follows:

2 records changed.
Credit card state: Unchanged Date updated: 7/15/2012 10:19:08 AM

The update process operates within a single transaction, so if any of the changes fail, the entire SaveChanges will fail.

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

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