Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 99 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
99
Dung lượng
12,93 MB
Nội dung
504 CHAPTER 16 ■ THE DATACONTEXT The result is that if you have an entity object cached in your DataContext, and another context updates a field for that entity object’s record in the database, and you perform a LINQ query speci- fying that field in the search criteria so that it matches the new value in the database, the record will be included in the results set. However, since you already have it cached, you get the cached entity object returned with the field not matching your search criteria. It will probably be clearer if I provide a specific example. What I will do is first query for a specific customer that I know will not match the search criteria I will provide for a subsequent query. I will use customer LONEP. The region for customer LONEP is OR, so I will search for customers whose region is WA. I will then display those customers whose region is WA. Next, I will update the region for customer LONEP to WA using ADO.NET, just as if some other context did it externally to my process. At this point, LONEP will have a region of OR in my entity object but WA in the database. Next, I will perform that very same query again to retrieve all the customers whose region is WA. When you look in the code, you will not see the query defined again though. You will merely see me enumerate through the returned sequence of custs. Remember that, because of deferred query execution, I need only enumerate the results to cause the query to be executed again. Since the region for LONEP is WA in the database, that record will be included in the results set. But, since that record’s entity object is already cached, it will be the cached entity object that is returned, and that object still has a region of OR. I will then display each returned entity object’s region. When customer LONEP is displayed, its region will be OR, despite the fact that my query specified it wanted customers whose region is WA. Listing 16-3 provides the code to demonstrate this mismatch. Listing 16-3. An Example Demonstrating the Results Set Cache Mismatch Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); // Let's get a cutomer to modify that will be outside our query of region == 'WA'. Customer cust = (from c in db.Customers where c.CustomerID == "LONEP" select c).Single<Customer>(); Console.WriteLine("Customer {0} has region = {1}.{2}", cust.CustomerID, cust.Region, System.Environment.NewLine); // Ok, LONEP's region is OR. // Now, let's get a sequence of customers from 'WA', which will not include LONEP // since his region is OR. IEnumerable<Customer> custs = (from c in db.Customers where c.Region == "WA" select c); Console.WriteLine("Customers from WA before ADO.NET change - start "); foreach(Customer c in custs) { // Display each entity object's Region. Console.WriteLine("Customer {0}'s region is {1}.", c.CustomerID, c.Region); } Console.WriteLine("Customers from WA before ADO.NET change - end.{0}", System.Environment.NewLine); // Now I will change LONEP's region to WA, which would have included it // in that previous query's results. Rattz_789-3C16.fm Page 504 Thursday, October 25, 2007 11:46 AM CHAPTER 16 ■ THE DATACONTEXT 505 // Change the customers' region through ADO.NET. Console.WriteLine("Updating LONEP's region to WA in ADO.NET "); ExecuteStatementInDb( "update Customers set Region = 'WA' where CustomerID = 'LONEP'"); Console.WriteLine("LONEP's region updated.{0}", System.Environment.NewLine); Console.WriteLine("So LONEP's region is WA in database, but "); Console.WriteLine("Customer {0} has region = {1} in entity object.{2}", cust.CustomerID, cust.Region, System.Environment.NewLine); // Now, LONEP's region is WA in database, but still OR in entity object. // Now, let's perform the query again. // Display the customers entity object's region again. Console.WriteLine("Query entity objects after ADO.NET change - start "); foreach(Customer c in custs) { // Display each entity object's Region. Console.WriteLine("Customer {0}'s region is {1}.", c.CustomerID, c.Region); } Console.WriteLine("Query entity objects after ADO.NET change - end.{0}", System.Environment.NewLine); // We need to reset the changed values so that the code can be run // more than once. Console.WriteLine("{0}Resetting data to original values.", System.Environment.NewLine); ExecuteStatementInDb( "update Customers set Region = 'OR' where CustomerID = 'LONEP'"); Here are the results: Customer LONEP has region = OR. Customers from WA before ADO.NET change - start Customer LAZYK's region is WA. Customer TRAIH's region is WA. Customer WHITC's region is WA. Customers from WA before ADO.NET change - end. Updating LONEP's region to WA in ADO.NET Executing SQL statement against database with ADO.NET Database updated. LONEP's region updated. So LONEP's region is WA in database, but Customer LONEP has region = OR in entity object. Query entity objects after ADO.NET change - start Customer LAZYK's region is WA. Customer LONEP's region is OR. Customer TRAIH's region is WA. Customer WHITC's region is WA. Query entity objects after ADO.NET change - end. Rattz_789-3C16.fm Page 505 Thursday, October 25, 2007 11:46 AM 506 CHAPTER 16 ■ THE DATACONTEXT Resetting data to original values. Executing SQL statement against database with ADO.NET Database updated. As you can see, even though I queried for customers in WA, LONEP is included in the results despite the fact that its region is OR. Sure, it’s true that in the database LONEP has a region of WA, but it does not in the object I have a reference to in my code. Is anyone else getting a queasy feeling? Another manifestation of this behavior is the fact that inserted entities cannot be queried back out and deleted entities can be, prior to calling the SubmitChanges method. Again, this is because of the fact that even though we have inserted an entity, when the query executes, the results set is deter- mined by what is in the actual database, not the DataContext object’s cache. Since the changes have not been submitted, the inserted entity is not yet in the database. The opposite applies to deleted entities. Listing 16-4 contains an example demonstrating this behavior. Listing 16-4. Another Example Demonstrating the Results Set Cache Mismatch Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); Console.WriteLine("First I will add customer LAWN."); db.Customers.InsertOnSubmit( new Customer { CustomerID = "LAWN", CompanyName = "Lawn Wranglers", ContactName = "Mr. Abe Henry", ContactTitle = "Owner", Address = "1017 Maple Leaf Way", City = "Ft. Worth", Region = "TX", PostalCode = "76104", Country = "USA", Phone = "(800) MOW-LAWN", Fax = "(800) MOW-LAWO" }); Console.WriteLine("Next I will query for customer LAWN."); Customer cust = (from c in db.Customers where c.CustomerID == "LAWN" select c).SingleOrDefault<Customer>(); Console.WriteLine("Customer LAWN {0}.{1}", cust == null ? "does not exist" : "exists", System.Environment.NewLine); Console.WriteLine("Now I will delete customer LONEP"); cust = (from c in db.Customers where c.CustomerID == "LONEP" select c).SingleOrDefault<Customer>(); db.Customers.DeleteOnSubmit(cust); Rattz_789-3C16.fm Page 506 Thursday, October 25, 2007 11:46 AM CHAPTER 16 ■ THE DATACONTEXT 507 Console.WriteLine("Next I will query for customer LONEP."); cust = (from c in db.Customers where c.CustomerID == "LONEP" select c).SingleOrDefault<Customer>(); Console.WriteLine("Customer LONEP {0}.{1}", cust == null ? "does not exist" : "exists", System.Environment.NewLine); // No need to reset database since SubmitChanges() was not called. ■Note In the Visual Studio 2008 Beta 2 release and earlier, the InsertOnSubmit method called in the preceding code was named Add and the DeleteOnSubmit method was named Remove. In the previous code, I insert a customer, LAWN, and then query to see if it exists. I then delete a different customer, LONEP, and query to see if it exists. I do all this without calling the SubmitChanges method so that the cached entity objects have not been persisted to the database. Here are the results of this code: First I will add customer LAWN. Next I will query for customer LAWN. Customer LAWN does not exist. Now I will delete customer LONEP Next I will query for customer LONEP. Customer LONEP exists. The Microsoft developer who told me that this was intentional behavior stated that the data retrieved by a query is stale the moment you retrieve it and that the data cached by the DataContext is not meant to be cached for long periods of time. If you need better isolation and consistency, he recommended you wrap it all in a transaction. Please read the section titled “Pessimistic Concur- rency” in Chapter 17 to see an example doing this. Change Tracking Once the identity tracking service creates an entity object in its cache, change tracking begins for that object. Change tracking works by storing the original values of an entity object. Change tracking for an entity object continues until you call the SubmitChanges method. Calling the SubmitChanges method causes the entity objects’ changes to be saved to the database, the original values to be forgotten, and the changed values to become the original values. This allows change tracking to start over. This works fine as long as the entity objects are retrieved from the database. However, merely creating a new entity object by instantiating it will not provide any identity or change tracking until the DataContext is aware of its existence. To make the DataContext aware of the entity object’s existence, simply insert the entity object into one of the Table<T> properties. For example, in my Northwind class, I have a Table<Customer> property named Customers. I can call the InsertOnSubmit method on the Customers property to insert the entity object, a Customer, to the Table<Customer>. When this is done, the DataContext will begin identity and change tracking on that entity object. Here is example code inserting a customer: Rattz_789-3C16.fm Page 507 Thursday, October 25, 2007 11:46 AM 508 CHAPTER 16 ■ THE DATACONTEXT db.Customers.InsertOnSubmit( new Customer { CustomerID = "LAWN", CompanyName = "Lawn Wranglers", ContactName = "Mr. Abe Henry", ContactTitle = "Owner", Address = "1017 Maple Leaf Way", City = "Ft. Worth", Region = "TX", PostalCode = "76104", Country = "USA", Phone = "(800) MOW-LAWN", Fax = "(800) MOW-LAWO"}); ■Note In the Visual Studio 2008 Beta 2 release and earlier, the InsertOnSubmit method called in the preceding code was named Add. Once I call the InsertOnSubmit method, identity and change tracking for customer LAWN begins. Initially, I found change tracking a little confusing. Understanding the basic concept is simple enough, but feeling comfortable about how it was working did not come easy. Understanding change tracking becomes even more important if you are writing your entity classes by hand. Be sure to read the section titled “Change Notifications” in Chapter 15 to gain an even more complete understanding of how change tracking works. Change Processing One of the more significant services the DataContext provides is change tracking for entity objects. When you insert, change, or delete an entity object, the DataContext is monitoring what is happening. However, no changes are actively being propagated to the database. The changes are cached by the DataContext until you call the SubmitChanges method. When you call the SubmitChanges method, the DataContext object’s change processor manages the update of the database. First, the change processor will insert any newly inserted entity objects to its list of tracked entity objects. Next, it will order all changed entity objects based on their depen- dencies resulting from foreign keys and unique constraints. Then, if no transaction is in scope, it will create a transaction so that all SQL commands carried out during this invocation of the SubmitChanges method will have transactional integrity. It uses SQL Server’s default isolation level of ReadCommitted, which means that the data read will not be physically corrupted and only committed data will be read, but since the lock is shared, nothing prevents the data from being changed before the end of the transaction. Last, it enumerates through the ordered list of changed entity objects, creates the necessary SQL statements, and executes them. If any errors occur while enumerating the changed entity objects, if the SubmitChanges method is using a ConflictMode of FailOnFirstConflict, the enumeration process aborts, and the transac- tion is rolled back, undoing all changes to the database, and an exception is thrown. If a ConflictMode of ContinueOnConflict is specified, all changed entity objects will be enumerated and processed despite any conflicts that occur, while the DataContext builds a list of the conflicts. But again, the transaction is rolled back, undoing all changes to the database, and an exception is thrown. However, while the changes have not persisted to the database, all of the entity objects’ changes still exist in the entity objects. This gives the developer the opportunity to try to resolve the problem and to call the SubmitChanges method again. Rattz_789-3C16.fm Page 508 Thursday, October 25, 2007 11:46 AM CHAPTER 16 ■ THE DATACONTEXT 509 If all the changes are made to the database successfully, the transaction is committed, and the change tracking information for the changed entity objects is deleted, so that change tracking can restart fresh. DataContext() and [Your]DataContext() The DataContext class is typically derived from to create the [Your]DataContext class. It exists for the purpose of connecting to the database and handling all database interaction. You will use one of the following constructors to instantiate a DataContext or [Your]DataContext object. Prototypes The DataContext constructor has four prototypes I will cover. The First DataContext Constructor Prototype DataContext(string fileOrServerOrConnection); This prototype of the constructor takes an ADO.NET connection string and is probably the one you will use the majority of the time. This prototype is the one used by most of the LINQ to SQL examples in this book. The Second DataContext Constructor Prototype DataContext (System.Data.IDbConnection connection); Because System.Data.SqlClient.SqlConnection inherits from System.Data.Common.DbConnection, which implements System.Data.IDbConnection, you can instantiate a DataContext or [Your]DataContext with a SqlConnection that you have already created. This prototype of the constructor is useful when mixing LINQ to SQL code with already existing ADO.NET code. The Third DataContext Constructor Prototype DataContext(string fileOrServerOrConnection, System.Data.Linq.MappingSource mapping); This prototype of the constructor is useful when you don’t have a [Your]DataContext class, and instead have an XML mapping file. Sometimes, you may have an already existing business class to which you cannot add the appropriate LINQ to SQL attributes. Perhaps you don’t even have the source code for it. You can generate a mapping file with SQLMetal or write one by hand to work with an already existing business class, or any other class for that matter. You provide a normal ADO.NET connection string to establish the connection. The Fourth DataContext Constructor Prototype DataContext (System.Data.IDbConnection connection, System.Data.Linq.MappingSource mapping) This prototype allows you to create a LINQ to SQL connection from an already existing ADO.NET connection and to provide an XML mapping file. This version of the prototype is useful for those times when you are combining LINQ to SQL code with already existing ADO.NET code, and you don’t have entity classes decorated with attributes. Rattz_789-3C16.fm Page 509 Thursday, October 25, 2007 11:46 AM 510 CHAPTER 16 ■ THE DATACONTEXT Examples For an example of the first DataContext constructor prototype, in Listing 16-5, I will connect to a physical .mdf file using an ADO.NET type connection string. Listing 16-5. The First DataContext Constructor Prototype Connecting to a Database File DataContext dc = new DataContext(@"C:\Northwind.mdf"); IQueryable<Customer> query = from cust in dc.GetTable<Customer>() where cust.Country == "USA" select cust; foreach (Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } ■Note You will need to modify the path passed to the DataContext constructor so that it can find your .mdf file. I merely provide the path to the .mdf file to instantiate the DataContext object. Since I am creating a DataContext and not a [Your]DataContext object, I must call the GetTable<T> method to access the customers in the database. Here are the results: Great Lakes Food Market Hungry Coyote Import Store Lazy K Kountry Store Let's Stop N Shop Lonesome Pine Restaurant Old World Delicatessen Rattlesnake Canyon Grocery Save-a-lot Markets Split Rail Beer & Ale The Big Cheese The Cracker Box Trail's Head Gourmet Provisioners White Clover Markets Next I want to demonstrate the same basic code, except this time, in Listing 16-6, I will use my [Your]DataContext class, which in this case is the Northwind class. Listing 16-6. The First [Your]DataContext Constructor Prototype Connecting to a Database File Northwind db = new Northwind(@"C:\Northwind.mdf"); IQueryable<Customer> query = from cust in db.Customers where cust.Country == "USA" select cust; foreach(Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } Rattz_789-3C16.fm Page 510 Thursday, October 25, 2007 11:46 AM CHAPTER 16 ■ THE DATACONTEXT 511 Notice that instead of calling the GetTable<T> method, I simply reference the Customers property to access the customers in the database. Unsurprisingly, this code provides the same results: Great Lakes Food Market Hungry Coyote Import Store Lazy K Kountry Store Let's Stop N Shop Lonesome Pine Restaurant Old World Delicatessen Rattlesnake Canyon Grocery Save-a-lot Markets Split Rail Beer & Ale The Big Cheese The Cracker Box Trail's Head Gourmet Provisioners White Clover Markets For the sake of completeness, I will provide one more example of the first prototype but this time use a connection string to actually connect to a SQL Express database server containing the attached Northwind database. And, because my normal practice will be to use the [Your]DataContext class, I will use it in Listing 16-7. Listing 16-7. The First [Your]DataContext Constructor Prototype Connecting to a Database Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); IQueryable<Customer> query = from cust in db.Customers where cust.Country == "USA" select cust; foreach(Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } And the results are still the same: Great Lakes Food Market Hungry Coyote Import Store Lazy K Kountry Store Let's Stop N Shop Lonesome Pine Restaurant Old World Delicatessen Rattlesnake Canyon Grocery Save-a-lot Markets Split Rail Beer & Ale The Big Cheese The Cracker Box Trail's Head Gourmet Provisioners White Clover Markets Since the second prototype for the DataContext class is useful when combining LINQ to SQL code with ADO.NET code, that is what my next example, Listing 16-8, will do. First, I will create a SqlConnection and insert a record in the Customers table using it. Then, I will use the SqlConnection Rattz_789-3C16.fm Page 511 Thursday, October 25, 2007 11:46 AM 512 CHAPTER 16 ■ THE DATACONTEXT to instantiate a [Your]DataContext class. I will query the Customers table with LINQ to SQL and display the results. Lastly, using ADO.NET, I will delete the record from the Customers table I inserted, query the Customers table one last time using LINQ to SQL, and display the results. Listing 16-8. The Second [Your]DataContext Constructor Prototype Connecting with a Shared ADO.NET Connection System.Data.SqlClient.SqlConnection sqlConn = new System.Data.SqlClient.SqlConnection( @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"); string cmd = @"insert into Customers values ('LAWN', 'Lawn Wranglers', 'Mr. Abe Henry', 'Owner', '1017 Maple Leaf Way', 'Ft. Worth', 'TX', '76104', 'USA', '(800) MOW-LAWN', '(800) MOW-LAWO')"; System.Data.SqlClient.SqlCommand sqlComm = new System.Data.SqlClient.SqlCommand(cmd); sqlComm.Connection = sqlConn; try { sqlConn.Open(); // Insert the record. sqlComm.ExecuteNonQuery(); Northwind db = new Northwind(sqlConn); IQueryable<Customer> query = from cust in db.Customers where cust.Country == "USA" select cust; Console.WriteLine("Customers after insertion, but before deletion."); foreach (Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } sqlComm.CommandText = "delete from Customers where CustomerID = 'LAWN'"; // Delete the record. sqlComm.ExecuteNonQuery(); Console.WriteLine("{0}{0}Customers after deletion.", System.Environment.NewLine); foreach (Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } } finally { // Close the connection. sqlComm.Connection.Close(); } Rattz_789-3C16.fm Page 512 Thursday, October 25, 2007 11:46 AM CHAPTER 16 ■ THE DATACONTEXT 513 Notice that I only defined the LINQ query once, but I caused it to be performed twice by enumer- ating the returned sequence twice. Remember, due to deferred query execution, the definition of the LINQ query does not actually result in the query being performed. The query is only performed when the results are enumerated. This is demonstrated by the fact that the results differ between the two enumerations. Listing 16-8 also shows a nice integration of ADO.NET and LINQ to SQL and just how well they can play together. Here are the results: Customers after insertion, but before deletion. Great Lakes Food Market Hungry Coyote Import Store Lawn Wranglers Lazy K Kountry Store Let's Stop N Shop Lonesome Pine Restaurant Old World Delicatessen Rattlesnake Canyon Grocery Save-a-lot Markets Split Rail Beer & Ale The Big Cheese The Cracker Box Trail's Head Gourmet Provisioners White Clover Markets Customers after deletion. Great Lakes Food Market Hungry Coyote Import Store Lazy K Kountry Store Let's Stop N Shop Lonesome Pine Restaurant Old World Delicatessen Rattlesnake Canyon Grocery Save-a-lot Markets Split Rail Beer & Ale The Big Cheese The Cracker Box Trail's Head Gourmet Provisioners White Clover Markets For an example of the third prototype, I won’t even use the Northwind entity classes. Pretend I don’t even have them. Instead, I will use a Customer class I have written by hand and an abbreviated mapping file. In truth, my hand-written Customer class is the SQLMetal generated Customer class that I have gutted to remove all LINQ to SQL attributes. Let’s take a look at my hand-written Customer class: My Hand-written Customer Class namespace Linqdev { public partial class Customer { private string _CustomerID; private string _CompanyName; private string _ContactName; private string _ContactTitle; Rattz_789-3C16.fm Page 513 Thursday, October 25, 2007 11:46 AM [...]... Source=.\SQLEXPRESS;Initial Catalog=Northwind ;Integrated Security=SSPI;", nwindMap); IQueryable query = from cust in db.GetTable() where cust.Country == "USA" select cust; foreach (Linqdev.Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } ■Note I placed the abbreviatednorthwindmap.xml file in my Visual Studio project’s bin\Debug directory for this example, since... abbreviatednorthwindmap.xml and placed that file in my bin\Debug directory In Listing 16-9 I will use this hand-written Customer class and external mapping file to perform a LINQ to SQL query without using any attributes Listing 16-9 The Third DataContext Constructor Prototype Connecting to a Database and Using a Mapping File string mapPath = "abbreviatednorthwindmap.xml"; XmlMappingSource nwindMap = XmlMappingSource.FromXml(System.IO.File.ReadAllText(mapPath));... TACONTEXT Listing 16-12 The Second SubmitChanges Prototype Demonstrating ContinueOnConflict Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); Console.WriteLine("Querying for the LAZYK Customer with LINQ. "); Customer cust1 = (from c in db.Customers where c.CustomerID == "LAZYK" select c).Single(); Console.WriteLine("Querying for the LONEP Customer with LINQ. ");... sqlComm.ExecuteNonQuery(); Console.WriteLine("{0}{0}Customers after deletion.", System.Environment.NewLine); foreach (Linqdev.Customer c in query) { Console.WriteLine("{0}", c.CompanyName); } } finally { // Close the connection sqlComm.Connection.Close(); } Listing 16 -10 depends on the Linqdev.Customer class and abbreviatednorthwindmap.xml external mapping file just at Listing 16-9 does This is a nice example of using... Catalog=Northwind"); IQueryable results = db.ProductsUnderThisUnitPrice(new Decimal(5.50)); foreach(ProductsUnderThisUnitPriceResult prod in results) { Console.WriteLine("{0} - {1:C}", prod.ProductName, prod.UnitPrice); } Here are the results of this example: Guaraná Fantástica - $4.50 Geitost - $2.50 ExecuteQuery() There is no doubt that LINQ to SQL is awesome Using the LINQ. .. the results: Querying for the LAZYK Customer with LINQ Querying for the LONEP Customer with LINQ Executing SQL statement against database with ADO.NET Database updated Change ContactTitle in entity objects for LAZYK and LONEP Calling SubmitChanges() Conflict(s) occurred calling SubmitChanges(): Row not found or changed Conflict for LAZYK occurred LINQ value = Vice President of Marketing Database... column name in the database Since you can perform joins in the query string, you could query columns with a different name from a different table, but specify their name as one of the mapped fields in the entity class Listing 16-20 contains an example of this Listing 16-20 An Example of the ExecuteQuery Method Specifying a Mapped Field Name Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial... perform a query using LINQ to SQL just to make sure the record is indeed in the database and display the results of the query to the console To clean up the database, I call the ExecuteCommand method to delete the inserted record This code produces the following results: Inserting customer Insert complete There were 1 row(s) affected Yes, customer is in database Is customer in database? Deleting customer... syntax makes crafting LINQ queries fun But, at one time or another, I think we have all experienced the desire to just perform a SQL query Well, you can do that too with LINQ to SQL In fact, you can do that and still get back entity objects That is rockin’ The ExecuteQuery method allows you to specify a SQL query as a string and to even provide parameters for substitution into the string, just as you... Northwind object using the ADO.NET database connection, query the same customer using LINQ to SQL, and display their ContactTitle At this point, the ContactTitle of each should match Console.WriteLine(String.Format( "{0}Change the title to 'Director of Marketing' in the entity object:", System.Environment.NewLine)); c.ContactTitle = "Director of Marketing"; title = GetStringFromDb(sqlConn, sqlQuery); . System.Data .Linq. MappingSource mapping) This prototype allows you to create a LINQ to SQL connection from an already existing ADO.NET connection and to provide an XML mapping file. This version of the prototype. mapping file to perform a LINQ to SQL query without using any attributes. Listing 16-9. The Third DataContext Constructor Prototype Connecting to a Database and Using a Mapping File string. Source=.SQLEXPRESS;Initial Catalog=Northwind ;Integrated Security=SSPI;", nwindMap); IQueryable<Linqdev.Customer> query = from cust in db.GetTable<Linqdev.Customer>() where cust.Country