In my last post I talked about some of the issues you typically face when using a large Entity model in your application. I have also described a few things that you can use to mitigate some of these problems. In this post I will walk through a couple of examples to demonstrate how you can split one large entity model into smaller ones while reusing types thus avoiding duplication.

Sub-dividing the model into smaller models with type reuse

The concept of “Using” in CSDL schema will allow you to do this. This is a pretty powerful feature that will enable you to create multiple models and let you reuse types in different models.

There are a few problems with using this approach which include:

a) No designer support: The designer does not support “Using”. So you would have to create your model first using designer and then edit the xml to use Entities from a related model.

b) Bi-Directional Navigation Not Supported: Since you cannot create cycles when creating model dependencies, you can declare navigation properties only in one model.

But even with these short comings, dividing the model into smaller models will make sense in a lot of cases. There are two ways you can sub-divide your model.

a. Multiple CSDL files( Models) while sharing MSL and SSDL:

The advantage with this approach is that the object model for your types is much cleaner. You don’t have the problem of having 1000 types in a single namespace. But it won’t solve the problem of performance or Intellisense.

How to create multiple CSDL files that will share MSL and SSDL:

The example uses Northwind sample database.

1. Create a new Ado.Net Entity Data Model using the Entity Data Model Wizard by pointing to Northwind database and choosing Products, Categories and Suppliers tables.

2. Change the “Metadata Artifact Processing” property of Edmx file to “Copy to Output Directory” directory and build the solution. This will drop the CSDL, SSDL and MSL files in the build output path.

3. Copy the schema files (CSDL, SSDL and MSL) to another location. This location will be used in the Metadata parameter of the EntityConnection string. Let’s call these files – model1.csdl, model.ssdl, model.msl

4. Open the CSDL file and copy the Cateogories Entity Type to a separate CSDL file. Let’s call this file model2.csdl. Use a different namespace for this schema. Let’s say this is NorthwindModelBase.

5. Remove the Categories entity type from the Model1.csdl.

6. Add a Using statement to import the newly created namespace.

Ex:

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

7. Change the Categories EntitySet and FK_Products_Categories to refer to the Categories Entity type.

Ex:

<Association Name="FK_Products_Categories">
  <End Role="Categories" Type="BaseModel.Categories" Multiplicity="0..1" />
  <End Role="Products" Type="NorthwindModel.Products" Multiplicity="*" />
</Association>

8. Use EdmGen to create two different C# files for these models.

Command:

   a.edmgen /mode:EntityClassGeneration /incsdl:model1.csdl

         /refcsdl:model2.csdl /outobjectlayer:model1.cs

   b.edmgen /mode:EntityClassGeneration /incsdl:model2.csdl

        /outobjectlayer: model2.cs

9. You can compile these c# files into the same assembly or a different assembly.

10. The Metadata property of the Connection string should point to both the CSDL files in addition to the SSDL and MSL files.

11. Assuming that the base model was built before the model that was using it, the Categories Entity type would not have a navigation property

The schemas for this example can be found in the attached .zip file under the folder MultipleModelsWithSingleSSDLAndMSLFiles

b. Divide application schemas into different sets of CSDL, MSL and SSDL files :

In the previous section, I described how to break your CSDL file into multiple CSDL files while still sharing the mapping. But as I mentioned earlier this would not solve the performance problems that could come up because of the size of the models. To solve the performance problems, you would have to actually map the database partially into different CSDL files.

Here are the things you need to remember when using this approach:

1. There could be cases where you might have to map the same table to two different models. So you would have some duplicate metadata lying around.

2. There could also be cases where you would expose foreign keys as scalar properties because you do not want to pull in all the related tables into your Entity model.

3. SSDL and MSL does not have any concept of reuse currently, so either you can choose to reuse the types in CSDL( as described in previous section) or you could choose to duplicate information in CSDL too. Reusing the types definitely has some advantages but given the pain in creating CSDL schemas that import other schemas, you might want to consider duplicating information in CSDL too. This would allow you to work with the designer. But if you are dividing the model for performance and maintainability reasons and you actually want to use these smaller models in a single application, duplicating the information would not be a viable option. There are definitely other disadvantages with duplicating information across multiple model files( typically the same problems that you would see with duplicate code). The way to avoid duplication would be by using the “Using” element in CSDL. In the below steps, I have described how to do model splitting with the support of Using and no duplication in the model( CSDL ) files.

How to split single set of CSDL, SSDL and MSL files into multiple sets:

The example uses Northwind sample database.

1. We want to create an application that uses the following tables: Products, Categories, Orders, Order Details, Customers and CustomerDemographics.

2. Let’s say for reasons of performance and maintainability we want to split these into two different models with two different containers.

3. To do this, create 2 new Ado.Net Entity Data Models using the Entity Data Model Wizard by pointing to Northwind database. In one model, choose Products, Categories, Orders, Customers and Order Details tables. In the second set, choose Customers and CustomerDemographics. So you have included Customers table both in the first set and second set. Let’s refer to the first set of schemas as ProductDetails and second set of schemas as CustomerDetails.

4. You can either choose to reuse the Customers type by using the “Using” element in CSDL or repeat the same type in both the sets.

5. In my sample( shared below) I have chosen to move the Customers type to a separate model called CustomerBase.csdl and reuse the Customers type in both CustomerDetails model in ProductDetails model.

6. Change the Customers end in “FK_Customers_Orders” association in ProductDetails model to refer to Customers type defined in CustomerBase model.

Ex :

<Association Name="FK_Customers_Orders">
  <End Role="Customers" Type="CustomerBase.Customers" Multiplicity="0..1" />
  <End Role="Orders" Type="NorthwindModel.Orders" Multiplicity="*" />
</Association>

7. You need to make a similar change to the CustomerCustomerDemo Association that relates Customers to CustomerDemogrpahics.

8. You can also see that there is a Navigation property on Orders type that you can use to Navigate to related Customer. Also observer that the Customer defined in the CustomerBase model does not have a navigation property to navigate back to related Orders. Since you are sharing Customers type between different models, you cannot add that navigation property. For example, if you add a navigation property for Orders on Customers type, it won’t make sense when you use Customers type in CustomerDetails model since Orders type is not present in that model.

9. At runtime, you could create either one Context that works with both the schema sets or two different contexts. To create a single context with both the schema sets, you would use the ObjectContext constructor that takes in an EntityConnectionString. In the Metadata parameter of the connection string, specify the paths to both sets of files.

10. Problems with Navigation properties being absent:

As mentioned above, in the ProductsModel there is a Navigation property on Orders type that you can use to Navigate to related Customer but you cannot navigate back to Orders from Customers because the NavigationProperty was not defined on the Customers type.

Here is some sample code that navigates from Orders to Customer and prints the name of Customer who ordered it. This is pretty simple since there is a Navigation property on Orders type that you can use to Navigate to related Customer.

NorthwindEntities productDetails = new NorthwindEntities(connString);
var orderInfos = from o in productDetails.Orders.Include("Customers")
                        select new { o.OrderID, o.Customers.ContactName);
foreach (var orderInfo in orderInfos)
{
    Console.WriteLine("Order ID:" + orderInfo.OrderID + "   Customer who                       
                       ordered:" + orderInfo.ContactName)
}

Here is another sample to navigate from Customer to Orders and prints the OrderID of all the orders that a customer has ordered. Since there is no navigation property, you would have to go to the Orders collection and use the Customer navigation property on Orders.

Here are a couple of samples in Linq to do the navigation back:

Sample 1

NorthwindEntities productDetails = new NorthwindEntities(connString);            
var customerInfos = from c in productDetails.Customers
                                 select new { Customer = c, Orders = (from o in productDetails.Orders where o.Customers == c select o) };
            foreach (var customerInfo in customerInfos)
            {
                Console.WriteLine("Customer " + customerInfo.Customer.ContactName);
                foreach (var orderInfo in customerInfo.Orders)
                {
                    Console.WriteLine("Order ID:" + orderInfo.OrderID);
                }
            }

Sample 2

NorthwindEntities productDetails = new NorthwindEntities(connString);
var customerInfos = from c in productDetails.Customers
                    join o in productDetails.Orders on c.CustomerID equals 
                    o.Customers.CustomerID into orders                                
                    select new {Customer = c, Orders = orders};
foreach (var customerInfo in customerInfos)
{
    Console.WriteLine("Customer " +  customerInfo.Customer.ContactName);
    foreach (var orderInfo in customerInfo.Orders)
    {
        Console.WriteLine("Order ID:" + orderInfo.OrderID);
    }
}

The schemas for this example can be found in the attached .zip file under the folder MultipleSchemaSets.

I hope that these posts have been helpful. I would love to hear your feedback and also things that you would like to add to the list of things that I suggested.

Srikanth Mandadi
Development Lead, Entity Framework