Ju-Yi Kuo's blog

  • EF Mapping Advisor (1) : The Language of Mapping

    In this post I will start explaining what’s behind the scene of ADO.NET Entity Framework mapping layer. Specifically I will focus on the direct mapping between entity sets and store tables. Other kinds of mapping such as stored procedure mapping will come later in this series.

    When it comes to mapping specification, ADO.NET Entity Framework takes a unique approach. You specify mapping by specifying a set of query pairs, where each query pair corresponds to a mapping fragment.

    To understand what I mean, let’s look at an example. Suppose we have a Person entity type and a Customer entity type which derives from the Person entity type. I want to split the Person entity type horizontally and map it to 2 tables in the store: PersonTable and AddressTable. This is a mapping strategy we called entity splitting. Furthermore the Customer type will have its own unique members mapped to yet another table, the CustomerTable. This is the typical table-per-type (TPT) mapping strategy to map an inheritance hierarchy. You can find the three artifact files below.

    CSDL file:

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>

    <Schema Namespace="EDMExample" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">

      <EntityContainer Name="EDMExampleContainer">

        <EntitySet Name="PersonSet" EntityType="Self.Person" />

      </EntityContainer>

      <EntityType Name="Person">

        <Key>

          <PropertyRef Name="Id" />

        </Key>

        <Property Name="Id" Nullable="false" Type="Int32" />

        <Property Name="Name" Type="String" MaxLength="512" FixedLength="false" Unicode="false" />

        <Property Name="Address" Type="String" MaxLength="512" FixedLength="false" Unicode="false" />

      </EntityType>

      <EntityType Name="Customer" BaseType="EDMExample.Person">

        <Property Name="CreditScore" Type="Int32" />

      </EntityType>

    </Schema>

    SSDL file:

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>

    <Schema Namespace="EDMExample.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">

      <EntityContainer Name="EDMExampleStoreContainer">

        <EntitySet Name="PersonTable" EntityType="EDMExample.Store.PersonTable" Schema="dbo" Table="PersonTable" />

        <EntitySet Name="AddressTable" EntityType="EDMExample.Store.AddressTable" Schema="dbo" Table="AddressTable" />

        <EntitySet Name="CustomerTable" EntityType="EDMExample.Store.CustomerTable" Schema="dbo" Table="CustomerTable" />

        <AssociationSet Name="FK_Address_PersonSet" Association="EDMExample.Store.FK_Address_Person">

          <End Role="Person" EntitySet="PersonTable" />

          <End Role="Address" EntitySet="AddressTable" />

        </AssociationSet>

        <AssociationSet Name="FK_Customer_PersonSet" Association="EDMExample.Store.FK_Customer_Person">

          <End Role="Person" EntitySet="PersonTable" />

          <End Role="Customer" EntitySet="CustomerTable" />

        </AssociationSet>

      </EntityContainer>

      <EntityType Name="PersonTable">

        <Key>

          <PropertyRef Name="Id" />

        </Key>

        <Property Name="Id" Type="int" Nullable="false" />

        <Property Name="Name" Type="varchar" Nullable="true" MaxLength="512" />

      </EntityType>

      <EntityType Name="AddressTable">

        <Key>

          <PropertyRef Name="Id" />

        </Key>

        <Property Name="Id" Type="int" Nullable="false" />

        <Property Name="Address" Type="varchar" Nullable="true" MaxLength="512" />

      </EntityType>

      <EntityType Name="CustomerTable">

        <Key>

          <PropertyRef Name="Id" />

        </Key>

        <Property Name="Id" Type="int" Nullable="false" />

        <Property Name="CreditScore" Type="int" Nullable="true" />

      </EntityType>

      <Association Name="FK_Address_Person">

        <End Role="Person" Type="EDMExample.Store.PersonTable" Multiplicity="1" />

        <End Role="Address" Type="EDMExample.Store.AddressTable" Multiplicity="0..1" />

        <ReferentialConstraint>

          <Principal Role="Person">

            <PropertyRef Name="Id" />

          </Principal>

          <Dependent Role="Address">

            <PropertyRef Name="Id" />

          </Dependent>

        </ReferentialConstraint>

      </Association>

      <Association Name="FK_Customer_Person">

        <End Role="Person" Type="EDMExample.Store.PersonTable" Multiplicity="1" />

        <End Role="Customer" Type="EDMExample.Store.CustomerTable" Multiplicity="0..1" />

        <ReferentialConstraint>

          <Principal Role="Person">

            <PropertyRef Name="Id" />

          </Principal>

          <Dependent Role="Customer">

            <PropertyRef Name="Id" />

          </Dependent>

        </ReferentialConstraint>

      </Association>

    </Schema>

    MSL file:

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>

    <Mapping Space="C-S" xmlns:cdm="urn:schemas-microsoft-com:windows:storage:mapping:CS" xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">

      <EntityContainerMapping StorageEntityContainer="EDMExampleStoreContainer" CdmEntityContainer="EDMExampleContainer">

        <EntitySetMapping Name="PersonSet">

          <EntityTypeMapping TypeName="IsTypeOf(EDMExample.Person)">

            <MappingFragment StoreEntitySet="PersonTable">

              <ScalarProperty ColumnName="Id" Name="Id" />

              <ScalarProperty Name="Name" ColumnName="Name" />

            </MappingFragment>

     

            <MappingFragment StoreEntitySet="AddressTable">

              <ScalarProperty ColumnName="Id" Name="Id" />

              <ScalarProperty Name="Address" ColumnName="Address" />

            </MappingFragment>

          </EntityTypeMapping>

          <EntityTypeMapping TypeName="EDMExample.Customer">

            <MappingFragment StoreEntitySet="CustomerTable">

              <ScalarProperty ColumnName="Id" Name="Id" />

              <ScalarProperty Name="CreditScore" ColumnName="CreditScore" />

            </MappingFragment>

          </EntityTypeMapping>

        </EntitySetMapping>

      </EntityContainerMapping>

    </Mapping>

    Both the CSDL and SSDL files are easily understood. I want to focus on the MSL file and explain how EF runtime reasons about the content of this file. As you can see, I have highlighted 3 mapping fragments. So what do these fragments mean? Well, when EF runtime looks at a mapping fragment, it considers it to be a pair of equivalent Entity SQL queries (Entity SQL is an extension of the SQL language that is supported by the Entity Framework):

                    QC = QS

    The query on the left hand side is an ESQL query against a Conceptual side entity type which returns rows of scalar values; the query on the right is an ESQL query on a Store side table which returns rows of scalar values, and these 2 result sets should be equal. If they are not, then it’s a mapping error and EF will throw an exception. You can also think of QC as a view V on the entity, and it should be equal to the view that QS represents. This means QC = V = QS. From now on we will call these queries “fragment queries”.

    As an example, let’s look at the first highlighted fragment. It is under the IsTypeOf(Person) type mapping element, therefore EF will consider the C side query to be:

                    QC = SELECT c.Id, c.Name FROM PersonSet c

    Notice that this query will return results from both Person and Customer instances. Since the mapping fragment maps to PersonTable, EF considers the S side query to be:

                    QS = SELECT Id, Name FROM PersonTable

    The results from these 2 queries should be equivalent. This, is the EF language of mapping. You can see that the Person entity type has 2 mapping fragment related to it, and the Customer entity type has 3 mapping fragments related to it. So in a sense, the Person entity instances are horizontally split into 2 portions and the Customer entity instances are horizontally split into 3 portions as indicated in the picture below.

     

     

    EF will “stitch up”, or join these queries to materialize Person and Customer instances. But how, you may ask? I will provide some more details in my next post.

     

     

  • Entity Framework Mapping Advisor (0)

    As a member of the ADO.NET Entity Framework team, I couldn't wait to see the day that our v1 product gets into the hands of the developers; I also would like to provide some insight into the product.

    One of the main advantages of EF is its flexible mapping layer. For an introduction, check out Mapping 101. When it comes to mapping, the entity framwork designer tool in Visual Studio does a good job in helping you specify a broad range of mapping scenarios. However, the Entity Framework runtime allows a lot more mapping flexibility than the entity designer supports currently. For advanced users, they might need to manually edit the XML mapping file in order to achieve the kind of mapping they want, because they couldn't do this through the designer tool. But it could be difficult for them to troubleshoot if they make any mapping mistake.

    That's why I'm starting this mapping advisor series. These articles hopefully can help advanced users do two things:

    1. Design mapping scenarios beyond those supported by the GUI designer tool.
    2. Understand the error message better, so that they can rectify or circumvent their mapping mistakes easily.

    My plan to achieve this is to first explain in some detail how the mapping layer works under the cover, and then give a few examples. So, here it goes.

    Next: The Language of Mapping

     


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker