Cloning Entities in Silverlight 26 March 2011 Diederik-Krols Spatial Here's a nice little extension method that returns a deep copy of a relatively simple object - like an Entity or a Data Transfer Object. It is based on the DataContractSerializer, so the object should be serializable through XML or a DataContract. The extension works from Silverlight 2 upwards: public static class GenericExtensions { public static T Clone<T>(this T source) { DataContractSerializer dcs = new DataContractSerializer(typeof(T)); using (MemoryStream ms = new MemoryStream()) { dcs.WriteObject(ms, source); ms.Position = 0; return (T)dcs.ReadObject(ms); } } } Enjoy!
Turning Silverlight Application Themes into Toolkit Themes 16 March 2011 Diederik-Krols Spatial Not every development team is blessed with a professional graphical designer. So I can imagine that a lot of developers appreciate a free, nice, and very complete set of themes, such as the Silverlight 4 Application Themes. Tim Heuer's team has built for us the following themes: JetPack, Accent Color, Windows 7, and Cosmopolitan (inspired by Zune and Phone7). The application themes' styles are nicely structured into a set of xaml-files. I reused the code from my previous article to transform all of these into Silverlight Toolkit compatible theme classes. Since the Toolkit architecture requires us to deliver all assets in one single file, I grouped all the files in a list of MergedDictionaries. Here's the structure of the new Theme.xaml for the JetPack theme: <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <ResourceDictionary.MergedDictionaries> <!-- Mind: Absolute Uri's and forward slashes --> <!-- Client apps should have all references --> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/Brushes.xaml" /> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/CoreStyles.xaml" /> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/Fonts.xaml" /> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/SDKStyles.xaml" /> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/Styles.xaml" /> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/ToolkitStyles.xaml" /> <ResourceDictionary Source="/System.Windows.Controls.Theming.JetPack;component/Resources/ToolkitTheme.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> The application themes contain styles for any official and semi-official Silverlight control you can think of (core, SDK, and Toolkit), so the client needs a zillion of references. I needed to update the test application from the previous article. These are the references you'll need: I added a resource dictionary with a default foreground and background brush for the Theme elements. The JetPack theme uses a white forground brush by default, so a grey background seemed a good idea: <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:System.Windows.Controls.Theming" xmlns:theming="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.Toolkit"> <!-- Theme --> <LinearGradientBrush x:Key="ThemeBackgroundBrush" StartPoint="0.5,1" EndPoint="0.5,0"> <GradientStop Color="Gray" Offset="0" /> <GradientStop Color="LightGray" Offset="1" /> </LinearGradientBrush> <SolidColorBrush x:Key="ThemeForegroundBrush" Color="White" /> <Style TargetType="theming:Theme"> <Setter Property="Background" Value="{StaticResource ThemeBackgroundBrush}" /> <Setter Property="Foreground" Value="{StaticResource ThemeForegroundBrush}" /> </Style> <Style TargetType="local:JetPackTheme"> <Setter Property="Background" Value="{StaticResource ThemeBackgroundBrush}" /> <Setter Property="Foreground" Value="{StaticResource ThemeForegroundBrush}" /> </Style> </ResourceDictionary> The Theme class itself looks like the one from my previous article. Here's JetPackTheme.cs: namespace System.Windows.Controls.Theming { using System; /// <summary> /// Implicitly applies the JetPack theme to all of its descendent FrameworkElements. /// </summary> public partial class JetPackTheme : Theme { /// <summary> /// Stores a reference to a Uri referring to the theme resource for the class. /// </summary> private static Uri ThemeResourceUri = new Uri("/System.Windows.Controls.Theming.JetPack;component/Theme.xaml", UriKind.Relative); /// <summary> /// Initializes a new instance of the JetPackTheme class. /// </summary> public JetPackTheme() : base(ThemeResourceUri) { } /// <summary> /// Gets a value indicating whether this theme is the application theme. /// </summary> /// <param name="app">Application instance.</param> /// <returns>True if this theme is the application theme.</returns> public static bool GetIsApplicationTheme(Application app) { return GetApplicationThemeUri(app) == ThemeResourceUri; } /// <summary> /// Sets a value indicating whether this theme is the application theme. /// </summary> /// <param name="app">Application instance.</param> /// <param name="value">True if this theme should be the application theme.</param> public static void SetIsApplicationTheme(Application app, bool value) { SetApplicationThemeUri(app, ThemeResourceUri); } } } Here's how to apply the JetPackTheme in an application: JetPackTheme.SetIsApplicationTheme(App.Current, true); Here's how the JetPack theme looks like in the test application: Source Code Here's the whole source code, containing all four application themes, and the test container: U2UConsult.Silverlight.ThemeBuilder.zip (2,99 mb) Enjoy!
Very Spatial Wishes 20 December 2010 Diederik-Krols Spatial, SQL Server Last year I encountered this T-SQL script on a MSDN Forum. Run it in SQL Management Studio (not Visual Studio!), and select 'Spatial Results': -- Original Source: http://social.msdn.microsoft.com/Forums/en/sqlspatial/thread/d52c686e-30cc-4ae0-bdc7-ae4a2536cd64 -- Prepare the scene CREATE TABLE #ChristmasScene ( item varchar(32), shape geometry); -- Put up the tree and star INSERT INTO #ChristmasScene VALUES ('Tree', 'POLYGON((4 0, 0 0, 3 2, 1 2, 3 4, 1 4, 3 6, 2 6, 4 8, 6 6, 5 6, 7 4, 5 4, 7 2, 5 2, 8 0, 4 0))'), ('Base', 'POLYGON((2.5 0, 3 -1, 5 -1, 5.5 0, 2.5 0))'), ('Star', 'POLYGON((4 7.5, 3.5 7.25, 3.6 7.9, 3.1 8.2, 3.8 8.2, 4 8.9, 4.2 8.2, 4.9 8.2, 4.4 7.9, 4.5 7.25, 4 7.5))') -- Decorate the tree DECLARE @i int = 0, @x int, @y int; WHILE (@i < 20) BEGIN INSERT INTO #ChristmasScene VALUES ('Ball' + CAST(@i AS varchar(8)), geometry::Point(RAND() * 5 +1.5, RAND() * 6, 0).STBuffer(0.3)) SET @i = @i + 1; END -- Christmas Greeting INSERT INTO #ChristmasScene VALUES ('M', 'POLYGON((0 10, 0 11, 0.25 11, 0.5 10.5, 0.75 11, 1 11, 1 10, 0.75 10, 0.75 10.7, 0.5 10.2, 0.25 10.7, 0.25 10, 0 10))'), ('E', 'POLYGON((1 10, 1 11, 2 11, 2 10.8, 1.25 10.8, 1.25 10.6, 1.75 10.6, 1.75 10.4, 1.25 10.4, 1.25 10.2, 2 10.2, 2 10, 1 10))'), ('R', 'POLYGON((2 10, 2 11, 3 11, 3 10.5, 2.4 10.5, 3 10, 2.7 10, 2.2 10.4, 2.2 10, 2 10),(2.2 10.8, 2.8 10.8, 2.8 10.7, 2.2 10.7, 2.2 10.8))'), ('R', 'POLYGON((3 10, 3 11, 4 11, 4 10.5, 3.4 10.5, 4 10, 3.7 10, 3.2 10.4, 3.2 10, 3 10),(3.2 10.8, 3.8 10.8, 3.8 10.7, 3.2 10.7, 3.2 10.8))'), ('Y', 'POLYGON((4 11, 4.2 11, 4.5 10.6, 4.8 11, 5 11, 4.6 10.5, 4.6 10, 4.4 10, 4.4 10.5, 4 11))'), ('C', 'POLYGON((0 9, 0 10, 1 10, 1 9.8, 0.2 9.8, 0.2 9.2, 1 9.2, 1 9, 0 9))'), ('H', 'POLYGON((1 9, 1 10, 1.2 10, 1.2 9.6, 1.8 9.6, 1.8 10, 2 10, 2 9, 1.8 9, 1.8 9.4, 1.2 9.4, 1.2 9, 1 9))'), ('R', 'POLYGON((2 9, 2 10, 3 10, 3 9.5, 2.4 9.5, 3 9, 2.7 9, 2.2 9.4, 2.2 9, 2 9),(2.2 9.8, 2.8 9.8, 2.8 9.7, 2.2 9.7, 2.2 9.8))'), ('I', 'POLYGON((3.2 9, 3.2 9.2, 3.4 9.2, 3.4 9.8, 3.2 9.8, 3.2 10, 3.8 10, 3.8 9.8, 3.6 9.8, 3.6 9.2, 3.8 9.2, 3.8 9, 3.2 9))'), ('S', 'POLYGON((4 9, 4 9.2, 4.8 9.2, 4.8 9.4, 4 9.4, 4 10, 5 10, 5 9.8, 4.2 9.8, 4.2 9.6, 5 9.6, 5 9, 4 9))'), ('T', 'POLYGON((5 9.8, 5 10, 6 10, 6 9.8, 5.6 9.8, 5.6 9, 5.4 9, 5.4 9.8, 5 9.8))'), ('M', 'POLYGON((6 9, 6 10, 6.25 10, 6.5 9.5, 6.75 10, 7 10, 7 9, 6.75 9, 6.75 9.7, 6.5 9.2, 6.25 9.7, 6.25 9, 6 9))'), ('A', 'POLYGON((7 9, 7 10, 8 10, 8 9, 7.75 9, 7.75 9.3, 7.25 9.3, 7.25 9, 7 9),(7.25 9.5, 7.25 9.8, 7.75 9.8, 7.75 9.5, 7.25 9.5))'), ('S', 'POLYGON((8 9, 8 9.2, 8.8 9.2, 8.8 9.4, 8 9.4, 8 10, 9 10, 9 9.8, 8.2 9.8, 8.2 9.6, 9 9.6, 9 9, 8 9))'); -- Admire the scene SELECT * FROM #ChristmasScene -- Tidy up the pine needles and put away the decorations DROP TABLE #ChristmasScene Happy holidays!
The Missing Linq to SQL Spatial 03 January 2010 Diederik-Krols SQL Server, Spatial This article provides hints and hacks on how to use the SQL Server spatial data types -Geography and Geometry- in Linq to SQL. The data provider has a problem with serializing and deserializing the SQL UDT's. If you ever tried to use these data types in a Linq to SQL (or Entity Framework) project then you certainly encountered the following error: “One or more selected items contain a data type that is not supported by the designer”. Setting up a test environment I created a small database called 'Spatial' with a schema called 'Europe', and a table called 'Countries'. Here's the structure of the table: CREATE TABLE [Europe].[Countries]( [CountryID] [int] IDENTITY(1,1) NOT NULL, [CountryName] [nvarchar](50) NOT NULL, [Shape] [geography] NOT NULL, CONSTRAINT [PK_Countries] PRIMARY KEY CLUSTERED ( [CountryID] ASC ) The source code that accompanies this article contains the scripts to create the assets and populate the table. Here's an indication of the table contents: I created a Visual Studio 2008 Console application, and added a reference to Microsoft.SqlServer.Types. Then I added Linq to SQL classes (Europe.dbml). When I dropped the Europe.Counties table into the designer, the result was the expected error message: Hint 1: Use views that cast the data type I decided to create a view in the schema: vw_Countries. It exposes the same signature as the table, but with one difference: the shape column is returned as Varbinary(MAX) instead of Geography: CREATE VIEW [Europe].[vw_Countries] AS SELECT CountryID ,CountryName ,CONVERT(VARBINARY(MAX), Shape) AS Shape FROM Europe.Countries The Geography UDT is physically stored as Varbinary(MAX) in SQL Server, so the table and the view are basically the same. There's one important difference though: Visual Studio is not allergic to the view. You can easily drag and drop it into the data model designer: The Linq data provider delivers the varbinary data as System.Data.Linq.Binary, but is -strangly enough- unable to convert this back to SqlGeography. It took me a while to figure out the conversion myself, but here's a working version: Binary belgiumb = (from c in db.vw_Countries where c.CountryID == 4 select c.Shape).FirstOrDefault(); SqlGeography geo = new SqlGeography(); geo.Read(new System.IO.BinaryReader(new System.IO.MemoryStream(belgiumb.ToArray()))); Hint 2: Package your conversion code We're going to get stuck with this type of conversions for a while, so it makes sense to wrap the calculations e.g. in extension methods. Here's an example: /// <summary> /// Converts a Linq Binary to a SQL Server Geograpy. /// </summary> /// <remarks>Throws an Exception if the Binary contains invalid data.</remarks> public static SqlGeography AsGeography(this Binary binary) { if (binary == null) { return SqlGeography.Null; } SqlGeography result = new SqlGeography(); result.Read( new System.IO.BinaryReader( new System.IO.MemoryStream( binary.ToArray()))); return result; } So now I can call it like this: SqlGeography b = (from c in db.vw_Countries where c.CountryID == 4 select c.Shape).FirstOrDefault().AsGeography(); Console.WriteLine(" The area of Belgium is {0} m²", b.STArea().Value); Hack 1: Change source mappings I wanted to execute my queries directly against the table. So I copy/pasted the view in the dbml, and modified the source mapping: Now I can call the queries like this: belgium = SqlGeography.Parse((from c in db.Countries where c.CountryID == 4 select c.Shape.ToString()).FirstOrDefault()); Hack 2: Change data type mappings Since I wanted IntelliSense, I copy/pasted the Country table again, and changed the .NET type of the Shape property to SqlGeography: The good news is: I now have IntelliSense at design time: The bad news is: I will have exceptions all over the place. If you return a spatial data type in the select, then a query is successfully constructed and sent to SQL Server, but the return values cannot be deserialized. Here's such a query: belgium = (from c in db.Countries2 where c.CountryID == 4 select c.Shape).FirstOrDefault(); It results in a runtime exception: If you call a method -apart from ToString()- on a spatial data type in the where-clause, then you'll bump into a NotSupportedException already at compile time. Here's such a query: var query = (from c in db.Countries2 where (c.Shape.STArea() > 10).Value select c.CountryName).FirstOrDefault(); And its result at compile time: Intermezzo I tried to use Varbinary as data type for the _Shape backing variable, and SqlGeography as data type for the Shape property, and call conversions in the getter and setter. It didn't work: the getters and setters seem to be bypassed by the Linq provider. Conclusion: hack 2 was not a good idea. We'll stop using the Countries2 entity... Hint 3: Use scalar functions Since STIntersects cannot be used, I created a scalar function that takes two Well-Known Texts, and returns whether or not the shapes intersect: CREATE FUNCTION [Europe].[Intersects] ( @Shape1 NVarchar(MAX), @Shape2 NVarchar(MAX) ) RETURNS integer AS BEGIN DECLARE @Geo1 Geography = geography::Parse(@Shape1) DECLARE @Geo2 Geography = geography::Parse(@Shape2) RETURN @Geo1.STIntersects(@Geo2) END Again, Visual Studio's designer has no problem with this, so you can drag and drop the function into the DataContext: So you can use it in your queries: var query = from c in db.Countries where db.Intersects(c.Shape.ToString(), belgium.ToString()) == 1 select c.CountryName; The response time seems to be good enough against such a small table. But I know that it is never a good idea to call functions in the where-clause of a query. It will force a table scan and an evaluation of the function for each row in the table. If you can live with the performance, then I suggest you just stick to this type of functions: they're highly reusable since they don't contain any hard-code table names. Hint 4: Use table valued functions If your geographical tables get bigger, it makes sense to define table valued functions or stored procedures to do the heavy lifting. The following example doesn't mask the usage of STIntersects in the where-clause, and may let SQL Server decide to use a spatial index (or you may even use a hint): CREATE FUNCTION [Europe].[Intersectors] ( @Shape NVarchar(MAX) ) RETURNS TABLE AS RETURN ( SELECT CountryID, CountryName FROM Europe.Countries WHERE Shape.STIntersects(geography::Parse(@Shape)) = 1 ) And yes: I do realize that Intersectors probable doesn't appear in a regular dictionary... Here's how a call to it looks like in Linq - we still have IntelliSense: var query = from c in db.Intersectors(belgium.ToString()) select c.CountryName; I couldn't keep myself from comparing the performance of the two functions. When I checked the actual query plans,my jaw hit the ground , ouch! Completely against my expectation, the cost of the table value function was higher than the cost of the scalar function. And not just a little bit, but 5 times higher: Anyway, you should never blindly base your conclusions on just the query plans, so I started the SQL Profiler to inspect reality. After thorough testing I was relieved . I observed that the tabled valued function easily outruns the scalar version: it consumes between 3 and 8 times less CPU and returns the results 5 to 10 times faster. This is actually an impressive difference for such a small table. Here's the result from a test session (scripts are included in the source code): A negative aspect of this table valued function is its reusability: the function only works against the Europe.Countries table. If you need to query more tables with spatial data, then you need to add complexity to it, or start copy/pasting. Conclusions The current Linq to Sql provider doesn't support the SQL Server spatial data types properly, so you have to be creative if you want or need to use these. It's not a waste of time to invest some effort in implementing the work arounds I suggested, and in optimizing and performance tuning these. Your solution will last for a while, since there seems to be no Microsoft solution on the horizon. The problems are not solved in SQL 2008 R2, Visual Studio 2010, or .NET 4.0. Source code Here's the source code for the test program. It also contains the necessary SQL scripts for creating and populating the entities, as well as for testing and performance tuning: U2UConsult.DockOfTheBay.LinqToSpatialSample.zip (153,76 kb) Here's how its output should look like: Credits I would like to thank my colleague Kris Vandermotten for his input during the research.