Export (0) Print
Expand All
4 out of 5 rated this helpful - Rate this topic

Making It Real: Connections, Transactions, Performance, and More

This chapter is excerpted from Programming Entity Framework: Building Data Centric Apps with the ADO.NET Entity Framework by Julia Lerman, published by O'Reilly Media

In previous chapters, you worked with bits and pieces of code and built small examples, but you did not build a real-world application. As such, you may be wondering how the Entity Framework addresses the everyday concerns of software developers. How do you control connections? Is there any connection pooling? Are database calls transactional? What about security? How's the performance? This chapter will address these and many of the additional questions developers ask after learning the basics of the Entity Framework.

EntityConnection and Database Connections in the Entity Framework

One of the benefits of using the Entity Framework is that it removes the need to write code to set up a database connection. Given that a connection string is available to the Entity Framework, most typically as part of the EntityConnectionString defined in a .config file, the Entity Framework will automatically set up, open, and close the database connection for you. Compared to typical ADO.NET code where you need to instantiate; define; and in many cases explicitly open a connection, instantiate and define a command, execute the command, and then explicitly close the connection, letting the ObjectContext handle all of this in the background as part of the query pipeline is certainly convenient. And this is the benefit you get in the default query scenarios. But oftentimes, you'll want more control over how and when connections are being made. To be able to do that, let's take a look at how the EntityConnection and DbConnection relate to each other. We'll also see how to programmatically force them to work the way you want if, in fact, the default behavior doesn't meet your needs.

EntityConnection Versus Database Connection

An EntityConnection is not a database connection. This can be a big point of confusion. Although you can open and close an EntityConnection, this does not mean you are opening and closing a connection to the database. Whether you use EntityClient directly or you let Object Services execute your commands and queries for you, the EntityConnection is just a path to the database connection.

An EntityConnection consists of four parts:

Metadata

The pointer to the metadata files (Conceptual Schema Definition Layer [CSDL], Mapping Schema Layer [MSL], and Store Schema Definition Layer [SSDL])

Provider connection

The database connection string

Provider name

The namespace of the database provider

Name

The name of the connection string

You can define the EntityConnection declaratively in the .config file. Example 16.1, "The EntityConnection string in an app.config or web.config file" lists the name of the connection string and then the EntityConnection string itself. Within the connection string, you can see the metadata parameter, the provider connection parameter, and the provider name. When the EDM Wizard builds this string for you, it replaces the quotes around the provider connection string with an escaped quote ("), which is the XML encoding for a quote. For readability, you can replace the escaped quotes with single quotes, as in Example 16.1, "The EntityConnection string in an app.config or web.config file".

Example 16.1. The EntityConnection string in an app.config or web.config file

<connectionStrings>
<add
 name="BreakAwayEntities"
 connectionString=
   "metadata=res://*/BAModel.csdl|res://*/BAModel.ssdl|
            res://*/BAModel.msl;
    provider=System.Data.SqlClient;
    provider connection string='Data Source=myserver;
                               Initial Catalog=BreakAway;
                               Integrated Security=True;
                               MultipleActiveResultSets=True'" 
    providerName="System.Data.EntityClient"
 />
</connectionStrings>

By default, an ObjectContext will use the connection string from the .config file that matches the EntityContainer name within your model. You have taken advantage of this in almost every code sample so far in the book, which is why you have not yet had to work explicitly with connection strings.

The database connection string that is embedded into the EntityConnection string is passed along to the database provider that eventually makes the actual connection to the database.

Development Database Versus Production Database

When you're moving from a development environment to a production environment, pointing to a new database can be as simple as changing the provider connection string parameter of the appropriate connection string in the .config file. You can also programmatically change the database connection string or modify connection strings on the fly.

Programming EntityConnection Strings

EntityConnection is a class within the EntityClient namespace. Earlier in the book, you worked directly with this class when using EntityClient for your queries. In the following code, the name of the connection string in the .config file is passed as a parameter (along with the parameter key name=) in the EntityConnection constructor. The connection in this case will be created from the details provided in the connection string:

Using conn As EntityConnection = New EntityConnection("name=BAEntities")
using (EntityConnecton conn = new EntityConnection("name=BAEntities");

You can use the preceding method to explicitly select a particular connection string from the .config file when instantiating an ObjectContext, as shown in the code that follows.

Dim context= New BAEntities("name=MyOtherConnectionString")
var context = new BAEntities("name=MyOtherConnectionString");

When you use this constructor for an EntityConnection or ObjectContext, the ConnectionString is not read in its entirety right away. In fact, if you inspect the EntityConnection in the debugger after it has been instantiated, you'll see that although the database connection object has been pulled into the StoreConnection property, the other parameters of the EntityConnectionString are nowhere to be found, as shown in Figure 16.1, "The EntityConnection object with no properties for the metadata or provider namespace attributes".

Figure 16.1. The EntityConnection object with no properties for the metadata or provider namespace attributes

The EntityConnection object with no properties for the metadata or provider namespace attributes

The metadata and provider namespace parameters are not displayed as properties of the EntityConnection class, and they will be accessed at the point in the query pipeline where EntityClient needs to read the Entity Data Model (EDM), and then again to determine which provider (e.g., Data.Sql.SqlClient) to which to pass the request for further processing.

If you do want to read the full connection string from the configuration file, you can use one of the .NET methods for reading data from a configuration file, such as System.Configuration.ConfigurationManager.

Using the EntityConnectionStringBuilder Class

You can programmatically construct an EntityConnectionString with the EntityConnectionStringBuilder, which inherits from DbConnectionStringBuilder. For example, you may store the location of your metadata files (.csdl, .msl, .ssdl) in a resource file and wish to programmatically change the EntityConnectionString to point to this location. Or you may want to programmatically change the ADO.NET DataProvider (e.g., System.Data.SqlClient) on the fly.

The code in Example 16.2, "Programmatically modifying an EntityConnectionString" reads the connection string from the configuration file into a string, creates an EntityConnectionStringBuilder from that string, modifies the Metadata property, and then instantiates an ObjectContext with the newly configured EntityConnectionString.

Note
For this example, a string for the path to the metadata files has been stored in the project's settings as MetadataFilePath. Its value is as follows:
C:\EFModels\Model.csdl|C:\EFModels\Model.ssdl|C:\EFModels\Model.msl

Example 16.2. Programmatically modifying an EntityConnectionString

Dim connstring = ConfigurationManager.ConnectionStrings.Item _
                 ("BAEntities").ConnectionString
Dim estringnew = New EntityConnectionStringBuilder(connstring)
With estringnew
  .Metadata = My.Settings.MetadataFilePath
  Dim context = New BAEntities(estringnew.ToString)
  Dim query = From con In context.Contacts _
              Where con.Addresses.Any(Function(a) a.City = "Seattle")
End With
var connstring = ConfigurationManager
                 .ConnectionStrings["BAEntities"].ConnectionString;
var estringnew = new EntityConnectionStringBuilder(connstring);
estringnew.Metadata = Properties.Settings.Default.MetadataFilePath; 
var context = new BAEntities(estringnew.ToString());
var query =
    from con in context.Contacts
    where con.Addresses.Any((a) => a.City == "Seattle")
    select con;
Note
The ConfigurationManager class can be tricky to find. You need to reference the System.Configuration namespace in your project; then you can get to System.Configuration.ConfigurationManager.

Unfortunately, the Metadata parameter is a string, so there's no strongly typed way to construct it. However, you can use one of the common DbConnectionStringBuilder classes, such as SqlConnectionStringBuilder, to programmatically construct the provider connection string (StoreConnection) of the EntityConnectionString.

Dynamic EntityConnections

One of the overloads for the EntityConnection constructor allows you to pass in a model that is in memory along with a database connection. This allows you to work with models that may not be stored in a particular file. For example, if you were to define different models and store them in a database, at runtime the code would determine which model to work with. You could then load the model's XML from the database into memory-for example, into an XMLReader-and then create an EntityConnection with the XMLReader. Once this connection has been instantiated, you can use it with an ObjectContext to query that model.

Note
The next version of Entity Framework (in Visual Studio 2010) will take advantage of the in-memory metadata with EF's new agile programming capabilities. This will be a scenario with EF that will not require developers to create a model in advance.

More is involved in this scenario because you will also need to have code that can determine what is in the model at runtime. The Entity Framework's MetadataWorkspace allows you to determine this, and as such create a completely dynamic application. See Chapter 17, Controlling Objects with ObjectStateManager and MetadataWorkspace for more about MetadataWorkspace.

Opening and Closing Entity and Database Connections

EntityConnection.Open loads the metadata files (to read the model) if they have not yet been loaded into application memory. This method calls the database provider's Connection.Open as well. EntityConnection.Close will, in turn, call the database connection's close method.

When an ObjectContext executes a query internally it creates an EntityConnection, and an EntityCommand then executes the command. As soon as the data has been consumed, whether you call a method such as ToList to read all of the data at once or you iterate through the data and come to the end, the context will close the EntityConnection, which in turn closes the database connection.

Opening and closing connections to the database is something that many developers fret about because we want to make the most efficient use of available resources. You may want to control when the open and close happen.

Manually opening and closing connections

When working with EntityClient, you need to explicitly create and open an EntityConnection before you can have your query executed.

When working with the ObjectContext directly or through LINQ to Entities, the default behavior is that the ObjectContext opens and closes connections as needed and as efficiently as possible. It is possible, however, to override that behavior and explicitly control when EntityConnection is opened and closed.

You have a few options here. You can manually open the connection and let it be closed implicitly when the context is disposed or you can manually open it and manually close it.

One of the advantages of opening and closing the connection yourself is that you can prevent the connection from being opened and closed numerous times when you are making a bunch of rapid-fire queries or performing a query followed by deferred loading.

You can see the difference in the following examples.

Default behavior 1: Many calls on a single connection

Example 16.3, "The initial query and subsequent loads executed on the same connection" performs a single query, iterates through the results, and calls Load and EntityCollection for some of the results. Each call to Load hits the database on the same connection because the context hasn't finished reading through the query results.

Example 16.3. The initial query and subsequent loads executed on the same connection

Using context As New BAEntities
  Dim cons = From con In context.Contacts Where con.FirstName = "Jose"
  For Each c In cons
    If c.AddDate < New Date(2007, 1, 1) Then
      c.Addresses.Load()
    End If
  Next
End Using
using (BAEntities context = new BAEntities())
{
  var cons =
      from con in context.Contacts
      where con.FirstName == "Jose"
      select con;
  foreach (var c in cons)
  {
    if (c.AddDate < new System.DateTime(2007, 1, 1))
    {
      c.Addresses.Load();
    }
  }
}

Only a single connection is used in this case because a connection is not closed until the results have been consumed. Therefore, because you are iterating through the resulting contacts, the connection remains open until you have reached the first contact. In the meantime, the additional calls to the database to load the addresses use that same connection. The MultipleActiveResultsSet setting in the connection string allows multiple streams to be read on the same connection. MultipleActiveResultsSet, also known as MARS, was introduced to ADO.NET in .NET 2.0.

Default behavior 2: Multiple connections

The set of queries in Example 16.4, "Two queries, each getting their own connection" opens and closes a connection twice. It closes the first connection when cons.ToList is called, as this forces the entire set of results to be consumed at once. Recall that a connection is disposed when its results have been fully consumed. Therefore, a new connection needs to be created for the second query.

Example 16.4. Two queries, each getting their own connection

Using context As New BAEntities
  Dim cons = From con In context.Contacts Where con.FirstName = "Jose"
  Dim conList = cons.ToList
  Dim allCustomers = From con In context.Contacts.OfType(Of Customer)(
  Dim allcustList = allCustomers.ToList
End Using
using (BAEntities context = new BAEntities())
{
  var cons = from con in context.Contacts where con.FirstName == "Jose"
             select con;
  var conList = cons.ToList();
  var allCustomers =  from con in context.Contacts.OfType<Customer>()
                      select con;
  var allcustList = allCustomers.ToList();
}
Forcing an explicit connection

To change the default behavior that happens in Example 16.4, "Two queries, each getting their own connection", you can force the connection to be reused by manually opening the connection as shown in Example 16.5, "Forcing queries to use the same connection". Then you can either explicitly close it or let the context automatically close it when the context goes out of scope. or let the garbage collector dispose it when the time comes.

Example 16.5. Forcing queries to use the same connection

Using context As New BAEntities
  context.Connection.Open()
  Dim cons = From con In context.Contacts Where con.FirstName = "Jose"
  Dim conList = cons.ToList
  Dim allCustomers = From con In context.Contacts.OfType(Of Customer)
  Dim allcustList = allCustomers.ToList
  context.Connection.Close()
End Using
using (BAEntities context = new BAEntities())
{
  context.Connection.Open();
  var cons = from con in context.Contacts where con.FirstName == "Jose"
             select con;
  var conList = cons.ToList();
  var allCustomers =  from con in context.Contacts.OfType<Customer>()
                      select con;
  var allcustList = allCustomers.ToList();
  context.Connection.Close();
}
Connection versus Connection.StoreConnection

Although ObjectContext.Connection returns the EntityConnection, you can drill deeper, as you saw in Figure 16.1, "The EntityConnection object with no properties for the metadata or provider namespace attributes", and get the actual database connection using EntityConnection's StoreConnection property.

If for some reason you want to have very granular control over the database connection, for example, by specifying the ConnectionTimeout, you can do so by working directly with the StoreConnection.

Taking Control of How Store Connections Are Disposed

As with any data access performed in .NET, it's important that you dispose the database connection, because it is not a managed resource and the garbage collector will not clean it up. Lingering database connections are a common cause of server resource issues. Again, when you rely on the Entity Framework's default behavior, the database connection will be properly disposed. Disposing the ObjectContext will automatically close the EntityConnection and will close and dispose the database connection. You can either explicitly dispose the ObjectContext or wait for the garbage collector to do the job. However, in the latter scenario, that means the database connection is still hanging around until that time.

In common usage scenarios with the Entity Framework, the worst offense (holding a connection open) should not be an issue, because as you have seen, the connection will be closed automatically. But if one of the triggers for closing a connection has not been executed-completing the consumption of query results, calling EntityConnection.Close, or disposing the ObjectContext-you could unwittingly be consuming extra resources.

ObjectContext's Dispose method calls EntityConnection.Dispose if ObjectContext created the connection. In turn, EntityConnection.Dispose will call the Dispose method on the StoreConnection. The code behind ObjectContext.Dispose is shown in Example 16.6, "The ObjectContext.Dispose method" so that you can see just how it works.

Example 16.6. The ObjectContext.Dispose method

Protected Overridable Sub Dispose(ByVal disposing As System.Boolean)
  If disposing Then
    If (Me._createdConnection AndAlso _
                              (Not Me._connection Is Nothing)) Then
        Me._connection.Dispose
    End If
    Me._connection = Nothing
    Me._adapter = Nothing
  End If
End Sub
protected virtual void Dispose(bool disposing)
{
  if (disposing)
  {
    if (this._createdConnection & (this._connection != null))
    {
      this._connection.Dispose();
    }
    this._connection = null;
    this._adapter = null;
  }
}
Note
An age-old debate in ADO.NET concerns whether you should close or dispose database connections. In fact, DbConnection.Close calls Dispose and DbConnection.Dispose calls Close. Close takes care of the critical connection resources, but the connection object itself is still there.

So, if you are using the defaults with LINQ to Entities or ObjectContext, the connection will be disposed. If you want to be sure the connection is disposed right away, you need to either explicitly make that call or be sure the ObjectContext is explicitly disposed. If you have created the EntityConnection explicitly, you have to either dispose it explicitly or wait for the garbage collector to dispose it; again, this in turn will dispose the database connection.

What About Connection Pooling?

Spinning up a database connection is expensive in terms of resources. When a connection is closed, it can be left in memory to be reused the next time a connection is required, eliminating the cost of creating a new connection. This is called connection pooling.

Developers often ask whether the Entity Framework does connection pooling. Because connection pooling is controlled by the database provider, the Entity Framework does not explicitly impact or interact with how connection pooling works. Instead, it relies on the provider's connection pooling. For more information on connection pooling in ADO.NET, a good starting point is the "SQL Server Connection Pooling (ADO.NET)" topic in the MSDN documentation.

Another question that is frequently asked about the Entity Framework is whether it uses transactions.

A transaction defines a unit of work that can contain a number of actions, such as database updates. When all of the actions have completed successfully, the transaction is committed. If any of the actions fail, the transaction is "rolled back," which causes all of the actions to roll back. Therefore, if you have actions that depend on each other and one action fails, you don't have to manually undo those that have already occurred.

Resources that provide the capability to process transactions, such as databases, can have their transactions be enlisted in .NET. Whether you have a number of updates on a single database connection within a single transaction, or you have a few of them combined with interactions on another database and possibly combined with work in message queuing, you can coordinate all of those individual transaction resource managers in a single transaction.

When performing a SaveChanges operation, the Entity Framework implicitly wraps all of the commands in a database transaction; however, you can take control of transactions as well.

Why Use Your Own Transaction?

Although the default use of DbTransaction takes care of operations on a single instance of a database connection, the TransactionScope class System.Transaction can coordinate operations across a variety of processes that use resource managers. Therefore, within a single transaction you could make calls to a database, to the Message Queue (MSMQ), or even to another database using ADO.NET. If one of those fails, System.Transaction will allow all of them to be rolled back together. System.Transaction leverages the Windows Distributed Transaction Coordinator (DTC) to make this happen, albeit with more overhead than a simple DbTransaction. But what is great about System.Transaction is that it will decide whether your actions need only the individual transaction (such as SQLTransaction), or whether they need to escalate to a DTC so that multiple transactions can be orchestrated. In that way, you don't needlessly waste resources with the DTC, but you also don't have to explicitly control it.

Warning
It's important to understand that the Entity Framework can only leverage transactions with database interaction. You cannot use transactions to control and roll back modifications to the ObjectContext itself-not even the creation of entities when performing a query.

Understanding the Entity Framework's Default: Implicit Transactions

The database constraint between Contact and Address in the BreakAway database makes a good test case for demonstrating the implicit transactions in the Entity Framework. An address cannot exist without a contact, yet no cascading delete is defined in the database to delete related addresses when a contact is deleted. Therefore, an attempt to delete a Contact entity without deleting its related addresses in code will cause the database to throw an error when SaveChanges is called. Let's take advantage of that and write some code to see the transaction in action.

The code in Example 16.7, "An implicit transaction that will roll back" queries for a particular contact, deletes it from the ObjectContext, and then calls SaveChanges. To add a twist, the code also creates a new payment for a reservation. Remember that when you attach the payment to the reservation in the context, SaveChanges automatically pulls the payment into the context and inserts it into the database.

Example 16.7. An implicit transaction that will roll back

Using context As New BAEntities
  Dim con = context.Contacts.Where _
             (Function(c) c.ContactID = 5).FirstOrDefault
  context.DeleteObject(con)
  Dim res = context.Reservations.FirstOrDefault
  Dim newPayment = New Payment
  With newPayment
    .Amount = "500"
    .PaymentDate = Now
    .Reservation = res
  End With
  context.SaveChanges()
End Using
using (BAEntities context = new BAEntities())
{
  var con = context.Contacts.Where(c => c.ContactID == 5)
                   .FirstOrDefault();
  context.DeleteObject(con);
  var res = context.Reservations.FirstOrDefault;
  var newPayment = new Payment();
  newPayment.Amount = "500";
  newPayment.PaymentDate = System.DateTime.Now;
  newPayment.Reservation = res;
  context.SaveChanges();
}

The attempt to delete the contact from the database will fail because of the referential constraint. Figure 16.2, "The Entity Framework automatically forcing a rollback if any of the commands to the database fail", a screenshot from SQL Profiler, shows what happens when SaveChanges is called.

Figure 16.2. The Entity Framework automatically forcing a rollback if any of the commands to the database fail

The Entity Framework automatically forcing a rollback if any of the commands to the database fail

A transaction was created, and because the delete failed, the transaction is rolled back and the insert for the payment is not even bothered with.

On the client side, an exception is thrown containing the error from the database, which offers a very clear description of the problem:

"The DELETE statement conflicted with the REFERENCE constraint 
"FK_Address_Contact". The conflict occurred in database "BreakAway", table
"dbo.Address", column 'ContactID'. The statement has been terminated."

This highlights a good reason to be sure to include exception handling around SaveChanges in cases where any constraints in the database are not constrained in advance in the model or in the application.

In this example, SaveChanges caused two commands to be executed. Even if SaveChanges created only one command, it would still be wrapped in a database transaction.

Where did the transaction come from?

A DbTransaction is created within the SaveChanges method. If no exceptions are thrown during the actual command execution, DbTransaction.Commit is called.

Controlling AcceptAllChanges in a transaction

ObjectContext.AcceptAllChanges updates the object state of all of the entities being change-tracked. This will set the OriginalValues to whatever the current values are and it will change their EntityState to Unchanged.

During the SaveChanges process and after the transaction has been committed, AcceptAllChanges is automatically called, causing the ObjectContext to be up-to-date and its entities to match the data in the database.

It's possible to indicate in the SaveChanges call that SaveChanges should not call AcceptAllChanges when the save is complete. All you need to do is pass a Boolean of False as a parameter. The default is True, and you do not need to explicitly call it if you want the default behavior.

ObjectContext.SaveChanges(false)

If you override the default, you can then control when and (if necessary) how many times AcceptAllChanges should occur.

This is especially useful when you're using your own transaction, since you may want to retry the save or just call AcceptAllChanges even when the transaction did not complete.

Specifying Your Own Transaction

Just as you can override the default behavior with connections, you can also control transaction functionality. If you explicitly create your own transaction, SaveChanges will not create a DbTransaction. You won't create a System.Common.DbTransaction, though. Instead, when creating your own transactions, you need to use a System.Transaction.TransactionScope object.

You can use a transaction for read and write activities in the database, which means that this will work with both ObjectContext and EntityClient.

Note
Remember that if you are using LINQ to Entities and you want to take advantage of ObjectContext behavior, you can cast the LINQ to Entities query to an ObjectQuery, as you learned in Chapter 9, Working with Object Services.

Example 16.8, "Creating your own System.Transaction for SaveChanges" uses an explicit transaction to save a new customer to a database and, if the call to SaveChanges is successful, to add the customer's name to a completely separate database. The application has references to two different projects with EDMs. If something goes wrong with either database update, the TransactionScope will not be completed and both updates will be rolled back.

Note
You'll need to create a reference in your project to System.Transactions.

Example 16.8. Creating your own System.Transaction for SaveChanges

Using context As New BreakAwayEntities
  Dim cust As Customer = Customer.CreateCustomer _
   ("George", "Jetson", "A real space cadet",New DateTime(1962,1,1))
  context.AddToContacts(cust)
  Using tran As New TransactionScope
    Try
      context.SaveChanges(False)
      Using altcontext As New altDBEntities
        altcontext.AddToContact(Contact _
         .CreateContact(cust.LastName.Trim & ", " & cust.FirstName))
        altcontext.SaveChanges()
      End Using
      tran.Complete()
      context.AcceptAllChanges()
    Catch ex As Exception
       'throw or handle database or Entity Framework exceptions
        Throw
    End Try
  End Using
End Using
using (var context = new BAEntities())
{
  Customer cust = Customer.CreateCustomer
   ("George", "Jetson", "A real space cadet",new DateTime(1962,1,1));
  context.AddToContacts(cust);
  using (TransactionScope tran = new TransactionScope())
  {
    try
    {
      context.SaveChanges(false);
      using (altDBEntities altcontext = new altDBEntities())
      {
        altcontext.AddToContact(Contact
        .CreateContact(cust.LastName.Trim() + ", " + cust.FirstName));
        altcontext.SaveChanges();
      }
      tran.Complete();
      context.AcceptAllChanges();
    }
    catch
    {
      //throw or handle database of Entity Framework exceptions
      throw;
    }
  }
}

You can watch the transaction being promoted in a few ways. For example, in SQL Profiler, you can see that System.Transaction starts out by using a simple database transaction, but as soon as it hits the call to SaveChanges to a different database, the transaction is promoted (see Figure 16.3, "SQL Profiler showing that a database transaction is used at first, but is then promoted when another database connection is made within the scope of a System.Transactions.TransactionScope").

Figure 16.3. SQL Profiler showing that a database transaction is used at first, but is then promoted when another database connection is made within the scope of a System.Transactions.TransactionScope

SQL Profiler showing that a database transaction is used at first, but is then promoted when another database connection is made within the scope of a System.Transactions.TransactionScope

You can also add a variety of performance counters into the Windows Performance Monitor that tracks the DTC and you can see whether a transaction was created, completed, or even rolled back.

Note
If you are testing on a development machine, chances are you don't have the DTC services started. When the code reaches the second SaveChanges and .NET attempts to promote the transaction to use the DTC, if the DTC is not started you will receive an exception telling you that the DTC has not started on the system. You can start this service through the Computer Management console in Windows.

The last way you can prove this is working is to force one of the updates to fail. You can see the rollback in the Profiler, or even just look in the database to verify that the changes have not been made.

Reading Queries Using System.Transaction or EntityTransaction

It is also possible to use a transaction on a read-only query using System.Transaction or EntityClient.EntityTransaction. An EntityTransaction is merely a wrapper for the database provider's transaction, and call EntityConnection.BeginTransaction to create it, as shown in Example 16.9, "Using a transaction on a read to control whether the read will read data that is in the process of being modified in the database".

Example 16.9. Using a transaction on a read to control whether the read will read data that is in the process of being modified in the database

Using econ = New EntityConnection("name=BAEntities")
  Dim eTran As EntityTransaction = _
   econ.BeginTransaction(IsolationLevel.ReadUncommitted)
   econ.Open()
   Dim eCmd = econ.CreateCommand
   eCmd.CommandText = _
    "SELECT  con.contactID FROM BreakAwayEntities.Contacts AS con"
   Dim dr = eCmd.ExecuteReader(CommandBehavior.SequentialAccess)
   While dr.Read
      'do something with the data
   End While
   eTran.Commit()
End Using
using (var econ = new EntityConnection("name=BAEntities"))
{
  EntityTransaction eTran = 
   econ.BeginTransaction(IsolationLevel.ReadUncommitted);
  econ.Open();
  var eCmd = econ.CreateCommand();
  eCmd.CommandText = 
   "SELECT  con.contactID FROM BreakAwayEntities.Contacts AS con";
  var dr = eCmd.ExecuteReader(CommandBehavior.SequentialAccess);
  while (dr.Read())
  {
     //do something with the data;
  }
  eTran.Commit();
}

At first glance, it may not make sense to have a transaction on a read, since you can't roll back a read. The purpose of performing a read within a transaction is to control how to read data in the database that may be involved in another transaction at the same time. Notice the IsolationLevel.ReadUncommitted parameter being passed in. IsolationLevel lets you determine how your query should read data that some other person or process is currently updating. The ReadUncommitted enum says that it is OK for this query to read data that is being modified, even if it has not yet been committed in the other transaction. The other possibilities are Serializable, RepeatableRead, ReadCommitted, Snapshot, Chaos, and Unspecified. You can check the docs to learn more about these IsolationLevels, which are not specific to the Entity Framework.

Although you can use EntityTransaction directly, it is recommended that you use System.Transaction.TransactionScope where you can also determine IsolationLevel. In that case, you would wrap the query (EntityClient, LINQ to Entities, or ObjectQuery) within a TransactionScope, just as in the previous example, which used TransactionScope for SaveChanges.

Note
Distributed transactions are more expensive to process, and often the events that cause a transaction to be promoted do not really require the extra cost of the DTC. Improvements were made in SqlClient so that transactions are escalated more wisely when using SQL Server 2008. Prior to SQL Server 2008, it helps to explicitly open the connection after creating the transaction. To read more about this, see the ADO.NET Team blog post "Extending Lightweight Transactions in SqlClient," at http://blogs.msdn.com/adonet/archive/2008/03/26/extending-lightweight-transactions-in-sqlclient.aspx/.

Can You Use Transactions Within ObjectContext?

People often ask about the ability to roll back changes to entities in the context. Unfortunately, Object Services does not have a mechanism to achieve this. If you want to roll all the way back to the server values, you can use ObjectContext.Refresh to reset specific entities or a collection of entities, but you cannot do a thorough refresh of everything in the context. You'll learn more about refresh in Chapter 17, Controlling Objects with ObjectStateManager and MetadataWorkspace. Alternatively, you can dispose the context, create a new one, and requery the data. But still, this is not the same as rolling back to a previous state of the entities; all you're doing is getting fresh data from the store.

If you want to persist the state of your entities at any given point in time and then restore them into the context, you'll need a better understanding of the ObjectStateManager, which we will cover in detail in Chapter 17, Controlling Objects with ObjectStateManager and MetadataWorkspace.

Unfortunately, at this time no pattern is available for pulling this off, but eventually someone from Microsoft or the development community will create and share a pattern to solve this. Hopefully, we'll see this feature added to version 2 of the Entity Framework.

Security is an important issue to be concerned with, and it is the subject of frequently asked questions regarding the Entity Framework, mostly due to database access.

If you were to look at the security topic in the MSDN documentation (see the topic "Security Considerations [Entity Framework]"), you might find the lengthy list of items covered to be daunting. But on more careful inspection, you would see that most of the points are generic to programming and to data access, with only a few items pertaining specifically to the Entity Framework.

The most frequently asked security topic in the Entity Framework concerns SQL injection. Another security issue of interest is the fact that developers can piggyback onto the Entity Framework's database connections. I will discuss these two scenarios in this chapter. Check the aforementioned MSDN topic for additional security topics.

SQL Injection

SQL injection attacks are one of the most worrisome problems for data developers. An injection occurs when an end user is able to append actual query syntax in data entry form fields that can damage your data (e.g., delete table x) or access information by piggybacking on the executed command.

Note
Wikipedia has a handy tutorial on SQL injection if you want to learn more. See http://en.wikipedia.org/wiki/SQL_injection/.

SQL injection can occur when you build queries dynamically in your code. For example:

QueryString="select * from users where username='" & TextBox.Text & "'"

Therefore, it is always recommended that programmers avoid building dynamic queries. Instead, we use parameterized queries or leverage stored procedures from our data access code.

Because we have been trained to have an inherent fear of dynamic queries, on the surface the fact that the Entity Framework (and LINQ to SQL, for that matter) builds queries for us raises a big red flag.

You're safe with LINQ, but be careful with Entity SQL

You do not have to worry when you are using LINQ to Entities (or LINQ to SQL). The queries that eventually land in your data store for execution are definitely parameterized queries, not dynamic ones. You've seen that throughout this book.

And of course, you can always use stored procedures, which are the ultimate way to avoid SQL injection attacks.

You'll need to be much more careful with Entity SQL. Entity SQL is broken down differently than LINQ to Entities, and the queries that result are composed differently.

Let's look at the difference between a few queries in which it might be possible to inject some debilitating SQL by way of a text box in a data entry form.

Here is a LINQ to Entities query:

From loc In context.Locations Where loc.LocationName = textBox.Text

When textbox.Text=Norway, the T-SQL that results is parameterized:

SELECT 
[Extent1].[LocationID] AS [LocationID], 
[Extent1].[LocationName] AS [LocationName]
FROM [dbo].[Locations] AS [Extent1]
WHERE [Extent1].[LocationName] = @p__linq__1

@p__linq__1='Norway'

Similarly, when textbox.Text= a' OR 't'='t (a classic injection attack), the native query still puts this "value" into a single parameter, and the injection is unsuccessful:

SELECT 
[Extent1].[LocationID] AS [LocationID], 
[Extent1].[LocationName] AS [LocationName]
FROM [dbo].[Locations] AS [Extent1]
WHERE [Extent1].[LocationName] = @p__linq__1

@p__linq__1='a'' OR ''t''=''t'

However, the same query in Entity SQL looks like this:

SELECT VALUE loc FROM BreakAwayEntities.Locations AS loc
WHERE loc.LocationName='" & city & "'"

With Norway, the T-SQL is benign:

SELECT
[Extent1].[LocationID] AS [LocationID],
[Extent1].[LocationName] AS [LocationName]
FROM [dbo].[Locations] AS [Extent1]
WHERE [Extent1].[LocationName] = 'Norway'

but the injection succeeds. Here is the T-SQL:

SELECT
[Extent1].[LocationID] AS [LocationID],
[Extent1].[LocationName] AS [LocationName]
FROM [dbo].[Locations] AS [Extent1]
WHERE ([Extent1].[LocationName] = 'a') OR ('t' = 't')

Getting a list of all of the cities is still somewhat benign, but the point is that you have just lost control of your query.

These types of attacks are not as easy to pull off with Entity SQL as they are when composing native queries in ADO.NET, because the injection needs to be valid Entity SQL syntax and valid native SQL syntax at the same time. Therefore, an attack using this method:

"a' ; SELECT * FROM LOGINS"

or even this one:

"a' UNION ALL (SELECT value log from entities.logins as log)"

will fail because the Entity SQL command text will be invalid in both cases.

Entity SQL injection

Injecting SQL that goes to the store is one problem. What about injecting Entity SQL into an Entity SQL string? Again, this is possible. Imagine appending a JOIN clause to your Entity SQL, followed by an Entity SQL expression that selects logins and passwords. The user only needs access to your EDM files to know the structure of the model and to figure out what your queries might look like to append the right string to get at the data she is looking for.

It may not sound very easy to do, but some people spend a lot of time figuring out how to crack into our applications, and that is who you need to worry about.

Therefore, as with any other data access that is dependent on user input, you need to validate all user input before inserting it into your queries; and you need to be very thoughtful regarding where and when you concatenate strings to build Entity SQL queries. Don't forget that you can use ObjectEntityParameters when building queries with ObjectContext and EntityClient.

Protecting Data from Connection Piggybacks

Although your model might limit what parts of your database a user has access to, it does make a connection to the database, providing an open door to users who might not otherwise have access to the database.

As you saw in the section called "EntityConnection and Database Connections in the Entity Framework", it is possible to get at the database connection through an EntityConnection; therefore, a developer writing queries against the model could easily execute his own commands by using the existing connection. This would enable him to access data that is not even part of the model.

Consider the code in Example 16.10, "Using the EntityConnection to make an ADO.NET call to the database" where the developer uses the connection from the context to return the employee data from the database.

Example 16.10. Using the EntityConnection to make an ADO.NET call to the database

Using context As New MyEntities
  Dim query = From con In context.Contacts Take 10
  Dim conn As EntityConnection = context.Connection
  Dim dbconn As SqlConnection = conn.StoreConnection
  conn.Open()
  Dim sqlcmd = New SqlCommand("Select * from HR.Employees", dbconn)
  Dim dr As SqlClient.SqlDataReader = sqlcmd.ExecuteReader
  While dr.Read
    Console.WriteLine(dr.Item("SocialSecurityNumber"))
  End While
End Using
using (MyEntities context = new MyEntities())
{
  var query = (from con in context.Contacts
               select con).Take(10);
  EntityConnection conn = context.Connection;
  SqlConnection dbconn = conn.StoreConnection;
  conn.Open();
  var sqlcmd = new SqlCommand("Select * from HR.Employees", dbconn);
  SqlClient.SqlDataReader dr = sqlcmd.ExecuteReader();
  while (dr.Read())
  {
    Console.WriteLine(dr["SocialSecurityNumber"]);
  }
}

Even worse, with the connection string, any type of command against the database can be executed, not just queries.

Although the developer may not necessarily have access to the connection string being used for the EDM queries-for example, the connection string may be encrypted-he can use this connection and any of the permissions associated with the login.

This type of abuse is not particular to the Entity Framework, but it's important to be aware that the Entity Framework doesn't prevent it. As with any data access scenario, applying permissions carefully in your database can help you avoid this situation.

"What about performance?" is another frequently asked question and a completely valid concern.

There's no question that when you introduce layers into your application, performance will be impacted. Using ADO.NET to stream data directly from the database into your hands is certainly going to be faster than having a query interpreted and transformed and then having the returned data transformed again. You can do some things to help, and they can be hugely beneficial, but when comparing Entity Framework queries to "classic" ADO.NET queries or even LINQ to SQL, you are definitely paying a price for the benefits you gain.

A Few "Backyard Benchmarks"

Following are some tests to give you a feel for the difference in performance (speed) between the Entity Framework, classic ADO.NET, and LINQ to SQL, because that's an important comparison as well.

Note
Backyard benchmarks is my own term for identifying that these are simple tests that I conducted on my computer and that do not represent any official benchmarks from Microsoft or follow any type of official benchmarking guideline, if any even exists. The numbers are meant only to provide some relative comparisons between the Entity Framework, ADO.NET, and LINQ to SQL. Here are the specs of the computer used for these tests:
  • Intel Core 2 Duo CPU, E4600 at 2.4 GHz

  • 6 GB of RAM

  • Windows Vista Ultimate SP 1 64-bit operating system

Each test presents the average time it takes to process and return a query of the AdventureWorksLT Customer table 100 times. The tests are designed so that the processes will be comparable. For example, with the DataReader test, the code performs 100 individual queries, opening and closing a connection for each query. In the LINQ to Entities and ObjectContext tests, the sample instantiates a new context and performs 100 queries on that context. With the two Entity Framework queries, the default connection is being used; therefore, the Entity Framework will open a new connection for each query and then close the connection when the results have been iterated through. This is why the DataReader tests open and close the connection each time as well. The fourth test performs the same query using LINQ to SQL, which also opens and closes a connection for each query.

In each test, the loop of 100 queries runs twice. The first time is to "prime the pump" so that any performance advantages provided by repeated queries are evened out between the various tests. The second set of 100 tests is used to gather the timings. In each test, the results are iterated through completely. The time quoted is not the time it took to perform a single query. It is the time it took to perform 100 queries, opening and closing the connection 100 times. It was a nice coincidence that the DataReader test resulted in an even 100 ms, making it easier to compare the other results.

Note
For the Entity Framework queries, the metadata files were preloaded so that you do not see the cost of that in any of the results. Loading the metadata files happens only once during the lifetime of an application.

Table 16.1, "Comparison of relative times for different methods of querying" compares the relative times for the different methods of querying. In the following section, I interpret the results as well as list the code used to generate the results (see Examples 16.11 through 16.15).

Table 16.1. Comparison of relative times for different methods of querying

Access type

Time for 100 queries

Difference from base

DataReader (base)

100 ms

--

LINQ to Entities

320 ms

+ 32%

Entity SQL with ObjectQuery

108 ms

+ 8%

Entity SQL with EntityClient

412 ms

+ 41%

LINQ to SQL

207 ms

+ 20%


Interpreting the tests

It makes sense that the DataReader would be the fastest, as it has direct access to the database. It reads data directly from the database and streams it out to the client application. Therefore, this becomes the base for comparison.

LINQ to Entities goes through a number of transformations prior to hitting the database, and the returned results need to be materialized, so this requires an extra hit.

A query written in Entity SQL has one less transformation to go through before hitting the database, but whether you start with LINQ to Entities or an ObjectQuery, a number of expensive tasks need to be performed. The object materialization on the results incurs the same cost as using LINQ to Entities.

If you look at the time required for the individual queries it's interesting to note the cost of the first query for each query method. The results shown in Table 16.2, "Differences in time between first and subsequent query calls" display the times for the first and second queries compared to the average of all 100 queries.

Table 16.2. Differences in time between first and subsequent query calls

Access type

First query

Second query

100-query average

DataReader (base)

1 ms

1 ms

1 ms

LINQ to Entities

9 ms

4 ms

3.2 ms

Entity SQL with ObjectQuery

6 ms

1 ms

1.1 ms

Entity SQL with EntityClient

13 ms

4 ms

4.1 ms

LINQ to SQL

7 ms

2 ms

2.1 ms


These results show the initial cost of the first query, whether you are using a DataReader, an EDM, or LINQ to SQL. The LINQ to Entities query has to do the additional work of creating the LINQ command tree, which is then sent to Object Services.

Table 16.3, "A new set of tests comparing only the EDM queries" shows a comparison of just the three Entity Framework queries. Each is run in its own application; therefore, each must load the metadata on the first query.

Table 16.3. A new set of tests comparing only the EDM queries

Access type

First EDM query in application

LINQ to Entities

703 ms

Entity SQL with ObjectQuery

638 ms

Entity SQL with EntityClient

597 ms


Why did these queries take so long?

In all three queries, a lot of up-front expense occurs in query compilation-getting from the original query to the native query.

The first is something that happens only once during the lifetime of an application-loading the EDM metadata into the ObjectContext. However, the metadata is also loaded into the application memory, so subsequent queries throughout the application do not have to load the metadata again. Because each of these tests is the first query in a newly running application instance, each of them incurs the cost of loading the metadata.

Additionally, with LINQ to Entities and ObjectQuery, other operations occur, such as the creation of ObjectStateEntries for entities and for relationships. On the way back, the results need to be either materialized as objects with LINQ to Entities and ObjectQuery, or transformed into an EntityDataReader with an EntityClient query. So, this is an expense you pay during the query, rather than later (in effort and processing time), as you would have to do when creating objects from a DataReader or dealing with relationships in DataTables.

It's interesting to see that the EntityClient test, which does not materialize objects, is slower than the ObjectQuery test. Even though the objects are not being created, EntityClient still needs to transform the data store results into the structure of the entities that it is returning.

Note
Because these tests are somewhat lengthy, the C# version is provided here, and both the C# and Visual Basic versions will be available on the book's website.

Example 16.11. The DataReader performance test

private static void DataReaderTest(string connstring)
{
  List<decimal> testresults = new List<decimal>();
  string cmdText = "select CustomerID, NameStyle, Title, FirstName," +
                   "MiddleName, LastName,Suffix,CompanyName, " +
                   "SalesPerson, EmailAddress,Phone,PasswordHash, " +
                   "PasswordSalt, rowguid, ModifiedDate " +
                   "FROM SalesLT.Customer";
  // start the timer
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  for (int i = 0; i < 2; i++)
  {
    testresults.Clear();
    SqlConnection sqlCon = new SqlConnection(connstring);
    for (int j = 0; j < 100; j++)
    {
      sw.Reset();
      sw.Start(); //timing the whole loop of 100 queries
      sqlCon.Open();
      SqlCommand cmd = new SqlCommand(cmdText, sqlCon);
      SqlDataReader reader = cmd.ExecuteReader();
      while (reader.Read())
      {
        object val = reader.GetValue(2);
      }
      reader.Close();
      sqlCon.Close();
    }
    sw.Stop();
    testresults.Add((decimal)sw.ElapsedMilliseconds);
  }
  // return second set of results
  Console.WriteLine("DataReader: {0}ms", testsresults[1])
}

Example 16.12. The LINQ to Entities performance test

private static void LINQtoEntitiesTest()
{
  List<decimal> testresults = new List<decimal>();
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  for (int i = 0; i < 2; i++)
  {
    testresults.Clear();
    AWLTEntities edmAW = new AWLTEntities();
    for (int j = 0; j < 100; j++)
    {
      sw.Reset();
      sw.Start();
      var customers = from c in edmAW.EFCustomers select c;
      foreach (EFCustomer cust in customers)
      {
        object o = cust;
      }
    }
    sw.Stop();
    testresults.Add((decimal)sw.ElapsedMilliseconds);
  } //end for loop
  //toss first result, calc average of rest
  Console.WriteLine("DataReader: {0}ms", testsresults[1])
}

Example 16.13. The Entity SQL ObjectQuery performance test

private static void ESQLQueryTest()
{
  List<decimal> testresults = new List<decimal>();
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  for (int i = 0; i < 2; i++)
  {
    testresults.Clear();
    AWLTEntities AWL2E = new AWLTEntities();
    for (int j = 0; j < 100; j++)
    sw.Reset();
    sw.Start();
    {
      string esql = "SELECT VALUE c from AWLTEntities.EFCustomers AS c";
      ObjectQuery<EFCustomer> customers =
                       AWL2E.CreateQuery<EFCustomer>(esql);
      foreach (EFCustomer cust in customers)
      {
        object c = cust;
      }
    }
    sw.Stop();
    testresults.Add((decimal)sw.ElapsedMilliseconds);
  }
  Console.WriteLine("DataReader: {0}ms", testsresults[1])
}

Example 16.14. The Entity SQL EntityClient performance test

private static decimal EntityClientTest()

{
  List<decimal> testresults = new List<decimal>();
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  for (int i = 0; i < 2; i++)
  {
    testresults.Clear();
    AWLTEntities AWL2E = new AWLTEntities();
    EntityConnection eConn = new EntityConnection("name=AWLTEntities");
    for (int j = 0; j < 100; j++)
    {
      sw.Reset();
      sw.Start();
      string esql = "SELECT VALUE c from AWLTEntities.EFCustomers AS c";
      EntityCommand eCmd = new EntityCommand(esql, eConn);
      eConn.Open();
      EntityDataReader eReader=
                 eCmd.ExecuteReader(CommandBehavior.SequentialAccess);
      while (eReader.Read())
      {
        object val = eReader.GetValue(2);
      }
      eReader.Close();
      eConn.Close();
    }
    sw.Stop();
    testresults.Add((decimal)sw.ElapsedMilliseconds);
  }
  Console.WriteLine("DataReader: {0}ms", testsresults[1])
}

Example 16.15. The LINQ to SQL performance test

private static decimal LINQtoSQLTest()
{
  List<decimal> testresults = new List<decimal>();
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

  for (int i = 0; i < iOuterLoop; i++)
  {
    testresults.Clear();
    AWLTDataContext AWL2SContext = new AWLTDataContext();
    for (int j = 0; j < iInnerloop; j++)
    {
      sw.Reset();
      sw.Start();
      var query = from c in AWL2SContext.L2SCustomers select c;
      foreach (L2SCustomer cust in query)
      {
        object c = cust;
      }
    }
    sw.Stop();
    testresults.Add((decimal)sw.ElapsedMilliseconds);
  } //end for loop
  Console.WriteLine("DataReader: {0}ms", testsresults[1])
}

Reducing the Cost of Query Compilation

In an early 2008 blog post titled "Exploring the Performance of the ADO.NET Entity Framework-Part 1" (http://blogs.msdn.com/adonet/archive/2008/02/04/exploring-the-performance-of-the-ado-net-entity-framework-part-1.aspx/), Brian Dawson of the Entity Framework team breaks down query time by task. In his tests, 56 percent of the total time for processing a query is devoted to "view generation." View generation refers to the process of creating the native command from an Entity SQL ObjectQuery or a call to SaveChanges. Fifty-six percent!

Here's a quick refresher on what's going on during this process. The Entity SQL is broken down into a command tree comprising Entity SQL operators and functions with entity names, properties, and relationships. This command tree is sent to the data provider, which translates the Entity SQL operators and functions to native operators and functions and uses the EDM to translate the entities and properties to tables and columns. Because the original query might be too complex for the native query, a series of simplifications is also performed on the tree. Finally, this newly created command tree is sent to the database.

This is a lot of work. But it doesn't necessarily need to happen on the fly at runtime. Given the queries and the EDM, the native queries can be precompiled. You can take advantage of query precompilation in two ways: precompiled views and precompiled LINQ to Entities queries.

The EDM Generator for Precompiled Views (and More)

The EDM Generator, a command-line tool (EDMGen.exe), allows you to perform many of the same tasks that the EDM Wizard performs, as well as some others.

The EDM Generator has five command-line switches, which you can use to do the following:

/mode:FromSSDLGeneration

Generates CSDL and MSL EDM files from an existing SSDL file

/mode:EntityClassGeneration

Generates classes from a CSDL file

/mode:ValidateArtifacts

Validates an EDM

/mode:ViewGeneration

Precompiles queries from a specified project into a source code file

/mode:FullGeneration

Creates CSDL, MSL, and SSDL files from a database, and generates the object classes and precompiled queries for each entity and relationship

Note
Try out FullGeneration on a database so that you can see what the output looks like. It's quick and painless. All you need to pass in is a connection string and the project parameter to give it a name that will be used for all of the created files:
C:\Program Files\Microsoft Visual Studio 9.0\VC> 
  edmgen /mode:FullGeneration
   /c:"Data Source=127.0.0.1;
       Initial Catalog=AdventureWorksLT;
       Integrated Security=True"
   /p:AWEDMGenTest
Note
You can add other parameters, such as a Language parameter, to create Visual Basic files.Here are the files that result:
  • AWEDMGenTest.csdl

  • AWEDMGenTest.ssdl

  • AWEDMGenTest.msl

  • AWEDMGenTest.ObjectLayer.cs

  • AWEDMGenTest.Views.cs

What's in a precompiled view code file?

Pregenerating views in the full generation will create views for each EntitySet and association. For example, the Views class for the FullGeneration example in the preceding note will create a view for dbo.Customers that will be used anytime a query is made that involves customers. FK_SalesOrderHeader_Customer_CustomerID association also has a view that will be used anytime that association is required. It contains the necessary joins between the Customer table and the SalesOrderHeader table.

Precompiling views against an existing project

You can also target a project when precompiling views. However, be aware that EDMGen will not precompile any queries that are in the project. Only the model's EntitySets get compiled. The purpose of targeting a project when precompiling views is so that the project's namespace gets used in the generated code.

To see view generation working against an existing project, pick a project against which to test this-for example, the BreakAway WCF service you created in Chapter 15, Working with Relationships and Associations.

The ViewGeneration option requires SSDL, MSL, and CSDL files that you don't actually have because you have been embedding them into the compiled assemblies. So, you'll need to go back to the BreakAwayModel project and generate these files:

  1. Open the BreakAwayModel project if it's not already open.

  2. Open the EDMX file in the Designer.

  3. Click the background of the model to open the model's Properties window.

  4. Change the Metadata Artifact Processing property to Copy to Output Directory.

  5. Save the project. This will create the files.

  6. Open the project's output directory in Windows Explorer.

    You can do this directly from the Solution Explorer by right-clicking the project and choosing Open Folder in Windows Explorer, then navigating to the output folder.

  7. Copy the CSDL, SSDL, and MSL files to another location on your drive (e.g., c:\EDMs).

    When you change the Metadata Artifact Processing property back to Embed in Output Assembly, the files will be removed from the output directory.

Now you can generate the view file. Note in Example 16.16, "Using the EDM Generator command-line tool" that the quotes around the project are there only because of a space in the file path.

Example 16.16. Using the EDM Generator command-line tool

C:\Program Files\Microsoft Visual Studio 9.0\VC>
edmgen /mode:ViewGeneration 
 /inssdl:c:\efmodels\BreakAwayModel.ssdl 
 /incsdl:c:\efModels\BreakAwayModel.csdl 
 /inmsl:c:\efmodels\BreakAwayModel.msl 
 /p:"D:\VS2008\projects\_EFBOOK Samples\BreakAwayWCFService\
     BreakAwayWCFService.vbproj"
/language:VB

You'll find the newly generated file in the folder designated in the p (path) parameter. Be sure to include the file in the project in Solution Explorer. Again, it contains all the views that are represented in the model files. Now when you run this project, the runtime will be able to skip the bulk of the quey compilation tasks.

Note
Because of the length of the generated code, I will not display it here. You can find sample Views files on the Downloads page of the book's website. The generated views are essentially strings containing native store commands.

Precompiled LINQ to Entities Queries

Although the view generation feature lets you create the native SQL for all of the model's EntitySets and associations, there's also a way to precompile the actual queries that you create in your application. This happens at runtime. For LINQ to Entities queries, you can explicitly precompile your queries using the CompiledQuery.Compile method. Entity SQL queries, whether called through EntityClient or through ObjectQuery, can be implicitly compiled and stored in a cache based on a setting that enables or disables query plan caching.

LINQ to Entities compiled queries

CompiledQuery.Compile allows you to compile a particular query, even one that takes parameters, at runtime. Then, anytime you need to use that query, you can point to the compiled version.

Note
Query compilation is also available in LINQ to SQL, though the syntax is a bit different.

Compiled queries can make a valuable performance improvement for queries that are used repeatedly in an application. You will still pay the compilation cost the first time the query is used, but subsequent uses of the query will avoid that part of the process.

The code for creating compiled queries may seem a bit daunting at first because it takes advantage of functional programming. LINQ is based on functional programming, and more and more programmers are waking up to the benefits of it. Although this technique takes a bit of getting used to, it can be addictive once you get past the learning curve.

The Entity Framework has a System.Data.Objects.CompiledQuery class, which lets you precompile a query into a CompiledQuery object and then reuse that object. CompiledQuery.Compile takes two parameters and a query in the form of a delegate:

Compile(args, ReturnType) (Delegate Query)

The first parameter is args used to pass in any arguments. You'll want to pass in an instance of an ObjectContext and then any other variables that are used in the query. For example, your query may perform filtering on an integer, so you'll need to have an integer variable as one of the arguments.

The second parameter is ReturnType, which might be an entity or it might be an ObjectQuery of a particular type. The last, Delegate, will be a lambda expression whose function is a LINQ to Entities query.

Example 16.17, "A frequently used query that is a good candidate for precompilation" is an example of a query that might be used a number of times during an application's lifetime; it finds customers who have gone to a particular adventure location.

Example 16.17. A frequently used query that is a good candidate for precompilation

Dim custsToDestination = _
From cust In context.Contacts.OfType(Of Customer)() _
Where cust.Reservations.FirstOrDefault.Trip.Destination.DetinationName _
= "Patagonia"
var newCustomersSinceDate =
from cust in context.Contacts.OfType<Customer>()
where cust.Reservations.FirstOrDefault().Trip.Destination.DetinationName
== "Patagonia"
select cust;

To turn this into a compiled query, you will need a variable to represent the object context, such as ctx. You will also need a variable for the location name. Construct a lambda expression that processes these two variables in a LINQ to Entities query, as shown in Example 16.18, "A lambda expression of the query to be precompiled".

Example 16.18. A lambda expression of the query to be precompiled

Function(ctx As BreakAwayEntities, DestinationString As String) _
  From cust In ctx.Contacts.OfType(Of Customer)() _
  Where cust.Reservations.FirstOrDefault.Trip.Destination.DestinationName _
  = DestinationString

(BreakAwayEntities ctx,String DestinationString) =>
  from cust in ctx.Contacts.OfType<Customer>()
  where cust.Reservations.FirstOrDefault().Trip.Destination.DestinationName 
  == DestinationString select cust

This lambda expression is used as a parameter of CompiledQuery.Compile.

Example 16.19, "The compiled LINQ to Entities query" shows the CompiledQuery, which will take a BreakAwayEntities object and a string when it's called, and will return an IQueryable(of Customer). Those are passed into the Compile generic method. Then the lambda expression follows, inside parentheses. The query passes these parameters into the lambda expression. For brevity, I've used a placeholder where you need to insert the lambda expression from Example 16.18, "A lambda expression of the query to be precompiled".

Example 16.19. The compiled LINQ to Entities query

Dim compQuery = CompiledQuery.Compile(Of BreakAwayEntities, String, _
 IQueryable(Of Customer))(*insert lambda expression from Example 16-18*)
var compQuery = CompiledQuery.Compile<BreakAwayEntities, String,
 IQueryable<Customer> (*insert lambda expression from Example 16-18*)

Once the CompiledQuery has been created, you can use it any time you want to use the query by implementing its Invoke method, as demonstrated in Example 16.20, "Using the compiled LINQ to Entities query". Because you have a parameter for this query, you can change the value of the parameter any time you use the query, which makes the compiled query pretty flexible.

Example 16.20. Using the compiled LINQ to Entities query

Dim context As New BreakAwayEntities
Dim loc As String = "Malta"
Dim custs As ObjectQuery(Of Customer) = compQuery.Invoke(context, loc)
Dim custlist = custs.ToList
var context = new BreakAwayEntities();
var loc = "Malta";
IQueryable<Customer> custs = compQuery.Invoke(context, loc);
var custlist = custs.ToList();

Now you can use the code in Example 16.21, "A performance test of the compiled query" to test the performance of the compiled query. The first query loads the metadata files into the application memory so that the time for that task is not counted in the first run of the compiled query. You'll learn more about metadata files in Chapter 17, Controlling Objects with ObjectStateManager and MetadataWorkspace. Subsequent queries (the example lists only some of them) will not require query compilation and will be faster.

Example 16.21. A performance test of the compiled query

Using context = New BAEntities
  Dim cust = context.Contacts.FirstOrDefault
End Using

Using context As New BAEntities
  Dim destination As String = "Malta"
  Dim custs As ObjectQuery(Of Customer) =  _
           compQuery.Invoke(context, destination)
  Dim custlist = custs.ToList
End Using

Using context As New BAEntities
  Dim destination As String = "Bulgaria"
  Dim custs As ObjectQuery(Of Customer) =  _
     compQuery.Invoke(context, destination)
  Dim custlist = custs.ToList
End Using
using (var context = new BAEntities ())
{
  var cust = context.Contacts.FirstOrDefault();
}
using (BreakAwayEntities context = new BAEntities ())
{
  string destination = "Malta";
  ObjectQuery<Customer> custs = compQuery.Invoke(context, destination);
  var custlist = custs.ToList();
}
using (BreakAwayEntities context = new BAEntities ())
{
  string destination = "Bulgaria";
  ObjectQuery<Customer> custs = compQuery.Invoke(context, destination);
  var custlist = custs.ToList();
}

Notice that for each timed test, a completely new context is created that also creates a new connection. The times shown in Table 16.4, "Performance comparisons between compiled and noncompiled LINQ to Entities queries" are compared to performing the same test without using compiled queries.

Table 16.4. Performance comparisons between compiled and noncompiled LINQ to Entities queries

Query 1

Query 2

Query 3

Using a compiled query

14 ms

1 ms

1 ms

Using a noncompiled query

8 ms

4 ms

3 ms


You can see that once the query has been compiled, query processing takes only a portion of the time it takes when repeating that particular task without the advantage of precompilation.

Query Plan Caching for Entity SQL

By default, compiled Entity SQL queries are stored in an application domain cache for both EntityClient queries and ObjectQuery queries. As part of the query pipeline, the cache will be checked for a matching Entity SQL query (parameters are taken into account here, as with the precompiled LINQ queries), and if a precompiled version of that query is available, it will be used.

Set the Boolean EntityCommand.EnablePlanCaching to true or false to enable or disable caching for EntityClient. ObjectQuery.EnablePlanCaching is the property for enabling or disabling query plan caching for ObjectQuery queries.

Given the previous advice about avoiding SQL injection attacks with dynamic Entity SQL, Microsoft recommends that if you are building Entity SQL expressions dynamically, you disable query plan caching. The stored queries are case-sensitive, so if you have a query in which you type "select value con …" in one method and "SELECT VALUE con …" in another, they won't be considered matching queries, and not only will you lose the benefit of the cached query, but the size of the cache will increase as a result of extra queries being stored.

Using tests similar to the previous performance tests, you can see the difference in query processing time when caching is enabled or disabled, as shown in the following code and in Table 16.5, "Comparing average query times for materialized entities versus streamed data":

SELECT VALUE c from AWLTEntities.EFCustomers AS c

Table 16.5. Comparing average query times for materialized entities versus streamed data

Query plan caching state

Enabled

Disabled

Entity SQL with Object Services

1.1 ms

3.23 ms

Entity SQL with EntityClient

4.1 ms

6.38 ms


Again, in this case the time for the cached query is significantly less than the non-cached query.

Entity SQL querying with EntityClient versus Object Services

Although the difference between querying with and without the cache may not be surprising, the difference between querying with Object Services and EntityClient might be.

When running the test with a query that returns data of a more complex shape, the difference shifts, as you can see in the following code and in Table 16.6, "Average query times for shaped results":

SELECT cust.CompanyName,cust.SalesOrderHeader,
    (SELECT VALUE order.SalesOrderDetail 
     FROM cust.SalesOrderHeader AS order)
FROM AWLTEntities.EFCustomers AS cust

Table 16.6. Average query times for shaped results

Query plan caching state

Enabled

Disabled

Entity SQL with Object Services

21.28 ms

42.51 ms

Entity SQL with EntityClient

17.05 ms

32.15 ms


Now the EntityClient and Object Services queries are more on par-with the EntityClient being about 15% faster. Because EntityClient does not materialize the objects, you would expect it to have some advantages. But why is the query itself impacting the difference between the two methods of querying?

Although object materialization takes some time, so does the task of shaping the EntityDataReader and then pushing in the results. In the case of the simple query, object materialization is very efficient in creating a Customer entity from data that maps exactly to the entity.

In the second query, you are building data that is shaped like that shown in Figure 16.4, "The shaped data returned by the Entity SQL performance test".

Figure 16.4. The shaped data returned by the Entity SQL performance test

The shaped data returned by the Entity SQL performance test

With the more complexly shaped data, once the EntityDataReader is created the cost of pushing the data into that DataReader is a lot less than the cost of materializing a lot of complexly shaped objects.

What About Database Updates and Performance?

Again, the Entity Framework will need to generate commands and transform the entity structure into the database structure; thus, compared to working with ADO.NET, where you would be working directly against the database, there will be a performance hit.

Note
In talking with one of the folks who focuses on performance for the Data Programmability team, I learned that the performance for updating data in the Entity Framework is very impressive when compared to other technologies. Although that was proof enough for me, I still had to see the performance benefits for myself!

For the following tests, I modified the previous tests to include updates and inserts, and because this is much more intensive and time-consuming than just querying data, there are only 10 iterations of the tests, not 100. Each test queries for the entire set of customers (approximately 450), iterates through those customers, and modifies a single field in each one. Once those modifications are made, 10 new customers are added. Finally, the appropriate update method is called (DataAdapter.Update, DataContext.SubmitChanges, or ObjectContext.SaveChanges).

To be fair, there are two tests for DataSet. The first uses the default Update, which sends one command at a time to the database. The second leverages UpdateBatch and sets the batch to 100 commands at a time. The final times represent the average of performing this entire operation 10 times.

Note
Remember that these tests are meant only to be relative to one another. I conducted them on my computer, which might not be as tricked out as the average server. The tests are not meant to indicate the actual potential of any of the tested technologies' performance overall.

The results are interesting. The Entity Framework is faster than DataAdapter and LINQ to SQL, as you can see in Table 16.7, "Comparing DataAdapter UpdateBatch to Entity Framework and LINQ to SQL".

Table 16.7. Comparing DataAdapter UpdateBatch to Entity Framework and LINQ to SQL

Method

Average time

DataAdapter with UpdateBatch=1

353 ms

DataAdapter with UpdateBatch=100

288 ms

Entity Framework Object Services

143 ms

LINQ to SQL

1333 ms


You can perform updates with "classic ADO.NET" in a variety of ways, and you may achieve different results relative to the two newer technologies. But this at least gives you an idea that something very smart is happening under the covers of the Entity Framework when SaveChanges is called.

Like much of .NET, the Entity Framework is not thread-safe. This means that to use the Entity Framework in multithreaded environments, you need to either explicitly keep individual ObjectContexts in separate threads, or be very conscientious about locking threads so that you don't get collisions.

Note
Straight from the source (MSDN docs): "ObjectContext only supports Single-Threaded scenarios."

Here are some examples of a few ways to use ObjectContext in separate threads.

Forcing an ObjectContext to Use Its Own Thread

Example 16.22, "Forcing an ObjectContext to use its own thread" uses a separate class for managing the ObjectContext and performing the database interaction. The main program then creates a separate thread when it needs the ObjectContext to do something. Delegates and callbacks are used so that it's possible for entities to be returned from the separate thread.

Notice that every time the ObjectContext is about to be impacted, a lock is placed on it. C# has a handy lock keyword, but with Visual Basic you'll need to use the Threading.Monitor class to do this.

Note
If you are unfamiliar with threading and delegates, you are not alone. It's an advanced topic, and lots of resources are available to help you get up and running on threading if you need to use it explicitly. The one area where it is useful to understand, even if you have no plans to perform advanced threading work, is in keeping your UI responsive while performing tasks such as making a call to the database, which might take some time. Look for topics on the BackgroundWorker component that you can use in both Windows Forms and Windows Presentation Foundation (WPF), and the Asynchronous Page features in ASP.NET.

Example 16.22. Forcing an ObjectContext to use its own thread

Imports System
Imports System.Threading
Imports BAGA.BreakAwayModel
Module Module2

' Delegate that defines the signature for the callback method.
Public Delegate Sub contextCallback _
 (ByVal contactList As List(Of Contact))
Private contacts As List(Of Contact)

Public Class MainModule
  Public Shared Sub Main()
    Dim occ As New ObjectContextClass _
             (New contextCallback(AddressOf ResultCallback))
    Dim t As New Thread(AddressOf occ.GetCustomers)
    t.Start()
    t.Join()
    Console.WriteLine("Contacts Retrieved: " & contacts.Count.ToString)
    Console.WriteLine(contacts(0).LastName & contacts(0).ModifiedDate)
    contacts(0).ModifiedDate=DateTime.Now
    Console.WriteLine(contacts(0).LastName & contacts(0).ModifiedDate)
    t = New Thread(AddressOf occ.SaveChanges)
    t.Start()
  End Sub

  Public Shared Sub ResultCallback(ByVal contactList As List(Of Contact))
    contacts = contactList
  End Sub
End Class

Public Class ObjectContextClass
  Private context As BreakAwayEntities

  ' Delegate used to execute the callback method when the task is done.
    Private callback As contextCallback

    ' The callback delegate is passed in to the constructor
    Public Sub New(ByVal callbackDelegate As contextCallback)
      callback = callbackDelegate
    End Sub

  Public Sub GetCustomers()
    If context Is Nothing Then
      context = New BreakAwayEntities
    End If
    'put a lock on the context during this operation
    Threading.Monitor.Enter(context) 
    Dim contactquery = From cust In context.Contacts _
                       Where cust.LastName.StartsWith("S")
    'unlock the context
    Dim conList = contactquery.ToList
    Threading.Monitor.Exit(context)
    If Not callback Is Nothing Then callback(conList)
  End Sub

  Public Sub SaveChanges()
    Threading.Monitor.Enter(context)
    context.SaveChanges()
    Threading.Monitor.Exit(context)
  End Sub

End Class

End Module
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using BAGA;
internal static class Module2
{

  // Delegate that defines the signature for the callback method.
  //
public delegate void contextCallback(List<Contact> contactList);
private static List<Contact> contacts;

public class MainModule
{
  public static void Main()
  {
    ObjectContextClass occ = 
       new ObjectContextClass(new contextCallback(ResultCallback));
    Thread t = new Thread(occ.GetCustomers);
    t.Start();
    t.Join();
    Console.WriteLine("Retrieved: " + contacts.Count.ToString());
    Console.WriteLine(contacts[0].LastName + contacts[0].ModifiedDate);
    contacts[0].ModifiedDate = DateTime.Now;
    Console.WriteLine(contacts[0].LastName + contacts[0].ModifiedDate);
    t = new Thread(occ.SaveChanges);
    t.Start();
  }

  public static void ResultCallback(List<Contact> contactList)
  {
    contacts = contactList;
  }
}

public class ObjectContextClass
{
  private BAEntities context;

  // Delegate used to execute the callback method when the task is done.
    private contextCallback callback;

    // The callback delegate is passed in to the constructor
    public ObjectContextClass(contextCallback callbackDelegate)
    {
      callback = callbackDelegate;
    }

  public void GetCustomers()
  {
    if (context == null)
    {
      context = new BAEntities();

    }
    //put a lock on the context during @this operation;
    System.Threading.Monitor.Enter(context);
    var contactquery = from c in context.Contacts
                       where c.LastName.StartsWith("S")
                       select c;

    ////unlock the context;
    var conList = contactquery.ToList();
    System.Threading.Monitor.Exit(context);
    if (callback != null)
      callback(conList);
}

  public void SaveChanges()
  {
    System.Threading.Monitor.Enter(context);
    context.SaveChanges();
    System.Threading.Monitor.Exit(context);
  }
}
}

It's important to call out the locking of the context. Because of the way the ObjectContext manages state and relationships, and because of the merge possibilities when new data is brought in, you need to be very careful so that two separate threads do not affect the context at the same time. You should consider this use as an edge case, and you should be sure that you really understand threading before you start spinning your own threads and working with classes that are not thread-safe.

It's much safer (though less practical in many cases) to keep individual ObjectContexts on completely separate threads so that you don't have to worry about this as much.

The BackgroundWorker component, introduced in .NET 2.0, does alleviate some of the complexities of working with multiple threads, but still, the Entity Framework does not have any inherent features that make it easy to use in multithreaded applications. Hopefully, future versions of the Entity Framework will make threading and asynchronous programming simpler to work with.

Another Spin on Threading: Concurrent Processing

Example 16.22, "Forcing an ObjectContext to use its own thread" used a separate thread to host the ObjectContext. Example 16.23, "Managing threads to get concurrent processing" shows another way to use worker threads to perform some concurrent processing on entities. Because this example only performs reads on the entities, the concerns of Example 16.22, "Forcing an ObjectContext to use its own thread" are not present. This example sends entities off to a variety of methods that will merely read information from the entities and possibly send a form letter or email. In this case, the code is writing some text out to the console only to demonstrate the concept.

The query pulls back customers along with their reservation and trip information. Then, based on the reservation status, the Customer entity is sent to a different method to create the email. Because the process is being performed in different threads, the emails can be written concurrently and there is no need in this case to wait for any type of result.

When the text is written out to the console, the example also displays the ID of the thread so that you can verify that different threads are being used.

Example 16.23. Managing threads to get concurrent processing

Imports System.Threading
Imports BAGA

Private Sub MultiThreadTest()
  Dim emailThread = New EmailThreadClass
  Using context As New BreakAwayEntities
    Dim custs = From cust In context.Contacts.OfType(Of Customer) _
        .Include("Reservations.Trip.Location") 
    For Each cust In custs
      If cust.Reservations.Any _
           (Function(r) r.Trip.StartDate > Today.AddDays(6)) Then
        'new thread for upcoming trip emails
         Dim workerThread As Threading.Thread = _
          New Threading.Thread(AddressOf emailThread.UpcomingTripEmails)
         workerThread.Start(cust)
      ElseIf cust.Reservations.Any _
              (Function(r) r.Trip.StartDate > Today _
                And r.Trip.StartDate <= Today.AddDays(6)) Then
        'new thread for very soon trip emails
        Dim workerThread As Threading.Thread =  _
         New Threading.Thread(AddressOf emailThread.NextWeek)
        workerThread.Start(cust)
      Else 'no future trips
        'new thread for no upcmoing trips emails
        Dim workerThread As Threading.Thread = _
         New Threading.Thread(AddressOf emailThread.ComeBackEmails)
        workerThread.Start(cust)
      End If
    Next
  End Using
End Sub

Public Class EmailThreadClass
  Public Sub UpcomingTripEmails(ByVal cust As Customer)
    Dim anytrip = cust.Reservations _
       .Where(Function(r) r.Trip.StartDate > Today.AddDays(6)) _
       .First.Trip
    Console.WriteLine("Thread " & Thread.CurrentThread.ManagedThreadId)
    Console.WriteLine("            Dear " & cust.FirstName.Trim & _
      ", Your trip to " & anytrip.Destination.DestinationName.Trim & _
      " begins on " & anytrip.Trip.StartDate & _
      ". We look forward to seeing you soon.")
    Console.WriteLine()
  End Sub

  Public Sub NextWeek(ByVal cust As Customer)
    Dim anytrip = cust.Reservations _
       .Where(Function(r) r.Trip.StartDate <= Today.AddDays(6)) _
       .First.Trip
    Console.WriteLine("Thread " & Thread.CurrentThread.ManagedThreadId)
    Console.WriteLine("            Dear " & cust.FirstName.Trim & _
      ",  Your trip to " & anytrip.Location.LocationName.Trim & _
      " begins in only a few days. Please let us know if " & _
      " you have any last minute questions.")
    Console.WriteLine()
  End Sub

  Public Sub ComeBackEmails(ByVal cust As Customer)
    Console.WriteLine("Thread " & Thread.CurrentThread.ManagedThreadId)
    Console.WriteLine("            Dear " & cust.FirstName.Trim & _
       ", We haven't seen you in a while. We hope you'll consider" &  _
       "  BreakAway Geek Adventures for your next vacation.")
    Console.WriteLine()
  End Sub
End Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BAGA;
using System.Threading;

namespace Chapter16CS
{
  public class EmailThreads
  {
    public static void Main()
    {
      var emailThread = new EmailThreadClass();
      using (BAEntities context = new BAEntities())
      {
        var custs =
            from cust in context.Contacts.OfType<Customer>()
             .Include("Reservations.Trip.Destination")
            select cust;
        foreach (var cust in custs)
        {
          if (cust.Reservations
              .Any((r) => r.Trip.StartDate > DateTime.Today.AddDays(6)))
          {
            //new thread for upcoming trip emails
            Thread workerThread = 
               new Thread(emailThread.UpcomingTripEmails);
            workerThread.Start(cust);
          }
          else if (cust.Reservations
            .Any(r => r.Trip.StartDate > DateTime.Today 
                 & r.Trip.StartDate <= DateTime.Today.AddDays(6)))
          {
            //new thread for very soon trip emails
            Thread workerThread = new Thread(emailThread.NextWeek);
            workerThread.Start(cust);
          }
          else //no future trips
          {
            //new thread for no upcmoing trips emails
            Thread workerThread = 
               new Thread(emailThread.ComeBackEmails);
            workerThread.Start(cust);
          }
        }
      }
    }
  }

  public class EmailThreadClass
  {
    public void UpcomingTripEmails(object customer)
    {
      var cust = (Customer)customer;
      var anytrip = cust.Reservations
       .Where(r => r.Trip.StartDate > DateTime.Today.AddDays(6))
       .First().Trip;

Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("            Dear " + cust.FirstName.Trim() + 
   ", Your trip to " + anytrip.Destination.DestinationName.Trim() +
 " begins on " + anytrip.StartDate + 
  ". We look forward to seeing you soon.");
Console.WriteLine();
    }

    public void NextWeek(object customer)
    {
      var cust = (Customer)customer;
      var anytrip = cust.Reservations
       .Where((r) => r.Trip.StartDate <=DateTime.Today.AddDays(6))
      .First().Trip;

Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("            Dear " + cust.FirstName.Trim() + 
  ",  Your trip to " + anytrip.Destination.DestinationName.Trim() + 
  " begins in only a few days. Please let us know if " + 
  " you have any last minute questions.");
Console.WriteLine();
    }

    public void ComeBackEmails(object customer)
    {
      var cust = (Customer)customer;

Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("            Dear " + cust.FirstName.Trim() + 
  ", We haven't seen you in a while. We hope you'll consider" +
  "  BreakAway Geek Adventures for your next vacation.");
Console.WriteLine();
    }
  }
}

This chapter looked at several important concerns of application developers-connections, transactions, security, performance, and threading-and how they relate to the Entity Framework. You should now have a good understanding of not only how things work under the covers, but also how you can take advantage of the Entity Framework's flexibility to control how connections are made, control transactions, or impact performance. This will enable you to implement the functionality that allows you to write enterprise-level applications while benefitting from using an EDM.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.