Nielsen c33.tex V4 - 07/21/2009 2:06pm Page 772 Part V Data Connectivity When tracking incremental changes, Sync Services uses net change tracking for peer-to-peer synchroniza- tion. This means that the latest version is synchronized and all changes to a given row are applied in order at each peer. Peer-to-peer synchronization can be compared in many ways to a combination of offline data-set technology and replication. Sync Services provides a programming model like that of offline datasets but with a feature set like that of replication. As stated in the previous section, replication is a database administrator function, but via Sync Services, developers can have the same synchronization support through the many available synchronization classes, enabling developers to synchronize with other data store types. Like the offline scenario, the choice between using peer-to-peer transaction replication versus using Sync Services for peer-to-peer synchronization, it comes down to need. Peer-to-peer transactional replication is more of an administrator responsibility, but it does provide a highly available and scalable system and requires almost no coding. Sync Services, however, is for developers. It allows for synchronization of different transports or services, and provides synchronization capabilities with data sources other than a database. Sync Services is the mechanism for moving beyond just replicating schema and data from one database to another. From an architecture standpoint, the peer-to-peer scenario has each peer containing its own application code and Sync Services code to initiate and facilitate synchronization. This functionality is provided via the Sync Services peer-to-peer API. During a typical synchronization, a pair of peers is synchronized. For example, given a scenario in which three peers need to be synchronized, synchronization could take place like this: Peer1 and peer2 are synchronized, then peer1 a nd peer3, and then finally peer2 and peer3. In each synchronization, one peer acts as the local peer, and one peer acts as the remote peer. However, this scenario might seem a bit redundant, as it is not necessary for each and every peer to be synchronized. In the earlier example, peer1 and peer2 could be synchronized, then peer2 and peer3. Peer1 and peer3 would be synchronized via peer2. As for the components used in a peer-to-peer synchronization, Figures 33-12 and 33-13 show which components are used in a two-tier and n-tier architecture, respectively. FIGURE 33-12 A two-tier architecture in peer-to-peer synchronization DbSyncProvider DbSyncProvider DbSyncAdapterDbSyncAdapter Local Computer Remote Computer SyncOrchestrator 772 www.getcoolebook.com Nielsen c33.tex V4 - 07/21/2009 2:06pm Page 773 Sync Framework 33 FIGURE 33-13 An n -tier architecture in peer-to-peer synchronization with a proxy DbSyncProvider DbSyncProvider DbSyncAdapterDbSyncAdapter Local Computer Remote Computer SyncOrchestrator Proxy Service In a two-tier architecture, all the services exist on the local peer, as shown in Figure 33-12. In an n-tier architecture, a proxy and service are also introduced, and some of the services reside on the remote computer, as shown in Figure 33-13. The n-tier architecture requires a service and a transport system which provides communication between a proxy on the local computer and a service on the remote computer. The difference between peer-to- peer and client-server synchronization is that the proxy is not part of the Sync Framework API. This proxy must be written by the developer and must be derived from the KnowledgeSyncProvider class. The SyncOrchestrator in a peer-to-peer synchronization is responsible for the following: ■ Storing table information on the peer ■ Applying incremental changes to the peer database ■ Detecting conflict changes ■ Assisting applications in obtaining changes that occurred on the peer since the last synchro- nization By now it should be fairly clear that using Sync Services for ADO.NET 2.0 provides a very powerful yet flexible synchronization mechanism. There are many options to consider. What makes Sync Services great is simply how comprehensive it is for building offline and collaboration applications. Summary This chapter provided a look at the Microsoft Sync Framework and Sync Services for ADO.NET 2.0. The chapter began by providing a brief and simple example demonstrating how easy it is to use this great technology. It then jumped feet first into first an overview, and then the architecture, of the Sync Framework. That discussion was followed by an overview and architectural discussion about one of the Sync Frame- work components, Sync Services, and why it should be considered when architecting offline solutions. 773 www.getcoolebook.com Nielsen c33.tex V4 - 07/21/2009 2:06pm Page 774 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 775 LINQ IN THIS CHAPTER Overview and history of LINQ Understanding LINQ LINQ standard query operators Syntax options LINQ to SQL LINQ to XML LINQ to Dataset LINQ to Entities L et’s get right to the point. I often see code like this: string ConnectionString = @"Data Source=(local); Initial Catalog = Adventureworks;UID=username;PWD=password"; using (SqlConnection conn = new SqlConnection(ConnectionString)) { conn.Open(); SqlCommand cmd = conn.CreateCommand(); cmd.CommandType = CommandType.Text; cmd.CommandText = "SELECT LastName, FirstName, MidleName FROM Person.Contact"; using (SqlDataReader rdr = cmd.ExecuteReader()) { // do something } } Maybe you have also seen code like this or have even done it yourself, but at some point in your career one of the two have happened. Given the preceding code, ask yourself two questions. First, will it compile when run? Second, if so, will it run successfully? Set aside for a minute the fact that there is no WHERE clause and ignore the lack of encapsulation. That is not the intent of this example. What is important are the two questions. First, yes, it will compile. Second, when it runs it will fail. It will fail because the column ‘‘MidleName’’ should actually be ‘‘MiddleName.’’ The problem is that you need to run the program in order to catch this. Even worse, if your program does error, where do you look? (That is, the developer was unaware that the column name was misspelled.) Debugging an error such as this is time consuming and unproductive. Equally, the example is ‘‘unfriendly’’ on several levels. First, 775 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 776 Part V Data Connectivity two languages are being combined, in this case, C# and T-SQL. The problem is that the T-SQL language is not understood in the context of .NET. Another nuisance is the fact that there is a different set of technologies for the different data sources (SQL databases, XML, collections, in-memory objects, and so on) that developers work with on a daily basis. For example, you typically use ADO.NET to work with databases, but you might use XQuery, XPath, or some of the XML classes built into the .NET Framework to work with XML. Those are the two issues: Because queries against data are often written as strings embedded in code, the result is no compile-time checking or IntelliSense support, as well as difficulty debugging; and multiple technologies (different query languages) for different data sources (SQL, XML, etc.). Wouldn’t it be nice if there were a way to solve both of these issues? Even better, wouldn’t it be nice if there were a way to solve both of these issues with a single technology? Luckily, there is an answer: Microsoft LINQ. This chapter provides a brief history and overview of LINQ, and then looks at the different LINQ providers, including examples that demonstrate how to use them. LINQ Overview Before looking at LINQ’s current incarnation, it would be wise to first look at its history. Microsoft LINQ has been in the works for well over five years. LINQ (and other LINQ providers such as LINQ to SQL) has roots that extend to previous projects, which have been growing and evolving for quite some time. LINQ goes all the way back to a previous Microsoft project named C-Omega (or C). C-Omega, in its fullness, really contained more than what shows up in LINQ, as it contained functionality that truly experimented with using integrated queries (a mix of C# and other query languages such as SQL and XQuery). C was initially released to the public as a preview in 2004, and it quickly caught the attention of other Microsoft people (such as Anders Hejlsberg) who then took that same concept to C# and other languages. Yet, as good as C-Omega was, LINQ now contains a lot of what C contained, plus many additional fea- tures and functionality. It could be said that LINQ ‘‘leapfrogged’’ over C. Those familiar with Microsoft’s ORM history know that LINQ to SQL is not Microsoft’s first endeavor at object-relational mapping. The roots of LINQ to SQL can actually be traced to something called ObjectSpaces dating back to 2001. In simple terms, ObjectSpaces is a set of data access APIs that enable data to be treated as objects, regard- less of the source of the data. This story has both a happy ending and a sad ending. The sad ending is that ObjectSpaces was never officially released, simply because ObjectSpaces depended on WinFS and when Microsoft made the announcement that WinFS would not make it into the first release of Windows Vista, that was the end of ObjectSpaces. Happily, however, Microsoft learned a ton about ORM from ObjectSpaces and applied that knowledge to LINQ to SQL, the result being a well thought-out and solid ORM product. Is it the ORM product to end all ORM products? No, but even for a first release, LINQ to SQL is surely nothing to sneeze at. In the same category is a product called Nordic, developed by Paul Nielsen, which is a T-SQL-based O/R DBMS fac¸ade. It is a nice utility that uses code-gen to emulate an object-database. It includes 776 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 777 LINQ 34 polymorphism, inheritance, inheritable workflow states, and inheritable complex expressions. Moreover, according to Paul, it’s fast. This nice tool can be found on CodePlex at www.codeplex.com/Nordic. What Is LINQ? LINQ stands for language-integrated query. In essence, this means that developers have at their disposal a query language integrated directly into their .NET programming of choice (such as C# and Visual Basic), giving them general-purpose query capabilities. LINQ is a standard, unified query experience across different data sources, built directly into your .NET- supported programming language. LINQ brings powerful query facilities to the .NET Framework lan- guages, such as C# and VB.NET. Microsoft accomplished this by turning query set operations, trans- forms, and constructs into high-level concepts (on the same level of classes and objects) within the .NET Framework. LINQ makes a query a first-class construct within C# and Visual Basic. Moreover, LINQ addresses the very large mismatch between programming languages and data sources such as databases, XML, and in-memory objects. The entire goal of LINQ is to make the interaction between objects and data sources easier and simpler, enabling developers to interact with the different data sources (such as SQL and XML), because there is a disconnect between the data sources and the programming languages that communicate with (that is, work with) each of them. As stated earlier, LINQ is a ‘‘unified’’ query experience. This means that it unifies data access regardless of the source of data. Simply stated, LINQ bridges the gap between the world of data and the world of programming. Prior to LINQ, developers had to learn different technologies to query different data sources. No more! LINQ gives developers a new way of querying different types of data using strongly typed queries com- mon across many different data sources. The result is improved developer productivity along with LINQ- specific IntelliSense in the Visual Studio IDE and compile-time error checking! That’s right. Because developers are now working with data in an object-oriented fashion, they are also working in a strongly typed environment. The benefits of this include compile-time error checking, not runtime error checking. In addition, developers can now write queries using Visual Studio’s IntelliSense, driving the experience toward a more declarative programming model. Because LINQ makes a query a first-class construct within the programmer’s development language, developers can now write and execute queries against strongly typed objects using keywords, or operators. These keywords, called standard query operators, are discussed in the next section. Who should use LINQ? The real question is who shouldn’t use LINQ? LINQ is targeted toward appli- cation developers who want a new way to query data using strongly typed queries and strongly typed results. It is for developers seeking commonality across a number of different data sources, including but not limited to relational databases. It is for developers who want improved productivity — and who like using really cool technology. Standard Query Operators Standard query operators are essentially methods that form the LINQ pattern. They provide the query capabilities, including filtering, projection, sorting, and more. A large portion of these methods that make up the standard query operators operate on sequences, objects that have implemented the IEnumerable<T> or IQueryable<T> interface. These two interfaces are what provide the querying and iteration for queries over a non-generic collection. 777 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 778 Part V Data Connectivity There are two sets of standard query operators that implement the two interfaces mentioned above. The key to remember when working with the standard query operators is that regardless of the set, each method of the standard query operators is a static member of the Enumerable and Queryable classes, meaning they can be called using either static method syntax or instance method syntax. The standard query operators also vary in their execution timing. This timing depends on the results of the query. Whether a sequence of values is returned or a singleton value is returned determines when the query is executed. Methods are executed immediately if they return a singleton value; otherwise, the query execution is deferred and an enumerable object is returned if the method returns a sequence. Table 34-1 lists the 53 standard query operators, grouped according to their functionality. Query expression syntax To understand the flow of LINQ query syntax, compare it to standard T-SQL syntax. If you have written any T-SQL, you know the basic T-SQL query syntax and how it is written. For instance, a simple query looks like the following: SELECT FirstName, LastName FROM Person.Contact TABLE 34-1 Standard Query Operators Functionality Standard Query Operator Description Sorting OrderBy Sorts values in ascending order OrderByDescending Sorts values in descending order ThenBy Applies a secondary sort in ascending order ThenByDescending Applies a secondary sort in descending order Reverse Sorts the elements of a collection in reverse order Set Distinct Removes duplicate values from a collection Except Returns the differences between two sets (elements in one collection that do not exist in a second collection) Intersect Returns the matches between two sets (elements that appear in two separate collections) Union Returns the union of two sets (unique elements that appear in either of two collections) continued 778 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 779 LINQ 34 TABLE 34-1 (continued ) Functionality Standard Query Operator Description Filtering OfType Selects values from a sequence based on their ability to be cast to a specified type Where Selects values from a sequence based on a predicate function Quantifier All Determines whether all the elements in a sequence meet a condition Any Determines whether any of the elements in a sequence meet a condition Contains Determines whether a sequence contains a specified element Projection Select Projects values based on a transform function SelectMany Projects sequences of values based on a transform function and then combines them into a single sequence Partitioning Skip Skips elements up to a specified position in a sequence SkipWhile Skips elements based on a predicate function until an element does not satisfy the condition Take Takes elements up to a specified position in a sequence TakeWhile Takes elements based on a predicate function until an element does not satisfy the condition Join Join Joins two sequences based on key selector functions GroupJoin Joins two sequences based on key selector, grouping the matches for each element Grouping GroupBy Groups elements that share a common attribute ToLookup Inserts elements into a Lookup(TKey, TElement) Generation DefaultIfEmpty Replaces an empty collection with a default singleton collection Empty Returns an empty collection Range Generates a collection that contains a sequence of numbers Repeat Generates a collection that contains one repeated value Equality SequenceEqual Determines whether two sequences are equal by comparing elements in a pair-wise manner continued 779 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 780 Part V Data Connectivity TABLE 34-1 (continued ) Functionality Standard Query Operator Description Element ElementAt Returns the element at a specified index in a c ollection ElementAtOrDefault Returns the element at the specified index in a collection, or the first element that satisfies a condition First Returns the first element of a collection, or the first element that satisfies a condition FirstOrDefault Returns the first element of a collection, or the first element that satisfies a condition. A default value is returned if the specified element does not exist. Last Returns the last element of a collection or the last element that satisfies a condition LastOrDefault Returns the last element of a collection or the last element that satisfies a condition. A default value is returned if the specified element does not exist in the collection. Single Returns the only element of a collection or the only element that satisfies a condition SingleOrDefault Returns the only element of a collection or the only element that satisfies a condition. A default value is returned if the specified element does not exist in the collection. Conversion AsEnumerable Returns the input types as IEnumerable(T) AsQueryable Converts a generic IEnumerable to a generic IQueryable Cast Casts the elements of a collection to a specified type OfType Filters values based on their ability to be cast to a specified type ToArray Converts a collection to an array. Forces query execution. ToDictionary Puts elements into a Dictionary(TKey, TValue). Forces query execution. ToList Converts a collection to a List(T). Forces query execution. ToLookup Puts elements into a Lookup(TKey, TValue)as a one-to-many dictionary. Forces query execution. continued 780 www.getcoolebook.com Nielsen c34.tex V4 - 07/23/2009 1:56pm Page 781 LINQ 34 TABLE 34-1 (continued ) Functionality Standard Query Operator Description Concatenation Concat Concatenates two sequences into a single sequence Aggregation Aggregate Performs a custom aggregation operation on the values of a collection Average Calculates the average value of a collection of values Count Counts the elements in a collection; optionally, only those elements that satisfy a predicate expression LongCount Counts the elements in a large collection; optionally, only those elements that satisfy a predicate expression Max Determines the maximum value in a collection Min Determines the minimum value in a collection Sum Calculates the sum of the values in a collection This example queries the Person.Contact table in the AdventureWorks database and returns the FirstName and LastName columns for each row in the table. Because that’s so simple, the following example adds a secondary table, and then applies a filter and a sort: SELECT E.EmployeeID,C.FirstName, C.LastName FROM Person.Contact AS C INNER JOIN HoumanResources.Employee AS E ON C.ContactID = E.ContactID WHERE E.EmployeeID < 100 ORDER BY C.LastName This is the syntax with which all T-SQL developers are familiar. At the very minimum, the query begins with a SELECT clause, which specifies the columns you want to be returned by the query, followed by a FROM clause, which lists the tables and/or views containing the columns identified in the SELECT clause. The query could include one or more joins such as an INNER JOIN or OUTER JOIN, followed by some filtering using the WHERE clause and possibly a GROUP BY or HAVING clause, and quite possibly some ordering using the ORDER BY clause. How many developers have really stopped to think about how SQL Server processes these queries? Does it execute the query from top to bottom, starting with the SELECT clause and working its way down? One might assume that, but that is not how a query is processed in SQL Server at all. SQL Server logi- cally processes a query in the order indicated here by the number in parentheses: (8) SELECT (9) TOP (1) FROM (3) JOIN (2) ON 781 www.getcoolebook.com . ‘‘leapfrogged’’ over C. Those familiar with Microsoft s ORM history know that LINQ to SQL is not Microsoft s first endeavor at object-relational mapping. The roots of LINQ to SQL can actually be traced to something. working its way down? One might assume that, but that is not how a query is processed in SQL Server at all. SQL Server logi- cally processes a query in the order indicated here by the number in parentheses: (8). Catalog = Adventureworks;UID=username;PWD=password"; using (SqlConnection conn = new SqlConnection(ConnectionString)) { conn.Open(); SqlCommand cmd = conn.CreateCommand(); cmd.CommandType = CommandType.Text; cmd.CommandText