Data Access has been discontinued. Please refer to this page for more information.

How to: Handle Serialization of Entities with Navigation Properties

This article is relevant to entity models that utilize the deprecated Visual Studio integration of Telerik Data Access. The current documentation of the Data Access framework is available here.

Serialization of entities with serializable navigation properties is specific process that may have a negative impact on the performance of your application if not handled correctly. This article will show you how to handle the serialization of entities with navigation properties.

Potential Issues when Serializing Entities with Navigation Properties

Consider the following scenario - you have the model of the SofiaCarRental database and serialization is enabled for its entities. The Reference and Collection navigation properties will also be serialized - the Ignore Reference Associations and Ignore Collection Associations checkboxes are not checked:

Screen1b

The Category persistent class has two navigation properties - RentalRates and Cars which are serializable:

Screen2b

We will attempt to serialize a Category object retrieved from the database like so:

using (EntitiesModel context = new EntitiesModel())
{
    Category categoryToSerialize = context.Categories.FirstOrDefault();
    using (MemoryStream serializationStream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        //Not Recommended: this will cause additional SQL statements that will
        //serialize the entire graph of related objects
        formatter.Serialize(serializationStream, categoryToSerialize);
    }
}
Using context As New EntitiesModel()
    Dim retrievedCategory As Category = context.Categories.FirstOrDefault()
    Using serializationStream As New MemoryStream()
        Dim formatter As New BinaryFormatter()
        'Not Recommended: this will cause additional SQL statements that will
        'serialize the entire graph of related objects
        formatter.Serialize(serializationStream, categoryForSerialization)
    End Using
End Using

This approach to serializing entities with navigation properties is not recommended.

The entity will be serialized, however, as the RentalRates and Cars navigation properties are lazy loaded and serializable, the serializer will cause additional SQL statements to be executed in order to serialize them. The same process will repeat for loading the navigation properties of the RentalRates and Car entities and continue down the graph of related objects. Essentially serialization done in this manner can retrieve a huge part the information in your database in memory. A SQL query similar to the following will be generated upon serialization:

SELECT * 
FROM [Categories] a 
LEFT JOIN [RentalRates] AS b ON (a.[CategoryID] = b.[CategoryID]) 
WHERE a.[CategoryID] = @p0                                        
SELECT * 
FROM [Categories] a LEFT JOIN [Cars] AS b ON (a.[CategoryID] = b.[CategoryID])
WHERE a.[CategoryID] = @p0                                        
SELECT * 
FROM [Cars] a LEFT JOIN [RentalOrders] AS b ON (a.[CarID] = b.[CarID]) 
WHERE a.[CarID] = @p0                                        
SELECT * 
FROM [Cars] a LEFT JOIN [RentalOrders] AS b ON (a.[CarID] = b.[CarID]) 
WHERE a.[CarID] = @p0                                        
SELECT * 
FROM [Cars] a LEFT JOIN [RentalOrders] AS b ON (a.[CarID] = b.[CarID]) 
WHERE a.[CarID] = @p0   
SELECT * 
FROM [Customers] 
WHERE [CustomerID] = @p0                                        
SELECT * 
FROM [Employees] 
WHERE [EmployeeID] = @p0

Safe Serialization of Entities with Navigation Properties

You can safely serialize entities with navigation properties without loading unnecessary data in memory. To do this you would need utilize the Detach API and a Fetch Strategy to load an entity together with required navigation properties and serialize only them. This can be done with the following steps:

  1. Create a Fetch Strategy which specifies the navigation properties should be loaded with the entity.

    FetchStrategy loadCategoryWithCars = new FetchStrategy();
    loadCategoryWithCars.LoadWith<Category>(category => category.Cars);
    
    Dim loadCategoryWithCars As New FetchStrategy()
    loadCategoryWithCars.LoadWith(Of Category)(Function(category) category.Cars)
    
  2. Retrieve the required entity or entities using the defined fetch strategy. This way all required information will be loaded in a single SQL query.

    Category retrievedCategory = context
                .Categories.LoadWith(loadCategoryWithCars).FirstOrDefault();
    
    Dim retrievedCategory As Category = context.
                Categories.LoadWith(loadCategoryWithCars).FirstOrDefault()
    
  3. Detach the entity that needs to be serialized. This will prevent the serializator from causing the generation of additional SQL statements which would load the graph beyond the desired depth.

    Category categoryForSerialization = context
                .CreateDetachedCopy<Category>(retrievedCategory, loadCategoryWithCars);
    
    Dim categoryForSerialization As Category = context.
                CreateDetachedCopy(Of Category)(retrievedCategory, loadCategoryWithCars)
    
  4. Serialize the detached entity - no additional SQL statements will be executed.

    using (MemoryStream serializationStream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        context.Log = new StringWriter();
        formatter.Serialize(serializationStream, categoryForSerialization);
        //check the generated SQL to see that no additional statements are generated
        string query = context.Log.ToString(); 
    }
    
    Using serializationStream As New MemoryStream()
        Dim formatter As New BinaryFormatter()
        context.Log = New StringWriter()
        formatter.Serialize(serializationStream, categoryForSerialization)
        'check the generated SQL to see that no additional statements are generated
        Dim query As String = context.Log.ToString()
    End Using