Chapter 11. Customizing Entity Persistence and Adding Business Logic

The LINQ to SQL designer, which is also known as the Object Relational Designer, and SqlMetal help you by generating classes and mapping from a database. The generated code targets the most common and standard way to persist objects and is not meant to be modified. However, it includes mechanisms for customizing how the objects are retrieved, modified, and persisted. It also provides easy extensibility for the addition of business logic. Finally, the generated code is only one way to use the LINQ to SQL runtime libraries. You can also write your own classes and specify mapping external to the classes if you want to. In this chapter we will look at the common ways to customize generated classes and write your own classes to use LINQ to SQL in the most effective fashion for your applications.

Customizing Generated Code

Let’s revisit the code generated by the LINQ to SQL designer when the Customers and Orders tables from the Northwind database are dropped on the designer surface in a new project. Recall that the command-line tool SqlMetal uses the same code generator. So a few project configuration items aside, you will get substantially the same code from SqlMetal as well.

The generated code in Northwind.designer.cs has two main sets of classes—a DataContext class, NorthwindDataContext, and a set of entity classes—Customer and Order in this case. Both sets of classes allow a rich set of customizations without any need to modify the generated source code. This is done through the use of partial classes, partial methods, and virtual methods. Partial classes and virtual methods have been available in C# and VB.NET prior to the LINQ-enabled release. Partial methods were added along with LINQ support and are covered in detail in Chapter 4, “C# 3.0 Technical Overview.”

The LINQ to SQL designer provides an easy way to create a partial class stub to add your code. For the DataContext class, right-click the designer surface, and select the View Code option. A corresponding file, Northwind.cs, is created with a partial class declaration for NorthwindDataContext. This is where you can write your own code without worrying about the designer overwriting it when you make changes in the designer and regenerate code. Likewise, right-clicking an entity class gives you a partial class stub in Northwind.cs for the corresponding entity class. The generated stubs along with manually added code are shown in a following listing.

Customizing the DataContext Class

The generated NorthwindDataContext class contains the following partial methods tucked away in a code region:

#region Extensibility Method Definitions
partial void OnCreated();
partial void InsertCustomer(Customer instance);
partial void UpdateCustomer(Customer instance);
partial void DeleteCustomer(Customer instance);
partial void InsertOrder(Order instance);
partial void UpdateOrder(Order instance);
partial void DeleteOrder(Order instance);
#endregion

As explained in Chapter 4, the partial methods provide placeholders for you to write code if you want to. In this case, the implementations of the partial methods are called either in the generated code, as in the case of OnCreated(), or by the LINQ to SQL runtime, as in the case of insert, update, and delete methods for an entity type. If you choose not to customize the specific behavior, the compiler simply omits the method calls in the generated code and optimizes away the method metadata. The method declaration just acts as a stub. The LINQ to SQL runtime provides its default insert, update, and delete behavior when it cannot find an overriding implementation of the method.

The OnCreated() method is called in the NorthwindDataContext constructors and provides you a way to write the code you want in the constructor. For example, it lets you initialize any additional properties you choose to create in your partial class.

We briefly covered the partial methods for insert, update, and delete customization in Chapter 10, “Using Stored Procedures and Database Functions with LINQ to SQL,” for using stored procedures. That is by far the most common use of these methods. However, the implementation of the methods can do any other operation as well. In fact, it can even add pre-and post-operation logic and simply use the LINQ to SQL capabilities to perform the actual operation, as shown in the following code fragment. It shows how insert, update, and delete operations can be customized for Customer entities while using LINQ to SQL methods such as DataContext.ExecuteInsert() that generate dynamic SQL on your behalf. In each case, you can add the logging code before and/or after the actual operation. You do not need to take over the entire operation, which involves generating a command, opening a connection, executing the command, and flowing back the database-generated values.

The code fragment also shows how to obtain the original values for an entity in case you want to use them for your pre- or post-processing. It also illustrates that you can change a delete operation into an update—a common practice in which database records are marked as deleted or are moved to a “tombstone” table instead of being deleted. Finally, the code fragment shows how relationship loading can be customized—in this case by using a stored procedure as described in the preceding chapter. Together the methods let you customize the CRUD operations per entity type—in this case shown for the Customer type.

partial class NorthwindDataContext
{
   partial void OnCreated()
   {
      // Set up the log for logging operations
   }


   partial void InsertCustomer(Customer instance)
   {
      // pre-insert processing; e.g. log attempted operation
      this.ExecuteDynamicInsert(instance);
      // post-insert processing; e.g. log completed operation
   }

   partial void UpdateCustomer(Customer instance)
   {
      // Get the original version for logging
      Customer original =
         this.Customers.GetOriginalEntityState(instance);
      // Add code for pre-update processing
      // e.g. log the original and current state

      // Use LINQ to SQL method for the update operation
      this.ExecuteDynamicUpdate(instance);
   }

   partial void DeleteCustomer(Customer instance)
   {
      // pre-"delete" processing
      // set some status field to deleted and issue an update instead
      this.ExecuteDynamicUpdate(instance);
      // post-"delete" processing
   }

   private IEnumerable<Order> LoadOrders(Customer customer)
   {
      // Call a stored procedure for loading
      return this.OrdersByCustomer(customer.CustomerID);
   }
}

Overriding SubmitChanges

In addition to the per-entity-type customization just described, you can customize the overall SubmitChanges() operation by overriding the method in your partial class. As in the case of InsertCustomer() and other methods in the previous example, the heavy lifting can be done by the SubmitChanges() method in the base class implemented in LINQ to SQL code. You can just add the functionality specific to your application—in this case logging of the set of changed entities.

public override void SubmitChanges(ConflictMode failureMode)
{
   ChangeSet cs = this.GetChangeSet();
   // Add code to log the entire change set

   // Use base class operation for bulk of the work
   base.SubmitChanges(failureMode);
}

Customizing the Entity Classes

A common need for entity class customization is the ability to specify your own base class with common functionality. LINQ to SQL does not require a specific base class. In other words, it does not “hijack” your base class. Hence, you are free to use your base class. The command-line tool SqlMetal lets you specify a base class with the /entitybase option. The designer currently does not expose this capability, but you can work around that limitation by using SqlMetal to generate code with the /entitybase option from the designer-generated dbml file.

Another common need is to use the entity classes as return values from a web method. To serialize entities using the Windows Communication Foundation (WCF), DataContract and DataMember attributes are needed on entity and entity properties, respectively. LINQ to SQL designer and SqlMetal provide an easy way to add the DataContract serialization attributes. In the designer, right-click the designer surface, and select Properties. Figure 11.1 shows the serialization options. Select Unidirectional to get the DataContract attributes. Because the serializer in .NET Framework Version 3.5 did not permit cycles,[1] the Unidirectional option ensures that the DataMember attribute is placed in only one direction of a bidirectional relationship. Customer.Orders gets the DataMember attribute, but Order.Customer does not.

[1] The WCF DataContract serializer enabled the handling of circular object references in 3.5 SP1 using the IsReference property on DataMember after LINQ to SQL shipped in Version 3.5 (pre-SP1). Earlier releases required serializer configuration (not the default setting).

Figure 11.1. Generating DataContract attributes in the designer.

image

When the dbml file is saved, the generated code includes serialization attributes as follows:

[Table(Name="dbo.Customers")]
[DataContract()]
public partial class Customer : INotifyPropertyChangingINotifyPropertyChanged
{
   ...
   [Column(Storage="_CustomerID", DbType="NChar(5) NOT NULL",
    CanBeNull=false, IsPrimaryKey=true)]
   [DataMember(Order=1)]
   public string CustomerID
   {
   ...

This entity class can now be used as a return type in a WCF web service. The following is a simple implementation to illustrate how the class can be used with an appropriate service contract. You need to add a reference to System.ServiceModel.dll in your project through the Visual Studio Solution Explorer.

[ServiceContract]
public interface ICustomerService
{
   [OperationContract]
   Customer GetCustomer(string id);

}

public class CustomerService : ICustomerService
{
   public Customer GetCustomer(string id)
   {
      NorthwindDataContext db = new NorthwindDataContext();
      return (db.Customers.Where(c => c.CustomerID == id).Single());
   }
}

Beyond the use of designer and SqlMetal options, you can extend entity classes by adding your own partial class, as in the case of the DataContext class. In the designer, if you right-click an entity class and select the View Code option, a partial class stub for the corresponding entity is generated. The stub is generated in the same file mentioned before. For Northwind.designer.cs, the stub class is created in Northwind.cs. The newly created partial class provides a place to add methods containing business logic or additional nonpersistent properties. For example, you could add a method for discount computation to an Order entity in the partial class, or you could add a Discount property that does not map to any database column. The two members are shown in the following code snippet:

partial class Order
{
   public decimal ComputeDiscount(string CouponCode)
   {
      // call a web service and obtain the discount percentage
   }

   public decimal Discount { getset; }
}

Any field or property you add in your partial class typically is not be mapped to a column in the database. Hence, it cannot be used in the LINQ to SQL query. LINQ to SQL cannot find it in the mapping. Hence, you get a runtime exception when LINQ to SQL attempts to translate the unmapped property to a column for use in the generated SQL.

Using Entity Lifecycle Events

When a LINQ query is executed against a database, LINQ to SQL constructs entities and sets the values of its properties. Likewise, when SubmitChanges() is called, LINQ to SQL computes the set of changed entities and submits the changes as database commands. When you are relying on generated code for entities, it is useful to be able to add custom behaviors at these persistence-related points in an entity’s lifecycle. LINQ to SQL provides ways to add code during the lifecycle using partial methods.

Generated entity classes contain partial method declarations and calls for customization similar to those in the DataContext class. The following methods are declared for the Order class. Three partial methods are related to the entity’s lifecycle, and then one pair of methods per property, such as OnRequiredDateChanging() and OnRequiredDateChanged(). The method pairs for one property are shown; the others are omitted.

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
...
partial void OnRequiredDateChanging(string value);
partial void OnRequiredDateChanged();
...
#endregion

The OnCreated() method is called in the entity class constructor as a part of the Initialize() method. You can see the call to OnCreated() in the generated code. The OnLoaded() method is called after the entity’s properties are set using the values retrieved during the execution of a LINQ query. It is invoked by the LINQ to SQL runtime if an implementation is provided. The OnValidate() method is called on an instance that is a part of the change during SubmitChanges() processing. It is invoked before the SQL commands for insert, update, and delete operations are executed. Hence, it provides a way to validate an entity that is about to be changed in the database, as shown in the following code example.

The per-property method pair allows you to hook in your code before and after the value is set. The following example shows how a change in Order.RequiredDate can be checked before setting the value and how the new value can be used to kick off additional processing:

partial void OnCreated()
{
    // Provide 5% discount by default

    this.Discount = (decimal)0.05;
}

partial void OnLoaded()
{
    // Increase the discount if this order is late
    if (this.ShippedDate == null && this.RequiredDate.Value <
        DateTime.Now)
        this.Discount = (decimal)0.10;
}

partial void OnValidate(ChangeAction action)
{
    // Check discount rules and throw in case of violation
}

partial void OnRequiredDateChanging(DateTime? value)
{
    if (this.ShippedDate != nullthrow new ArgumentOutOfRangeException();
}

partial void OnRequiredDateChanged()
{
    // Change freight and shipper accordingly
}

Writing Your Own Persistent Classes

The customization entry points are designed to take care of the most common cases with small, incremental work. However, if you have your own separate code generator, or you need to heavily customize your entity classes, you are free to do so while using the LINQ to SQL runtime. DataContext and entity classes generated by the designer or SqlMetal are meant to simplify your tasks—they are not essential for using LINQ to SQL runtime capabilities. In fact, Chapters 7 through 9 gave examples of handwritten (not designer-generated) classes with mappings in attributes. Let’s revisit the handwritten classes from Chapter 8, “Reading Objects with LINQ to SQL,” but this time with the mapping moved from .NET attributes to an external XML file.

Authors of handwritten classes often prefer to keep the classes free of attributes and use a different artifact such as an XML file to specify the mapping. In part, this is a matter of personal preference. However, in certain cases, it may be useful to modify the mapping file without changing the files containing the C# or VB.NET classes that are mapped in the mapping file. This may be true for trivial database schema changes such as renaming a column. In practice, many mapping changes due to database schema changes may require some changes in the mapped classes.

public class Customer
{
   public string CustomerID;
   public string Country;
   ...
   public List<Order> Orders;
}

public class Order
{
   public int OrderID;
   public string CustomerID;
   ...
   public Customer Customer;
}

The external XML mapping provides the same information as the mapping in attributes. The .NET attributes appear as XML elements, and properties of .NET attributes appear as XML attributes. The contextual information about the class or property that the .NET attribute is placed on is added to the mapping element because it is external to the entity classes. The following is a fragment of the mapping you can generate using SqlMetal. Specifically, most columns in the Customers table are omitted. So you need to use the longer mapping file generated using SqlMetal to run the C# code that appears after the mapping listing.

<?xml version="1.0encoding="utf-8?>
<Database Name="northwind"
 xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
  <Table Name="dbo.CustomersMember="Customers">
    <Type Name="Customers">
      <Column Name="CustomerIDMember="CustomerID"
       Storage="_CustomerIDDbType="NChar(5) NOT NULL"
       CanBeNull="falseIsPrimaryKey="true/>
      ...
      <Association Name="FK_Orders_CustomersMember="Orders"
       Storage="_OrdersThisKey="CustomerIDOtherKey="CustomerID"
       DeleteRule="NO ACTION/>
    </Type>

  </Table>
  <Table Name="dbo.OrdersMember="Orders">
    <Type Name="Orders">
      <Column Name="OrderIDMember="OrderIDStorage="_OrderID"
       DbType="Int NOT NULL IDENTITYIsPrimaryKey="true"
       IsDbGenerated="trueAutoSync="OnInsert/>
      <Column Name="CustomerIDMember="CustomerID"
       Storage="_CustomerIDDbType="NChar(5)/>
      ...
      <Association Name="FK_Orders_CustomersMember="Customer"
       Storage="_CustomerThisKey="CustomerID"
       OtherKey="CustomerIDIsForeignKey="true/>
    </Type>
  </Table>
</Database>

The mapping file can be specified using a DataContext constructor overload as follows. The code assumes that the mapping is stored in NorthwindMapping.xml and that a variable named connectionString contains the connection string for the Northwind database.

MappingSource ms = XmlMappingSource.FromXml("NorthwindMapping.xml");

NorthwindDataContext db =
   new NorthwindDataContext(connectionString, ms);

Just as the entity classes are free from LINQ to SQL concepts, you can also choose to wrap the DataContext so that all the objects used are your objects and LINQ to SQL just provides persistence service. This enables persistence ignorance, because your class is not tied to LINQ to SQL classes and generated code patterns. It can be used as is against different stores, including mocks created for testing.

There are some key differences between the preceding classes and the corresponding designer or SqlMetal-generated classes. Each difference has its own set of implications:

• There are no mapping attributes on classes and members; hence, external mapping needs to be supplied.

• It is up to you to decide which interfaces to implement and whether to use a base class. If you want to data-bind to a UI control, you need to implement INotifyPropertyChanged. If you want more efficient change tracking by ensuring that the original values are copied only in case of a modification, you need to implement INotifyPropertyChanging.

• Relationship members use List<T> and normal object reference instead of LINQ to SQL classes EntitySet and EntityRef that enable deferred loading. Hence, deferred loading is unavailable. Eager loading may still be specified.

• The generated code to keep the two ends of the relationships and the foreign key in sync is unavailable. The author of the class now has to provide this capability.

• Entity customization methods discussed in this chapter are not needed, because you are free to add whatever code you want in the constructors and in the property setters.

There is a trade-off between the convenience and the productivity of generated code versus the full flexibility of writing your own classes to suit your needs. However, it is important to remember that you can use your own entity classes if you want to.

Summary

LINQ to SQL provides a range of customization options for customizing entity classes and the DataContext. It also provides opportunities for you to add business logic. For the most common customization patterns, the generated code contains a set of partial classes, a set of partial methods, and calls to those partial methods. Partial classes allow you to add your own business logic in the form of nonpersistent properties and methods. You can provide partial method implementations for a DataContext class to control how entities are persisted or even loaded. Partial methods in entity classes can be implemented to add logic during key persistence-related points in the entity lifecycle.

LINQ to SQL also supports persistence ignorance if you want to author your own entity classes and control the persistence mechanism. The runtime effectively utilizes the patterns in the generated code but works well without them.

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

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