diff --git a/Madd0.AzureStorageDriver/AzureDriver.cs b/Madd0.AzureStorageDriver/AzureDriver.cs index 011d67d..da0a611 100644 --- a/Madd0.AzureStorageDriver/AzureDriver.cs +++ b/Madd0.AzureStorageDriver/AzureDriver.cs @@ -8,8 +8,9 @@ namespace Madd0.AzureStorageDriver { - using System; using System.Collections.Generic; + using System.Data.Services.Client; + using System.Reflection; using LINQPad.Extensibility.DataContext; using Madd0.AzureStorageDriver.Properties; @@ -18,6 +19,9 @@ namespace Madd0.AzureStorageDriver /// public class AzureDriver : DynamicDataContextDriver { + /// + /// Gets the name of the driver author. + /// public override string Author { get { return Resources.AuthorName; } @@ -39,6 +43,12 @@ public override string GetConnectionDescription(IConnectionInfo connectionInfo) return new StorageAccountProperties(connectionInfo).DisplayName; } + /// + /// Shows the connection dialog. + /// + /// The connection info. + /// if set to true [is new connection]. + /// public override bool ShowConnectionDialog(IConnectionInfo connectionInfo, bool isNewConnection) { if (isNewConnection) @@ -50,6 +60,12 @@ public override bool ShowConnectionDialog(IConnectionInfo connectionInfo, bool i return result == true; } + /// + /// Determines whether two repositories are equivalent. + /// + /// The connection information of the first repository. + /// The connection information of the second repository. + /// true if both repositories use the same account name; false otherwise. public override bool AreRepositoriesEquivalent(IConnectionInfo connection1, IConnectionInfo connection2) { var account1 = (string)connection1.DriverData.Element("AccountName") ?? string.Empty; @@ -58,6 +74,10 @@ public override bool AreRepositoriesEquivalent(IConnectionInfo connection1, ICon return account1.Equals(account2); } + /// + /// Gets the assemblies to add. + /// + /// A list of assembly names to add in order to execute the current driver. public override IEnumerable GetAssembliesToAdd() { return new string[] @@ -67,6 +87,10 @@ public override IEnumerable GetAssembliesToAdd() }; } + /// + /// Gets the namespaces to add. + /// + /// A list of namespaces to add in order to execute this driver. public override IEnumerable GetNamespacesToAdd() { return new string[] @@ -77,34 +101,74 @@ public override IEnumerable GetNamespacesToAdd() }; } - public override List GetSchemaAndBuildAssembly(IConnectionInfo connectionInfo, System.Reflection.AssemblyName assemblyToBuild, ref string nameSpace, ref string typeName) + /// + /// Gets the schema and builds the assembly that contains the typed data context. + /// + /// The connection information. + /// The assembly to build. + /// The namespace to be used in the generated code. + /// Name of the type of the typed data context. + /// A list of instaces that describes the current schema. + public override List GetSchemaAndBuildAssembly(IConnectionInfo connectionInfo, AssemblyName assemblyToBuild, ref string @namespace, ref string typeName) { + // The helper class SchemaBuilder will do the heavy lifting return SchemaBuilder.GetSchemaAndBuildAssembly( new StorageAccountProperties(connectionInfo), this.GetDriverFolder(), assemblyToBuild, - ref nameSpace, - ref typeName); + @namespace, + typeName); } + /// + /// Gets the context constructor arguments. + /// + /// The connection info. + /// An ordered collection of objects to pass to the data context as arguments. public override object[] GetContextConstructorArguments(IConnectionInfo connectionInfo) { var properties = new StorageAccountProperties(connectionInfo); + var storageAccount = properties.GetStorageAccount(); + return new object[] { - properties.GetStorageAccount().TableEndpoint.ToString(), - properties.GetStorageAccount().Credentials + storageAccount.TableEndpoint.ToString(), + storageAccount.Credentials, + storageAccount }; } + /// + /// Gets the context constructor parameters. + /// + /// The connection info. + /// A list of objects that describe the parameters + /// of the typed data context's constructor. public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo connectionInfo) { return new[] { new ParameterDescriptor("baseAddress", "System.String"), - new ParameterDescriptor("credentials", "Microsoft.WindowsAzure.StorageCredentials") + new ParameterDescriptor("credentials", "Microsoft.WindowsAzure.StorageCredentials"), + new ParameterDescriptor("account", "Microsoft.WindowsAzure.CloudStorageAccount") }; } + + /// + /// Initializes the data context. + /// + /// In this driver, initialization consists of listening to the + /// event in order to extract the requested + /// URI and display it in the SQL tab. + /// The connection info. + /// The context. + /// The execution manager. + public override void InitializeContext(IConnectionInfo connectionInfo, object context, QueryExecutionManager executionManager) + { + var dsContext = (DataServiceContext)context; + + dsContext.SendingRequest += (sender, e) => executionManager.SqlTranslationWriter.WriteLine(e.Request.RequestUri); + } } } diff --git a/Madd0.AzureStorageDriver/ConnectionDialog.xaml.cs b/Madd0.AzureStorageDriver/ConnectionDialog.xaml.cs index caac10e..32404e3 100644 --- a/Madd0.AzureStorageDriver/ConnectionDialog.xaml.cs +++ b/Madd0.AzureStorageDriver/ConnectionDialog.xaml.cs @@ -11,6 +11,7 @@ namespace Madd0.AzureStorageDriver using System; using System.Runtime.InteropServices; using System.Windows; + using System.Windows.Interop; using LINQPad.Extensibility.DataContext; /// @@ -18,6 +19,10 @@ namespace Madd0.AzureStorageDriver /// public partial class ConnectionDialog : Window { + /// + /// Initializes a new instance of the class. + /// + /// The connection info. public ConnectionDialog(IConnectionInfo connectionInfo) { InitializeComponent(); @@ -25,27 +30,42 @@ public ConnectionDialog(IConnectionInfo connectionInfo) DataContext = new StorageAccountProperties(connectionInfo); } - [DllImport("user32.dll")] - static extern uint GetWindowLong(IntPtr hWnd, int nIndex); - - [DllImport("user32.dll")] - static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); - - private const int GWL_STYLE = -16; - - private const uint WS_SYSMENU = 0x80000; - + /// + /// Raises the event. + /// + /// An that contains the event data. protected override void OnSourceInitialized(EventArgs e) { - IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle; + IntPtr hwnd = new WindowInteropHelper(this).Handle; + + // Change the window style to remove icon and buttons SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & (0xFFFFFFFF ^ WS_SYSMENU)); base.OnSourceInitialized(e); } + /// + /// Called when the OK button is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnOkClick(object sender, RoutedEventArgs e) { this.DialogResult = true; } + + #region p/invoke + + [DllImport("user32.dll")] + private static extern uint GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); + + private const int GWL_STYLE = -16; + + private const uint WS_SYSMENU = 0x80000; + + #endregion } } diff --git a/Madd0.AzureStorageDriver/DataContextTemplate.cs b/Madd0.AzureStorageDriver/DataContextTemplate.cs new file mode 100644 index 0000000..9e379c6 --- /dev/null +++ b/Madd0.AzureStorageDriver/DataContextTemplate.cs @@ -0,0 +1,458 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 10.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Madd0.AzureStorageDriver +{ + using System; + using System.Collections; + using System.Collections.Generic; + + + #line 1 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "10.0.0.0")] + public partial class DataContextTemplate : DataContextTemplateBase + { + public virtual string TransformText() + { + this.Write("\r\nnamespace "); + + #line 6 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.Namespace)); + + #line default + #line hidden + this.Write("\r\n{\r\n using System;\r\n using System.Data.Services.Client;\r\n using Microso" + + "ft.WindowsAzure;\r\n using Microsoft.WindowsAzure.StorageClient;\r\n\r\n public " + + "class "); + + #line 13 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.TypeName)); + + #line default + #line hidden + this.Write(" : TableServiceContext\r\n {\r\n public "); + + #line 15 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.TypeName)); + + #line default + #line hidden + this.Write(@"(string baseAddress, StorageCredentials credentials, CloudStorageAccount account) + : base(baseAddress, credentials) + { + this.TableClient = account.CreateCloudTableClient(); + } + + public CloudTableClient TableClient + { + get; + private set; + } + +"); + + #line 27 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + +foreach (var table in this.Tables) +{ + + + #line default + #line hidden + this.Write(" public DataServiceQuery<"); + + #line 31 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(table.Name)); + + #line default + #line hidden + this.Write("Entity> "); + + #line 31 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(table.Name)); + + #line default + #line hidden + this.Write("\r\n {\r\n get\r\n {\r\n return this.CreateQu" + + "ery<"); + + #line 35 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(table.Name)); + + #line default + #line hidden + this.Write("Entity>(\""); + + #line 35 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(table.Name)); + + #line default + #line hidden + this.Write("\");\r\n }\r\n }\r\n"); + + #line 38 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + +} + + + #line default + #line hidden + this.Write(" }\r\n\r\n"); + + #line 43 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + +foreach (var table in this.Tables) +{ + + + #line default + #line hidden + this.Write(" public class "); + + #line 47 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(table.Name)); + + #line default + #line hidden + this.Write("Entity : TableServiceEntity\r\n {\r\n "); + + #line 49 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + + foreach (var column in table.Columns) + { + if (!this.DefaultProperties.Contains(column.Name)) + { + + + #line default + #line hidden + this.Write(" public "); + + #line 55 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(column.TypeName)); + + #line default + #line hidden + this.Write(" "); + + #line 55 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(column.Name)); + + #line default + #line hidden + this.Write("\r\n {\r\n get;\r\n set;\r\n }\r\n "); + + #line 60 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + + } + } + + + #line default + #line hidden + this.Write(" }\r\n"); + + #line 65 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + +} + + + #line default + #line hidden + this.Write("}\r\n\r\n"); + return this.GenerationEnvironment.ToString(); + } + + #line 70 "C:\Users\madd0\Documents\Personal Projects\Madd0.AzureStorageDriver\Madd0.AzureStorageDriver\DataContextTemplate.tt" + +private readonly List DefaultProperties = new List { "PartitionKey", "RowKey", "Timestamp" }; + +public string Namespace { get; set; } + +public string TypeName { get; set; } + +public IEnumerable Tables { get; set; } + + + #line default + #line hidden + } + + #line default + #line hidden + #region Base class + /// + /// Base class for this transformation + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "10.0.0.0")] + public class DataContextTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Madd0.AzureStorageDriver/DataContextTemplate.tt b/Madd0.AzureStorageDriver/DataContextTemplate.tt new file mode 100644 index 0000000..1bf623f --- /dev/null +++ b/Madd0.AzureStorageDriver/DataContextTemplate.tt @@ -0,0 +1,78 @@ +<#@ template language="C#" #> +<#@ import namespace="System" #> +<#@ import namespace="System.Collections" #> +<#@ import namespace="System.Collections.Generic" #> + +namespace <#= this.Namespace #> +{ + using System; + using System.Data.Services.Client; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.StorageClient; + + public class <#= this.TypeName #> : TableServiceContext + { + public <#= this.TypeName #>(string baseAddress, StorageCredentials credentials, CloudStorageAccount account) + : base(baseAddress, credentials) + { + this.TableClient = account.CreateCloudTableClient(); + } + + public CloudTableClient TableClient + { + get; + private set; + } + +<# +foreach (var table in this.Tables) +{ +#> + public DataServiceQuery<<#= table.Name #>Entity> <#= table.Name #> + { + get + { + return this.CreateQuery<<#= table.Name #>Entity>("<#= table.Name #>"); + } + } +<# +} +#> + } + +<# +foreach (var table in this.Tables) +{ +#> + public class <#= table.Name #>Entity : TableServiceEntity + { + <# + foreach (var column in table.Columns) + { + if (!this.DefaultProperties.Contains(column.Name)) + { + #> + public <#= column.TypeName #> <#= column.Name #> + { + get; + set; + } + <# + } + } + #> + } +<# +} +#> +} + +<#+ +private readonly List DefaultProperties = new List { "PartitionKey", "RowKey", "Timestamp" }; + +public string Namespace { get; set; } + +public string TypeName { get; set; } + +public IEnumerable Tables { get; set; } +#> \ No newline at end of file diff --git a/Madd0.AzureStorageDriver/GenericEntity.cs b/Madd0.AzureStorageDriver/GenericEntity.cs deleted file mode 100644 index ae37d2f..0000000 --- a/Madd0.AzureStorageDriver/GenericEntity.cs +++ /dev/null @@ -1,47 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) madd0, . All rights reserved. -// -//----------------------------------------------------------------------- -namespace Madd0.AzureStorageDriver -{ - using System; - using System.Linq; - using Microsoft.WindowsAzure.StorageClient; - using System.Data.Services.Common; - using System.Collections.Generic; - - [DataServiceKey("PartitionKey", "RowKey")] - public class GenericEntity : TableServiceEntity - { - Dictionary properties = new Dictionary(); - - /// - /// Gets or sets the table name. - /// - /// The table name. - public string TableName - { - get; - set; - } - - public Dictionary Properties - { - get { return this.properties; } - } - - internal string this[string key] - { - get - { - return this.properties[key]; - } - - set - { - this.properties[key] = value; - } - } - } -} diff --git a/Madd0.AzureStorageDriver/Madd0.AzureStorageDriver.csproj b/Madd0.AzureStorageDriver/Madd0.AzureStorageDriver.csproj index 805463b..70c000c 100644 --- a/Madd0.AzureStorageDriver/Madd0.AzureStorageDriver.csproj +++ b/Madd0.AzureStorageDriver/Madd0.AzureStorageDriver.csproj @@ -40,6 +40,7 @@ ..\References\LINQPad.exe + False @@ -56,11 +57,22 @@ - + + True + True + DataContextTemplate.tt + + ConnectionDialog.xaml - + + + + True + True + Exceptions.resx + True @@ -68,17 +80,25 @@ Resources.resx - + + + TextTemplatingFilePreprocessor + DataContextTemplate.cs + + + ResXFileCodeGenerator + Exceptions.Designer.cs + ResXFileCodeGenerator - Resources.Designer.cs + Resources1.Designer.cs @@ -87,6 +107,9 @@ MSBuild:Compile + + + "$(ProjectDir)DevDeploy.bat" diff --git a/Madd0.AzureStorageDriver/Model/CloudTable.cs b/Madd0.AzureStorageDriver/Model/CloudTable.cs new file mode 100644 index 0000000..713336a --- /dev/null +++ b/Madd0.AzureStorageDriver/Model/CloudTable.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) 2012 Mauricio DIAZ ORLICH. +// Code licensed under the MIT X11 license. +// +// Mauricio DIAZ ORLICH +//----------------------------------------------------------------------- + +namespace Madd0.AzureStorageDriver +{ + using System.Collections.Generic; + + /// + /// Holds information about a table from Azure storage. + /// + public class CloudTable + { + /// + /// Gets or sets the name of the table. + /// + public string Name + { + get; + set; + } + + /// + /// Gets or sets the table's properties. + /// + public IEnumerable Columns + { + get; + set; + } + } +} diff --git a/Madd0.AzureStorageDriver/Model/GenericEntity.cs b/Madd0.AzureStorageDriver/Model/GenericEntity.cs new file mode 100644 index 0000000..3fb75a2 --- /dev/null +++ b/Madd0.AzureStorageDriver/Model/GenericEntity.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) 2012 Mauricio DIAZ ORLICH. +// Code licensed under the MIT X11 license. +// +// Mauricio DIAZ ORLICH +//----------------------------------------------------------------------- + +namespace Madd0.AzureStorageDriver +{ + using System.Collections.Generic; + using Microsoft.WindowsAzure.StorageClient; + + /// + /// Represents a generic entity from table storage. + /// + /// + /// This class extends , which gives it the + /// , and properties, + /// but all other properties that are obtained from table storage are simply stored in its + /// dictionary as string values, where the property name is the property in + /// the dictionary. + /// + public class GenericEntity : TableServiceEntity + { + /// + /// Holds the property data. + /// + private Dictionary properties = new Dictionary(); + + /// + /// Gets or sets the table name. + /// + /// The table name. + public string TableName + { + get; + set; + } + + /// + /// Gets the list of properties of a table entity (except for PartitionKey, RowKey and + /// Timestamp) and their string values. + /// + public Dictionary Properties + { + get { return this.properties; } + } + + /// + /// Gets or sets the with the specified property. + /// + /// The name of the property. + public string this[string property] + { + get + { + return this.properties[property]; + } + + set + { + this.properties[property] = value; + } + } + } +} diff --git a/Madd0.AzureStorageDriver/StorageAccountProperties.cs b/Madd0.AzureStorageDriver/Model/StorageAccountProperties.cs similarity index 70% rename from Madd0.AzureStorageDriver/StorageAccountProperties.cs rename to Madd0.AzureStorageDriver/Model/StorageAccountProperties.cs index 175ba95..5a890a7 100644 --- a/Madd0.AzureStorageDriver/StorageAccountProperties.cs +++ b/Madd0.AzureStorageDriver/Model/StorageAccountProperties.cs @@ -9,9 +9,8 @@ namespace Madd0.AzureStorageDriver { using System; - using System.Linq; - using LINQPad.Extensibility.DataContext; using System.Xml.Linq; + using LINQPad.Extensibility.DataContext; using Madd0.AzureStorageDriver.Properties; using Microsoft.WindowsAzure; @@ -23,18 +22,29 @@ public class StorageAccountProperties private readonly IConnectionInfo _connectionInfo; private readonly XElement _driverData; + /// + /// Initializes a new instance of the class. + /// + /// The connection info. public StorageAccountProperties(IConnectionInfo connectionInfo) { this._connectionInfo = connectionInfo; this._driverData = connectionInfo.DriverData; } + /// + /// Gets or sets a value indicating whether this connection should be remembered. + /// + /// true if this connection should be remembered; otherwise, false. public bool Persist { get { return this._connectionInfo.Persist; } set { this._connectionInfo.Persist = value; } } + /// + /// Gets the display name of the connection. + /// public string DisplayName { get @@ -50,6 +60,10 @@ public string DisplayName } } + /// + /// Gets or sets a value indicating whether local storage is being used. + /// + /// true if local storage is used; otherwise, false. public bool UseLocalStorage { get @@ -69,6 +83,10 @@ public bool UseLocalStorage } } + /// + /// Gets or sets a value indicating whether to use HTTPS. + /// + /// true if HTTPS is used; otherwise, false. public bool UseHttps { get @@ -83,12 +101,18 @@ public bool UseHttps } } + /// + /// Gets or sets the name of the storage account. + /// public string AccountName { get { return (string)this._driverData.Element("AccountName") ?? string.Empty; } set { this._driverData.SetElementValue("AccountName", value); } } + /// + /// Gets or sets the key for the storage account. + /// public string AccountKey { get @@ -104,6 +128,27 @@ public string AccountKey } } + /// + /// Gets a instace for the current connection. + /// + /// A instace configured with the credentials + /// of the current connection. + public CloudStorageAccount GetStorageAccount() + { + if (this.UseLocalStorage) + { + return CloudStorageAccount.DevelopmentStorageAccount; + } + else + { + return new CloudStorageAccount(new StorageCredentialsAccountAndKey(this.AccountName, this.AccountKey), this.UseHttps); + } + } + + /// + /// Clears the account name and key. + /// + /// This method is called when local storage is used. private void ClearAccountNameAndKey() { var accountName = this._driverData.Element("AccountName"); @@ -119,17 +164,5 @@ private void ClearAccountNameAndKey() accountKey.Remove(); } } - - public CloudStorageAccount GetStorageAccount() - { - if (this.UseLocalStorage) - { - return CloudStorageAccount.DevelopmentStorageAccount; - } - else - { - return new CloudStorageAccount(new StorageCredentialsAccountAndKey(this.AccountName, this.AccountKey), this.UseHttps); - } - } } } diff --git a/Madd0.AzureStorageDriver/Model/TableColumn.cs b/Madd0.AzureStorageDriver/Model/TableColumn.cs new file mode 100644 index 0000000..6d123a0 --- /dev/null +++ b/Madd0.AzureStorageDriver/Model/TableColumn.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) 2012 Mauricio DIAZ ORLICH. +// Code licensed under the MIT X11 license. +// +// Mauricio DIAZ ORLICH +//----------------------------------------------------------------------- + +namespace Madd0.AzureStorageDriver +{ + /// + /// Holds information about a column of a table in Azure storage. + /// + public class TableColumn + { + /// + /// Gets or sets the name of the property. + /// + public string Name + { + get; + set; + } + + /// + /// Gets or sets the type name of the property. + /// + public string TypeName + { + get; + set; + } + } +} diff --git a/Madd0.AzureStorageDriver/Properties/AssemblyInfo.cs b/Madd0.AzureStorageDriver/Properties/AssemblyInfo.cs index dfef70b..e8b2690 100644 --- a/Madd0.AzureStorageDriver/Properties/AssemblyInfo.cs +++ b/Madd0.AzureStorageDriver/Properties/AssemblyInfo.cs @@ -1,44 +1,29 @@ //----------------------------------------------------------------------- -// -// Copyright (c) madd0, . All rights reserved. +// +// Copyright (c) 2012 Mauricio DIAZ ORLICH. +// Code licensed under the MIT X11 license. // +// Mauricio DIAZ ORLICH //----------------------------------------------------------------------- + using System; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Resources; +using System.Runtime.InteropServices; +using Madd0.AzureStorageDriver.Properties; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. [assembly: AssemblyTitle("AzureStorageDriver")] [assembly: AssemblyDescription("A LINQPad driver to perform queries againt Azure Table Storage.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("madd0 (http://www.madd0.com)")] [assembly: AssemblyProduct("AzureStorageDriver")] [assembly: AssemblyCopyright("Copyright © 2012 Mauricio DIAZ ORLICH")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("0d14276c-9f70-46bf-903e-877750970f43")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.0.0")] -[assembly: AssemblyFileVersion("0.1.0.0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("")] diff --git a/Madd0.AzureStorageDriver/Properties/Exceptions.Designer.cs b/Madd0.AzureStorageDriver/Properties/Exceptions.Designer.cs new file mode 100644 index 0000000..babf237 --- /dev/null +++ b/Madd0.AzureStorageDriver/Properties/Exceptions.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.239 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Madd0.AzureStorageDriver.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Exceptions { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Exceptions() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Madd0.AzureStorageDriver.Properties.Exceptions", typeof(Exceptions).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cannot compile typed context: {0} (line {1}). + /// + internal static string CannotCompileCode { + get { + return ResourceManager.GetString("CannotCompileCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deserialized type '{0}' is not supported.. + /// + internal static string TypeNotSupported { + get { + return ResourceManager.GetString("TypeNotSupported", resourceCulture); + } + } + } +} diff --git a/Madd0.AzureStorageDriver/Properties/Exceptions.resx b/Madd0.AzureStorageDriver/Properties/Exceptions.resx new file mode 100644 index 0000000..b3ad500 --- /dev/null +++ b/Madd0.AzureStorageDriver/Properties/Exceptions.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot compile typed context: {0} (line {1}) + + + Deserialized type '{0}' is not supported. + + \ No newline at end of file diff --git a/Madd0.AzureStorageDriver/Properties/Resources.Designer.cs b/Madd0.AzureStorageDriver/Properties/Resources.Designer.cs index eded67e..e0c260f 100644 --- a/Madd0.AzureStorageDriver/Properties/Resources.Designer.cs +++ b/Madd0.AzureStorageDriver/Properties/Resources.Designer.cs @@ -60,6 +60,24 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to AzureTableStorage. + /// + internal static string AssemblyTitle { + get { + return ResourceManager.GetString("AssemblyTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 0.1.0.0. + /// + internal static string AssemblyVersion { + get { + return ResourceManager.GetString("AssemblyVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to Mauricio Diaz Orlich. /// diff --git a/Madd0.AzureStorageDriver/SchemaBuilder.cs b/Madd0.AzureStorageDriver/SchemaBuilder.cs index 2b4c153..9781ba7 100644 --- a/Madd0.AzureStorageDriver/SchemaBuilder.cs +++ b/Madd0.AzureStorageDriver/SchemaBuilder.cs @@ -8,115 +8,213 @@ namespace Madd0.AzureStorageDriver { using System; - using System.Linq; + using System.CodeDom.Compiler; using System.Collections.Generic; - using LINQPad.Extensibility.DataContext; + using System.Data.Services.Client; + using System.IO; + using System.Linq; using System.Reflection; using System.Xml.Linq; - using Microsoft.WindowsAzure; - using Microsoft.WindowsAzure.StorageClient; - using System.Data.Services.Client; - using System.CodeDom.Compiler; + using LINQPad.Extensibility.DataContext; + using Madd0.AzureStorageDriver.Properties; using Microsoft.CSharp; - using System.IO; + using Microsoft.WindowsAzure.StorageClient; /// - /// TODO: Provide summary section in the documentation header. + /// Provides the methods necessary to determining the storage account's schema and to building + /// the typed data context . /// internal static class SchemaBuilder { + // XML namespaces + private static readonly XNamespace AtomNS = "http://www.w3.org/2005/Atom"; + private static readonly XNamespace dNS = "http://schemas.microsoft.com/ado/2007/08/dataservices"; + private static readonly XNamespace mNS = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; - public static List GetSchemaAndBuildAssembly(StorageAccountProperties properties, string driverFolder, AssemblyName name, ref string nameSpace, ref string typeName) - { + // Names of columns that should be marked as table keys. + private static readonly List keyColumns = new List { "PartitionKey", "RowKey" }; - // Read the EDM schema into an XDocument: - //XDocument data; - //using (XmlReader reader = GetSchemaReader(props)) - // data = XDocument.Load(reader); + /// + /// Gets the schema and builds the assembly. + /// + /// The current configuration. + /// The driver folder. Used to resolve dependencies. + /// The instace of the assembly being created. + /// The namespace to be used in the generated code. + /// Name of the type of the typed data context. + /// A list of instaces that describes the current schema. + public static List GetSchemaAndBuildAssembly(StorageAccountProperties properties, string driverFolder, AssemblyName name, string @namepace, string typeName) + { + // Get the model from Azure storage + var model = GetModel(properties); - // Generate the code using the ADO.NET Data Services classes: - //string code; - //using (XmlReader reader = data.CreateReader()) - // code = GenerateCode(reader, nameSpace); + // Generate C# code + var code = GenerateCode(typeName, @namepace, model); - // Compile the code into the assembly, using the assembly name provided: - BuildAssembly(name, driverFolder, typeName, nameSpace); + // And compile the code into the assembly + BuildAssembly(name, driverFolder, code); - // Use the schema to populate the Schema Explorer: - //List schema = GetSchema(data, out typeName); - List schema = GetSchema(properties); + // Generate the schema for LINQPad + List schema = GetSchema(model); return schema; } - private static void BuildAssembly(AssemblyName name, string driverFolder, string typeName, string nameSpace) + /// + /// Build a model of the current Azure storage account. This model will be used to generate + /// the typed code as well as the schema needed by LINQPad. + /// + /// The current configuration. + /// A list of instances that describe the current Azure + /// storage model. + private static IEnumerable GetModel(StorageAccountProperties properties) { - var code = @"namespace " + nameSpace + @" -{ - public class " + typeName + @" : Microsoft.WindowsAzure.StorageClient.TableServiceContext - { - public " + typeName + @"(string baseAddress, Microsoft.WindowsAzure.StorageCredentials credentials) - : base(baseAddress, credentials) + var tableClient = properties.GetStorageAccount().CreateCloudTableClient(); + + var dataContext = tableClient.GetDataServiceContext(); + + // Entity deserialization has to be handled in a particular way since we are using a GenericEntity to + // to read all tables + dataContext.ReadingEntity += OnReadingEntity; + + // First get a list of all tables + var model = (from tableName in tableClient.ListTables() + select new CloudTable + { + Name = tableName + }).ToList(); + + // Then go through them + foreach (var table in model) + { + // Read the first entity to determine the table's schema + var firstRow = dataContext.CreateQuery(table.Name).Take(1).FirstOrDefault(); + + if (null == firstRow) + { + // If there is no first entity, set a list with the mandatory PartitionKey, RowKey and + // Timestamp columns, which we know always exist + table.Columns = new[] + { + new TableColumn { Name = "PartitionKey", TypeName = GetType("Edm.String") }, + new TableColumn { Name = "RowKey", TypeName = GetType("Edm.String") }, + new TableColumn { Name = "Timestamp", TypeName = GetType("Edm.DateTime") } + }; + } + else + { + // Otherwise create a new TableColumn for each type + table.Columns = from columnName in firstRow.Properties + select new TableColumn + { + Name = columnName.Key, + TypeName = GetType(columnName.Value) + }; + } + } + + return model; + } + + /// + /// Generates the code to build the typed data context. + /// + /// Name of the type. + /// The namespace. + /// The model. + /// The code to be compiled as a string. + private static string GenerateCode(string typeName, string @namespace, IEnumerable model) { + // We use a T4-generated class as the template + var codeGenerator = new DataContextTemplate(); + + codeGenerator.Namespace = @namespace; + codeGenerator.TypeName = typeName; + codeGenerator.Tables = model; + return codeGenerator.TransformText(); } - } -}"; + + /// + /// Builds the assembly described by the . + /// + /// The instace of the assembly being created. + /// The driver folder. Used to resolve dependencies. + /// The code of the typed data context. + private static void BuildAssembly(AssemblyName name, string driverFolder, string code) + { CompilerResults results; + + var dependencies = new[] + { + "System.dll", + "System.Core.dll", + "System.Data.Services.Client.dll", + Path.Combine(driverFolder, "Microsoft.WindowsAzure.StorageClient.dll") + }; + + // Use the CSharpCodeProvider to compile. Since the driver is .NET 4.0, the typed assembly should be also using (var codeProvider = new CSharpCodeProvider(new Dictionary() { { "CompilerVersion", "v4.0" } })) { - var options = new CompilerParameters( - new [] { "System.dll", "System.Core.dll", "System.Xml.dll", "System.Data.Services.Client.dll", Path.Combine(driverFolder, "Microsoft.WindowsAzure.StorageClient.dll") }, - name.CodeBase, - true); +#if DEBUG + var options = new CompilerParameters(dependencies, name.CodeBase, true); +#else + var options = new CompilerParameters(dependencies, name.CodeBase, false); +#endif results = codeProvider.CompileAssemblyFromSource(options, code); } + if (results.Errors.Count > 0) - throw new Exception - ("Cannot compile typed context: " + results.Errors[0].ErrorText + " (line " + results.Errors[0].Line + ")"); + { + throw new Exception(string.Format(Exceptions.CannotCompileCode, results.Errors[0].ErrorText, results.Errors[0].Line)); + } } - private static List GetSchema(StorageAccountProperties properties) + /// + /// Transforms the model based on instances into a schema based on + /// instances for LINQPad. + /// + /// The model. + /// A schema for LINQPad. + private static List GetSchema(IEnumerable model) { - var tableClient = properties.GetStorageAccount().CreateCloudTableClient(); - var dataContext = tableClient.GetDataServiceContext(); - dataContext.ReadingEntity += OnReadingEntity; - - return (from tableName in tableClient.ListTables() - select new ExplorerItem(tableName, ExplorerItemKind.QueryableObject, ExplorerIcon.Table) + return (from table in model + select new ExplorerItem(table.Name, ExplorerItemKind.QueryableObject, ExplorerIcon.Table) { - Children = (from columnName in dataContext.CreateQuery(tableName).Take(1).First().Properties - select new ExplorerItem(columnName.Key + " (" + columnName.Value + ")", ExplorerItemKind.Property, ExplorerIcon.Column)).ToList() + Children = (from column in table.Columns + select new ExplorerItem(column.Name + " (" + column.TypeName + ")", ExplorerItemKind.Property, ExplorerIcon.Column) + { + Icon = keyColumns.Contains(column.Name) ? ExplorerIcon.Key : ExplorerIcon.Column, + DragText = column.Name + }).ToList(), + DragText = table.Name, + IsEnumerable = true }).ToList(); } - static void OnReadingEntity(object sender, ReadingWritingEntityEventArgs e) + /// + /// Called when the data services data context has finished trying to deserialize and entity + /// from table storage. + /// + /// The sender. + /// The instance containing the event data. + private static void OnReadingEntity(object sender, ReadingWritingEntityEventArgs e) { - // TODO: Make these statics - XNamespace AtomNamespace = "http://www.w3.org/2005/Atom"; - XNamespace AstoriaDataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices"; - XNamespace AstoriaMetadataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; - GenericEntity entity = e.Entity as GenericEntity; - if (entity == null) + if (null == entity) { return; } - entity.TableName = e.Data.Element(AtomNamespace + "link").Attribute("title").Value; + entity.TableName = e.Data.Element(AtomNS + "link").Attribute("title").Value; - // read each property, type and value in the payload - //var properties = e.Entity.GetType().GetProperties(); - //where properties.All(pp => pp.Name != p.Name.LocalName) - var q = from p in e.Data.Element(AtomNamespace + "content") - .Element(AstoriaMetadataNamespace + "properties") - .Elements() + var q = from p in e.Data.Element(AtomNS + "content").Element(mNS + "properties").Elements() select new { Name = p.Name.LocalName, - IsNull = string.Equals("true", p.Attribute(AstoriaMetadataNamespace + "null") == null ? null : p.Attribute(AstoriaMetadataNamespace + "null").Value, StringComparison.OrdinalIgnoreCase), - TypeName = p.Attribute(AstoriaMetadataNamespace + "type") == null ? "Edm.String" : p.Attribute(AstoriaMetadataNamespace + "type").Value, + IsNull = string.Equals("true", p.Attribute(mNS + "null") == null ? null : p.Attribute(mNS + "null").Value, StringComparison.OrdinalIgnoreCase), + TypeName = p.Attribute(mNS + "type") == null ? "Edm.String" : p.Attribute(mNS + "type").Value, p.Value }; @@ -126,28 +224,33 @@ select new } } - private static Type GetType(string type) + /// + /// Gets the C# type equivalent of an entity data model (Edm) type. + /// + /// The Edm type. + /// The C# type. + private static string GetType(string type) { - if (type == null) - return typeof(string); - switch (type) { - case "Edm.String": return typeof(string); - case "Edm.Byte": return typeof(byte); - case "Edm.SByte": return typeof(sbyte); - case "Edm.Int16": return typeof(short); - case "Edm.Int32": return typeof(int); - case "Edm.Int64": return typeof(long); - case "Edm.Double": return typeof(double); - case "Edm.Single": return typeof(float); - case "Edm.Boolean": return typeof(bool); - case "Edm.Decimal": return typeof(decimal); - case "Edm.DateTime": return typeof(DateTime); - case "Edm.Binary": return typeof(byte[]); - case "Edm.Guid": return typeof(Guid); - - default: throw new NotSupportedException("Not supported type " + type); + case "Edm.Binary": + return "byte[]"; + case "Edm.Boolean": + return "bool?"; + case "Edm.DateTime": + return "DateTime?"; + case "Edm.Double": + return "double?"; + case "Edm.Guid": + return "Guid?"; + case "Edm.Int32": + return "int?"; + case "Edm.Int64": + return "long?"; + case "Edm.String": + return "string"; + default: + throw new NotSupportedException(string.Format(Exceptions.TypeNotSupported, type)); } } }