mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-14 02:37:08 -07:00
Merge branch 'develop'
This commit is contained in:
commit
ed1be19563
1531 changed files with 4323 additions and 2918 deletions
6
src/.nuget/NuGet.Config
Normal file
6
src/.nuget/NuGet.Config
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<solution>
|
||||
<add key="disableSourceControlIntegration" value="true" />
|
||||
</solution>
|
||||
</configuration>
|
BIN
src/.nuget/NuGet.exe
Normal file
BIN
src/.nuget/NuGet.exe
Normal file
Binary file not shown.
71
src/.nuget/NuGet.targets
Normal file
71
src/.nuget/NuGet.targets
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
|
||||
|
||||
<!-- Windows specific commands -->
|
||||
<NuGetToolsPath Condition=" '$(OS)' == 'Windows_NT'">$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
|
||||
<PackagesConfig Condition=" '$(OS)' == 'Windows_NT'">$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
|
||||
<PackagesDir Condition=" '$(OS)' == 'Windows_NT'">$([System.IO.Path]::Combine($(SolutionDir), "packages"))</PackagesDir>
|
||||
|
||||
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
|
||||
<NuGetToolsPath Condition=" '$(OS)' != 'Windows_NT'">$(SolutionDir).nuget</NuGetToolsPath>
|
||||
<PackagesConfig Condition=" '$(OS)' != 'Windows_NT' ">packages.config</PackagesConfig>
|
||||
<PackagesDir Condition=" '$(OS)' != 'Windows_NT'">$(SolutionDir)packages</PackagesDir>
|
||||
|
||||
<!-- NuGet command -->
|
||||
<NuGetExePath>$(NuGetToolsPath)\nuget.exe</NuGetExePath>
|
||||
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
|
||||
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>
|
||||
|
||||
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
|
||||
|
||||
<!-- Package sources used to restore packages. By default will used the registered sources under %APPDATA%\NuGet\NuGet.Config -->
|
||||
<PackageSources>"http://build.nzbdrone.com/guestAuth/app/nuget/v1/FeedService.svc";"https://nuget.org/api/v2/"</PackageSources>
|
||||
|
||||
<!-- Enable the restore command to run before builds -->
|
||||
<RestorePackages Condition="$(RestorePackages) == ''">false</RestorePackages>
|
||||
|
||||
<!-- Property that enables building a package from a project -->
|
||||
<BuildPackage Condition="$(BuildPackage) == ''">false</BuildPackage>
|
||||
|
||||
<!-- Commands -->
|
||||
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)"</RestoreCommand>
|
||||
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols</BuildCommand>
|
||||
|
||||
<!-- Make the build depend on restore packages -->
|
||||
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
|
||||
RestorePackages;
|
||||
$(BuildDependsOn);
|
||||
</BuildDependsOn>
|
||||
|
||||
<!-- Make the build depend on restore packages -->
|
||||
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
|
||||
$(BuildDependsOn);
|
||||
BuildPackage;
|
||||
</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CheckPrerequisites">
|
||||
<!-- Raise an error if we're unable to locate nuget.exe -->
|
||||
<Error Condition="!Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(BuildCommand)"
|
||||
Condition=" '$(OS)' != 'Windows_NT' " />
|
||||
|
||||
<Exec Command="$(BuildCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition=" '$(OS)' == 'Windows_NT' " />
|
||||
</Target>
|
||||
</Project>
|
74
src/Exceptron.Client/Configuration/ExceptronConfiguration.cs
Normal file
74
src/Exceptron.Client/Configuration/ExceptronConfiguration.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using Exceptron.Client.Message;
|
||||
|
||||
namespace Exceptron.Client.Configuration
|
||||
{
|
||||
|
||||
public class ExceptronConfiguration : ConfigurationSection
|
||||
{
|
||||
public ExceptronConfiguration()
|
||||
{
|
||||
Host = "http://exceptron.azurewebsites.net/api/v1/";
|
||||
IncludeMachineName = true;
|
||||
}
|
||||
|
||||
public static ExceptronConfiguration ReadConfig(string sectionName = "exceptron")
|
||||
{
|
||||
var configSection = ConfigurationManager.GetSection(sectionName);
|
||||
|
||||
if (configSection == null)
|
||||
{
|
||||
throw new ConfigurationErrorsException("ExceptronConfiguration section missing.");
|
||||
}
|
||||
|
||||
return (ExceptronConfiguration)configSection;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// exceptron api address. Do not modify this property.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public string Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If ExceptronClinet should throw exceptions in case of an error. Default: <see cref="bool.False"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Its recommended that this flag is set to True during development and <see cref="bool.False"/> in production systems.
|
||||
/// If an exception is thrown while this flag is set to <see cref="bool.False"/> the thrown exception will be returned in <see cref="ExceptionResponse.Exception"/>
|
||||
/// </remarks>
|
||||
[ConfigurationProperty("throwExceptions", DefaultValue = false)]
|
||||
public bool ThrowExceptions
|
||||
{
|
||||
get { return (bool)this["throwExceptions"]; }
|
||||
set { this["throwExceptions"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The API of this application. Can find your API key in application settings page.
|
||||
/// </summary>
|
||||
[ConfigurationProperty("apiKey")]
|
||||
public string ApiKey
|
||||
{
|
||||
get { return (string)this["apiKey"]; }
|
||||
set { this["apiKey"] = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If the machine name should be attached to the exception report
|
||||
/// </summary>
|
||||
/// <remarks>Machine name can be usefull in webfarm enviroments when multiple
|
||||
/// servers are running the same app and the issue could be machine specific.
|
||||
/// Hoewever, You might want to disable this feature for privacy reasons.</remarks>
|
||||
[ConfigurationProperty("includeMachineName", DefaultValue = true)]
|
||||
public bool IncludeMachineName
|
||||
{
|
||||
get { return (bool)this["includeMachineName"]; }
|
||||
set { this["includeMachineName"] = value; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
62
src/Exceptron.Client/ExceptionData.cs
Normal file
62
src/Exceptron.Client/ExceptionData.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Exceptron.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information that will be used to construct an exception report.
|
||||
/// </summary>
|
||||
public class ExceptionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception that is being reported
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Component that experianced this exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is common to use the logger name that was used to log the exception as the component.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// DataAccess, Configuration, Registration, etc.
|
||||
/// </example>
|
||||
public string Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID that will uniquely identify the user
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This Id does not have to be tied to the user's identity.
|
||||
/// You can use a system generated unique ID such as GUID.
|
||||
/// This field is used to report how many unique users are experiencing an error.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// "62E5C8EF-0CA2-43AB-B278-FC6994F776ED"
|
||||
/// "Timmy@aol.com"
|
||||
/// "26437"
|
||||
/// </example>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any message that should be attached to this exceptions
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Something went wrong while checking for application updates.
|
||||
/// </example>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Severity of the exception being reported
|
||||
/// </summary>
|
||||
public ExceptionSeverity Severity { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="System.Web.HttpContext"/> that triggered this exception. If no <see cref="System.Web.HttpContext"/> is provided
|
||||
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/>
|
||||
/// </summary>
|
||||
public HttpContext HttpContext { get; set; }
|
||||
}
|
||||
}
|
28
src/Exceptron.Client/ExceptionSeverity.cs
Normal file
28
src/Exceptron.Client/ExceptionSeverity.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
namespace Exceptron.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Severity of the exception being reported
|
||||
/// </summary>
|
||||
public enum ExceptionSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// Excepted Error. Can be ignored
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Error that can be handled gracefully
|
||||
/// </summary>
|
||||
Warning = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Blocking user from completing their intended action
|
||||
/// </summary>
|
||||
Error = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Will most likely cause the application to crash
|
||||
/// </summary>
|
||||
Fatal = 3
|
||||
}
|
||||
}
|
88
src/Exceptron.Client/Exceptron.Client.csproj
Normal file
88
src/Exceptron.Client/Exceptron.Client.csproj
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{B1784698-592E-4132-BDFA-9817409E3A96}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Exceptron.Client</RootNamespace>
|
||||
<AssemblyName>Exceptron.Client</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>..\..\_output\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>..\..\_output\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.XML" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration\ExceptronConfiguration.cs" />
|
||||
<Compile Include="ExceptionData.cs" />
|
||||
<Compile Include="ExceptronClient.cs" />
|
||||
<Compile Include="ExceptronApiException.cs" />
|
||||
<Compile Include="IExceptronClient.cs" />
|
||||
<Compile Include="Message\ExceptionResponse.cs" />
|
||||
<Compile Include="ExceptionSeverity.cs" />
|
||||
<Compile Include="IRestClient.cs" />
|
||||
<Compile Include="Message\ExceptionReport.cs" />
|
||||
<Compile Include="fastJSON\Getters.cs" />
|
||||
<Compile Include="fastJSON\JSON.cs" />
|
||||
<Compile Include="fastJSON\JsonParser.cs" />
|
||||
<Compile Include="fastJSON\JsonSerializer.cs" />
|
||||
<Compile Include="fastJSON\SafeDictionary.cs" />
|
||||
<Compile Include="Message\Frame.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RestClient.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="NuGet\web.config.transform">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="Exceptron.Client.nuspec">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="fastJSON\license.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
20
src/Exceptron.Client/Exceptron.Client.nuspec
Normal file
20
src/Exceptron.Client/Exceptron.Client.nuspec
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>$id$</id>
|
||||
<version>$version$</version>
|
||||
<title>$id$</title>
|
||||
<authors>$author$</authors>
|
||||
<owners>$author$</owners>
|
||||
<projectUrl>https://github.com/Exceptron/Exceptron.Net</projectUrl>
|
||||
<iconUrl>https://www.exceptron.com/logos/Exceptron128.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Exceptron.Client is a .NET wrapper for exceptron's REST API. exceptron help you report your application error to a central location for review, reporting and analytics.</description>
|
||||
<language>en-US</language>
|
||||
<copyright>Copyright © 2012</copyright>
|
||||
<tags>Exception Logging Analytics Reporting Exceptron</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="NuGet\web.config.transform" target="content" />
|
||||
</files>
|
||||
</package>
|
16
src/Exceptron.Client/ExceptronApiException.cs
Normal file
16
src/Exceptron.Client/ExceptronApiException.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Exceptron.Client
|
||||
{
|
||||
public class ExceptronApiException : Exception
|
||||
{
|
||||
public ExceptronApiException(WebException innerException, string message)
|
||||
: base(message, innerException)
|
||||
{
|
||||
Response = (HttpWebResponse)innerException.Response;
|
||||
}
|
||||
|
||||
public HttpWebResponse Response { get; private set; }
|
||||
}
|
||||
}
|
321
src/Exceptron.Client/ExceptronClient.cs
Normal file
321
src/Exceptron.Client/ExceptronClient.cs
Normal file
|
@ -0,0 +1,321 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Exceptron.Client.Configuration;
|
||||
using Exceptron.Client.Message;
|
||||
|
||||
namespace Exceptron.Client
|
||||
{
|
||||
public class ExceptronClient : IExceptronClient
|
||||
{
|
||||
internal IRestClient RestClient { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version of Client
|
||||
/// </summary>
|
||||
public string ClientVersion
|
||||
{
|
||||
get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Name of Client
|
||||
/// </summary>
|
||||
public string ClientName
|
||||
{
|
||||
get { return "Official .NET"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Configuration
|
||||
/// </summary>
|
||||
public ExceptronConfiguration Configuration { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Framework Type of the Host Application (.Net/mono)
|
||||
/// </summary>
|
||||
public string FrameworkType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ExceptronClient"/>
|
||||
/// Loads <see cref="ExceptronConfiguration"/> from application config file.
|
||||
/// </summary>
|
||||
/// <param name="applicationVersion">Version of the currently running application</param>
|
||||
public ExceptronClient(Version applicationVersion)
|
||||
: this(ExceptronConfiguration.ReadConfig(), applicationVersion)
|
||||
{
|
||||
FrameworkType = ".Net";
|
||||
}
|
||||
|
||||
private readonly string _applicationVersion;
|
||||
private readonly string _maxFrameworkVersion;
|
||||
|
||||
|
||||
/// <param name="exceptronConfiguration">exceptron client configuration</param>
|
||||
/// <param name="applicationVersion"> </param>
|
||||
public ExceptronClient(ExceptronConfiguration exceptronConfiguration, Version applicationVersion)
|
||||
{
|
||||
if (exceptronConfiguration == null)
|
||||
throw new ArgumentNullException("exceptronConfiguration");
|
||||
|
||||
if (applicationVersion == null)
|
||||
throw new ArgumentNullException("applicationVersion");
|
||||
|
||||
if (string.IsNullOrEmpty(exceptronConfiguration.ApiKey))
|
||||
throw new ArgumentException("An API Key was not provided");
|
||||
|
||||
Configuration = exceptronConfiguration;
|
||||
|
||||
RestClient = new RestClient();
|
||||
|
||||
_applicationVersion = applicationVersion.ToString();
|
||||
|
||||
_maxFrameworkVersion = GetMaximumFrameworkVersion();
|
||||
|
||||
FrameworkType = ".Net";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exception">Exception that is being reported</param>
|
||||
/// <param name="component"
|
||||
/// example="DataAccess, Configuration, Registration, etc."
|
||||
/// remarks="It is common to use the logger name that was used to log the exception as the component.">Component that experienced this exception.</param>
|
||||
|
||||
/// <param name="severity">Severity of the exception being reported</param>
|
||||
/// <param name="message"
|
||||
/// example="Something went wrong while checking for application updates.">Any message that should be attached to this exceptions</param>
|
||||
/// <param name="userId"
|
||||
/// remarks="This Id does not have to be tied to the user's identity.
|
||||
/// You can use a system generated unique ID such as GUID.
|
||||
/// This field is used to report how many unique users are experiencing an error."
|
||||
/// example="
|
||||
/// 62E5C8EF-0CA2-43AB-B278-FC6994F776ED
|
||||
/// Timmy@aol.com
|
||||
/// 26437
|
||||
/// ">ID that will uniquely identify the user</param>
|
||||
/// <param name="httpContext"><see cref="System.Web.HttpContext"/> in which the exception occurred. If no <see cref="System.Web.HttpContext"/> is provided
|
||||
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/></param>
|
||||
/// <returns></returns>
|
||||
public ExceptionResponse SubmitException(Exception exception, string component, ExceptionSeverity severity = ExceptionSeverity.None, string message = null, string userId = null, HttpContext httpContext = null)
|
||||
{
|
||||
var exceptionData = new ExceptionData
|
||||
{
|
||||
Exception = exception,
|
||||
Component = component,
|
||||
Severity = severity,
|
||||
Message = message,
|
||||
UserId = userId,
|
||||
HttpContext = httpContext
|
||||
};
|
||||
|
||||
return SubmitException(exceptionData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exceptionData">Exception data to be reported to the server</param>
|
||||
public ExceptionResponse SubmitException(ExceptionData exceptionData)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateState(exceptionData);
|
||||
|
||||
var report = new ExceptionReport();
|
||||
|
||||
report.ap = Configuration.ApiKey;
|
||||
report.dn = ClientName;
|
||||
report.dv = ClientVersion;
|
||||
report.aver = _applicationVersion;
|
||||
|
||||
report.ext = exceptionData.Exception.GetType().FullName;
|
||||
report.stk = ConvertToFrames(exceptionData.Exception);
|
||||
report.exm = exceptionData.Exception.Message;
|
||||
|
||||
report.cmp = exceptionData.Component;
|
||||
report.uid = exceptionData.UserId;
|
||||
report.msg = exceptionData.Message;
|
||||
report.sv = (int)exceptionData.Severity;
|
||||
report.fv = _maxFrameworkVersion;
|
||||
report.ft = FrameworkType;
|
||||
|
||||
SetHttpInfo(exceptionData, report);
|
||||
SetEnviromentInfo(report);
|
||||
|
||||
var exceptionResponse = RestClient.Put<ExceptionResponse>(Configuration.Host, report);
|
||||
|
||||
exceptionData.Exception.Data["et"] = exceptionResponse.RefId;
|
||||
|
||||
return exceptionResponse;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine("Unable to submit exception to exceptron. ", e.ToString());
|
||||
|
||||
if (Configuration.ThrowExceptions)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return new ExceptionResponse { Exception = e };
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateState(ExceptionData exceptionData)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Configuration.ApiKey))
|
||||
throw new InvalidOperationException("ApiKey has not been provided for this client.");
|
||||
|
||||
if (exceptionData == null)
|
||||
throw new ArgumentNullException("exceptionData");
|
||||
|
||||
if (exceptionData.Exception == null)
|
||||
throw new ArgumentException("ExceptionData.Exception Cannot be null.", "exceptionData");
|
||||
}
|
||||
|
||||
private void SetEnviromentInfo(ExceptionReport report)
|
||||
{
|
||||
report.cul = Thread.CurrentThread.CurrentCulture.Name;
|
||||
|
||||
try
|
||||
{
|
||||
report.os = Environment.OSVersion.VersionString;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (Configuration.ThrowExceptions) throw;
|
||||
}
|
||||
|
||||
if (Configuration.IncludeMachineName)
|
||||
{
|
||||
try
|
||||
{
|
||||
report.hn = Environment.MachineName;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (Configuration.ThrowExceptions) throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetHttpInfo(ExceptionData exceptionData, ExceptionReport report)
|
||||
{
|
||||
if (exceptionData.HttpContext == null && HttpContext.Current == null)
|
||||
return;
|
||||
|
||||
if (exceptionData.HttpContext == null)
|
||||
{
|
||||
exceptionData.HttpContext = HttpContext.Current;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
report.hm = exceptionData.HttpContext.Request.HttpMethod;
|
||||
|
||||
//TODO:find proper way to find http status code.
|
||||
/*
|
||||
var httpException = exceptionData.Exception as HttpException;
|
||||
if (httpException != null)
|
||||
{
|
||||
report.sc = httpException.GetHttpCode();
|
||||
}*/
|
||||
|
||||
report.url = exceptionData.HttpContext.Request.Url.ToString();
|
||||
report.ua = exceptionData.HttpContext.Request.UserAgent;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (Configuration.ThrowExceptions) throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<Frame> ConvertToFrames(Exception exception)
|
||||
{
|
||||
if (exception == null) return null;
|
||||
|
||||
var stackTrace = new StackTrace(exception, true);
|
||||
|
||||
var frames = stackTrace.GetFrames();
|
||||
|
||||
if (frames == null) return null;
|
||||
|
||||
var result = new List<Frame>();
|
||||
|
||||
for (int index = 0; index < frames.Length; index++)
|
||||
{
|
||||
var frame = frames[index];
|
||||
var method = frame.GetMethod();
|
||||
var declaringType = method.DeclaringType;
|
||||
|
||||
var fileName = frame.GetFileName();
|
||||
|
||||
var currentFrame = new Frame
|
||||
{
|
||||
i = index,
|
||||
fn = fileName,
|
||||
ln = frame.GetFileLineNumber(),
|
||||
m = method.ToString(),
|
||||
};
|
||||
|
||||
currentFrame.m = currentFrame.m.Substring(currentFrame.m.IndexOf(' ')).Trim();
|
||||
|
||||
|
||||
if (declaringType != null)
|
||||
{
|
||||
currentFrame.c = declaringType.FullName;
|
||||
}
|
||||
|
||||
result.Add(currentFrame);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetMaximumFrameworkVersion()
|
||||
{
|
||||
var clrVersion = Environment.Version;
|
||||
|
||||
if (clrVersion.Major == 2)
|
||||
{
|
||||
//Check if 2.0 or 3.5
|
||||
try
|
||||
{
|
||||
Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
|
||||
return "3.5";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return "2.0";
|
||||
}
|
||||
|
||||
if (clrVersion.Major == 4)
|
||||
{
|
||||
//Check if 4.0 or 4.5
|
||||
try
|
||||
{
|
||||
Assembly.Load("System.Threading.Tasks.Parallel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
||||
return "4.5";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return "4.0";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
45
src/Exceptron.Client/IExceptronClient.cs
Normal file
45
src/Exceptron.Client/IExceptronClient.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Web;
|
||||
using Exceptron.Client.Configuration;
|
||||
using Exceptron.Client.Message;
|
||||
|
||||
namespace Exceptron.Client
|
||||
{
|
||||
public interface IExceptronClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Configuration
|
||||
/// </summary>
|
||||
ExceptronConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exceptionData">Exception data to be reported to the server</param>
|
||||
ExceptionResponse SubmitException(ExceptionData exceptionData);
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exception">Exception that is being reported</param>
|
||||
/// <param name="component"
|
||||
/// example="DataAccess, Configuration, Registration, etc."
|
||||
/// remarks="It is common to use the logger name that was used to log the exception as the component.">Component that experienced this exception.</param>
|
||||
/// <param name="severity">Severity of the exception being reported</param>
|
||||
/// <param name="message"
|
||||
/// example="Something went wrong while checking for application updates.">Any message that should be attached to this exceptions</param>
|
||||
/// <param name="userId"
|
||||
/// remarks="This Id does not have to be tied to the user's identity.
|
||||
/// You can use a system generated unique ID such as GUID.
|
||||
/// This field is used to report how many unique users are experiencing an error."
|
||||
/// example="
|
||||
/// 62E5C8EF-0CA2-43AB-B278-FC6994F776ED
|
||||
/// Timmy@aol.com
|
||||
/// 26437
|
||||
/// ">ID that will uniquely identify the user</param>
|
||||
/// <param name="httpContext"><see cref="System.Web.HttpContext"/> in which the exception occurred. If no <see cref="System.Web.HttpContext"/> is provided
|
||||
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/></param>
|
||||
/// <returns></returns>
|
||||
ExceptionResponse SubmitException(Exception exception, string component, ExceptionSeverity severity = ExceptionSeverity.None, string message = null, string userId = null, HttpContext httpContext = null);
|
||||
}
|
||||
}
|
7
src/Exceptron.Client/IRestClient.cs
Normal file
7
src/Exceptron.Client/IRestClient.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Exceptron.Client
|
||||
{
|
||||
internal interface IRestClient
|
||||
{
|
||||
TResponse Put<TResponse>(string url, object report) where TResponse : class, new();
|
||||
}
|
||||
}
|
111
src/Exceptron.Client/Message/ExceptionReport.cs
Normal file
111
src/Exceptron.Client/Message/ExceptionReport.cs
Normal file
|
@ -0,0 +1,111 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Exceptron.Client.Message
|
||||
{
|
||||
internal class ExceptionReport
|
||||
{
|
||||
/// <summary>
|
||||
/// API key
|
||||
/// </summary>
|
||||
public string ap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Application Version
|
||||
/// </summary>
|
||||
public string aver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception Severity
|
||||
/// </summary>
|
||||
public int sv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User or Instance ID
|
||||
/// </summary>
|
||||
public string uid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of exception
|
||||
/// </summary>
|
||||
public string ext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception message
|
||||
/// </summary>
|
||||
public string exm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of frames that make up the StackTrace of the exception
|
||||
/// </summary>
|
||||
public List<Frame> stk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Component that experienced this exception
|
||||
/// </summary>
|
||||
public string cmp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message that was logged along with the exception.
|
||||
/// </summary>
|
||||
public string msg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User's culture in
|
||||
/// </summary>
|
||||
/// <remarks>http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.name.aspx</remarks>
|
||||
public string cul { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OS Version
|
||||
/// </summary>
|
||||
public string os { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the Client that generated and is sending this message
|
||||
/// </summary>
|
||||
public string dn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version of the Client that generated and is sending this message
|
||||
/// </summary>
|
||||
public string dv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host name of the machine that encountered this exception
|
||||
/// </summary>
|
||||
public string hn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Request url
|
||||
/// <remarks>Only used for exception in context of a web request/</remarks>
|
||||
public string url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Browser useragent
|
||||
/// </summary>
|
||||
/// <remarks>Only used for exception in context of a web request/</remarks>
|
||||
public string ua { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP response status code
|
||||
/// </summary>
|
||||
/// <remarks>Only used for exception in context of a web request/</remarks>
|
||||
public int sc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the HTTP data transfer method used by the client.
|
||||
/// </summary>
|
||||
/// <example>GET, POST, PUT, DELETE</example>
|
||||
public string hm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Framework Version (CLR) of the Host Application
|
||||
/// </summary>
|
||||
public string fv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Framework Type of the Host Application
|
||||
/// </summary>
|
||||
public string ft { get; set; }
|
||||
}
|
||||
}
|
34
src/Exceptron.Client/Message/ExceptionResponse.cs
Normal file
34
src/Exceptron.Client/Message/ExceptionResponse.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using Exceptron.Client.Configuration;
|
||||
|
||||
namespace Exceptron.Client.Message
|
||||
{
|
||||
public class ExceptionResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception report reference ID. This ID will be shared across
|
||||
/// similar exceptions
|
||||
/// </summary>
|
||||
public string RefId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Was the report successfully processed on the server
|
||||
/// </summary>
|
||||
public bool Successful
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(RefId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception that caused the message to fail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property will only be populated if <see cref="ExceptronConfiguration.ThrowExceptions"/> is set to <see cref="bool.False"/>/>
|
||||
/// Exception is thrown if <see cref="ExceptronConfiguration.ThrowExceptions"/> is set to <see cref="bool.True"/>.
|
||||
/// </remarks>
|
||||
public Exception Exception { get; internal set; }
|
||||
}
|
||||
}
|
30
src/Exceptron.Client/Message/Frame.cs
Normal file
30
src/Exceptron.Client/Message/Frame.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
namespace Exceptron.Client.Message
|
||||
{
|
||||
internal class Frame
|
||||
{
|
||||
/// <summary>
|
||||
/// Order of current frame
|
||||
/// </summary>
|
||||
public int i { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Line number of the current frame
|
||||
/// </summary>
|
||||
public int ln { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Method name for current frame
|
||||
/// </summary>
|
||||
public string m { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Class name for current frame
|
||||
/// </summary>
|
||||
public string c { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File name for current frame
|
||||
/// </summary>
|
||||
public string fn { get; set; }
|
||||
}
|
||||
}
|
7
src/Exceptron.Client/NuGet/web.config.transform
Normal file
7
src/Exceptron.Client/NuGet/web.config.transform
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<section name="exceptron" type="Exceptron.Client.Configuration.ExceptronConfiguration,Exceptron.Client" />
|
||||
</configSections>
|
||||
<exceptron apiKey="YOUR_EXCEPTRON_API_KEY" throwExceptions="true" includeMachineName="true"/>
|
||||
</configuration>
|
40
src/Exceptron.Client/Properties/AssemblyInfo.cs
Normal file
40
src/Exceptron.Client/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 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("Exceptron.Client")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyDescription(".NET client for exceptron API. In-cloud exception aggregation and analytics.")]
|
||||
[assembly: AssemblyCompany("Exceptron Solutions Inc.")]
|
||||
[assembly: AssemblyProduct("Exceptron.Client")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2012")]
|
||||
[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("a463887e-594f-4733-b227-a79f4ffb2158")]
|
||||
|
||||
// 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("10.0.0.*")]
|
||||
[assembly: AssemblyFileVersion("10.0.0.*")]
|
||||
[assembly: InternalsVisibleTo("Exceptron.Client.Tests")]
|
||||
[assembly: InternalsVisibleTo("Exceptron.Api.v1.Tests")]
|
||||
[assembly: InternalsVisibleTo("Exceptron.Rush")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
72
src/Exceptron.Client/RestClient.cs
Normal file
72
src/Exceptron.Client/RestClient.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Exceptron.Client.fastJSON;
|
||||
|
||||
namespace Exceptron.Client
|
||||
{
|
||||
public sealed class RestClient : IRestClient
|
||||
{
|
||||
public TResponse Put<TResponse>(string url, object content) where TResponse : class ,new()
|
||||
{
|
||||
|
||||
if (content == null)
|
||||
throw new ArgumentNullException("content can not be null", "content");
|
||||
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new ArgumentNullException("url can not be null or empty", "url");
|
||||
|
||||
Trace.WriteLine("Attempting PUT to " + url);
|
||||
|
||||
var json = JSON.Instance.ToJSON(content);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
var request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.Timeout = 10000;
|
||||
request.Method = "PUT";
|
||||
request.ContentType = "application/json";
|
||||
request.ContentLength = bytes.Length;
|
||||
request.Accept = "application/json";
|
||||
|
||||
var dataStream = request.GetRequestStream();
|
||||
dataStream.Write(bytes, 0, bytes.Length);
|
||||
dataStream.Close();
|
||||
|
||||
string responseContent = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var webResponse = request.GetResponse();
|
||||
responseContent = ReadResponse(webResponse);
|
||||
var response = JSON.Instance.ToObject<TResponse>(responseContent);
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
Trace.WriteLine(e.ToString());
|
||||
responseContent = ReadResponse(e.Response);
|
||||
throw new ExceptronApiException(e, responseContent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Trace.WriteLine(responseContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string ReadResponse(WebResponse webResponse)
|
||||
{
|
||||
if (webResponse == null) return string.Empty;
|
||||
|
||||
var responseStream = webResponse.GetResponseStream();
|
||||
|
||||
if (responseStream == null) return string.Empty;
|
||||
|
||||
var decodedStream = new StreamReader(responseStream, Encoding.GetEncoding(1252));
|
||||
return decodedStream.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
21
src/Exceptron.Client/fastJSON/Getters.cs
Normal file
21
src/Exceptron.Client/fastJSON/Getters.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Exceptron.Client.fastJSON
|
||||
{
|
||||
internal class Getters
|
||||
{
|
||||
public string Name;
|
||||
public JSON.GenericGetter Getter;
|
||||
public Type propertyType;
|
||||
}
|
||||
|
||||
internal class DatasetSchema
|
||||
{
|
||||
public List<string> Info { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
820
src/Exceptron.Client/fastJSON/JSON.cs
Normal file
820
src/Exceptron.Client/fastJSON/JSON.cs
Normal file
|
@ -0,0 +1,820 @@
|
|||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Exceptron.Client.fastJSON
|
||||
{
|
||||
|
||||
internal class JSON
|
||||
{
|
||||
public readonly static JSON Instance = new JSON();
|
||||
|
||||
private JSON()
|
||||
{
|
||||
UseSerializerExtension = false;
|
||||
SerializeNullValues = false;
|
||||
UseOptimizedDatasetSchema = false;
|
||||
UsingGlobalTypes = false;
|
||||
}
|
||||
public bool UseOptimizedDatasetSchema = true;
|
||||
public bool UseFastGuid = true;
|
||||
public bool UseSerializerExtension = true;
|
||||
public bool IndentOutput = false;
|
||||
public bool SerializeNullValues = true;
|
||||
public bool UseUTCDateTime = false;
|
||||
public bool ShowReadOnlyProperties = false;
|
||||
public bool UsingGlobalTypes = true;
|
||||
|
||||
public string ToJSON(object obj)
|
||||
{
|
||||
return ToJSON(obj, UseSerializerExtension, UseFastGuid, UseOptimizedDatasetSchema, SerializeNullValues);
|
||||
}
|
||||
|
||||
|
||||
public string ToJSON(object obj,
|
||||
bool enableSerializerExtensions,
|
||||
bool enableFastGuid,
|
||||
bool enableOptimizedDatasetSchema,
|
||||
bool serializeNullValues)
|
||||
{
|
||||
return new JSONSerializer(enableOptimizedDatasetSchema, enableFastGuid, enableSerializerExtensions, serializeNullValues, IndentOutput).ConvertToJSON(obj);
|
||||
}
|
||||
|
||||
|
||||
public T ToObject<T>(string json)
|
||||
{
|
||||
return (T)ToObject(json, typeof(T));
|
||||
}
|
||||
|
||||
|
||||
public object ToObject(string json, Type type)
|
||||
{
|
||||
var ht = new JsonParser(json).Decode() as Dictionary<string, object>;
|
||||
if (ht == null) return null;
|
||||
return ParseDictionary(ht, null, type);
|
||||
}
|
||||
|
||||
#if CUSTOMTYPE
|
||||
internal SafeDictionary<Type, Serialize> _customSerializer = new SafeDictionary<Type, Serialize>();
|
||||
internal SafeDictionary<Type, Deserialize> _customDeserializer = new SafeDictionary<Type, Deserialize>();
|
||||
|
||||
public void RegisterCustomType(Type type, Serialize serializer, Deserialize deserializer)
|
||||
{
|
||||
if (type != null && serializer != null && deserializer != null)
|
||||
{
|
||||
_customSerializer.Add(type, serializer);
|
||||
_customDeserializer.Add(type, deserializer);
|
||||
// reset property cache
|
||||
_propertycache = new SafeDictionary<string, SafeDictionary<string, myPropInfo>>();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsTypeRegistered(Type t)
|
||||
{
|
||||
Serialize s;
|
||||
return _customSerializer.TryGetValue(t, out s);
|
||||
}
|
||||
#endif
|
||||
|
||||
#region [ PROPERTY GET SET CACHE ]
|
||||
|
||||
readonly SafeDictionary<Type, string> _tyname = new SafeDictionary<Type, string>();
|
||||
internal string GetTypeAssemblyName(Type t)
|
||||
{
|
||||
string val = "";
|
||||
if (_tyname.TryGetValue(t, out val))
|
||||
return val;
|
||||
string s = t.AssemblyQualifiedName;
|
||||
_tyname.Add(t, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
readonly SafeDictionary<string, Type> _typecache = new SafeDictionary<string, Type>();
|
||||
private Type GetTypeFromCache(string typename)
|
||||
{
|
||||
Type val = null;
|
||||
if (_typecache.TryGetValue(typename, out val))
|
||||
return val;
|
||||
Type t = Type.GetType(typename);
|
||||
_typecache.Add(typename, t);
|
||||
return t;
|
||||
}
|
||||
|
||||
readonly SafeDictionary<Type, CreateObject> _constrcache = new SafeDictionary<Type, CreateObject>();
|
||||
private delegate object CreateObject();
|
||||
private object FastCreateInstance(Type objtype)
|
||||
{
|
||||
try
|
||||
{
|
||||
CreateObject c = null;
|
||||
if (_constrcache.TryGetValue(objtype, out c))
|
||||
{
|
||||
return c();
|
||||
}
|
||||
DynamicMethod dynMethod = new DynamicMethod("_", objtype, null, true);
|
||||
ILGenerator ilGen = dynMethod.GetILGenerator();
|
||||
|
||||
ilGen.Emit(OpCodes.Newobj, objtype.GetConstructor(Type.EmptyTypes));
|
||||
ilGen.Emit(OpCodes.Ret);
|
||||
c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject));
|
||||
_constrcache.Add(objtype, c);
|
||||
return c();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new Exception(string.Format("Failed to fast create instance for type '{0}' from assemebly '{1}'",
|
||||
objtype.FullName, objtype.AssemblyQualifiedName), exc);
|
||||
}
|
||||
}
|
||||
|
||||
private struct myPropInfo
|
||||
{
|
||||
public bool filled;
|
||||
public Type pt;
|
||||
public Type bt;
|
||||
public Type changeType;
|
||||
public bool isDictionary;
|
||||
public bool isValueType;
|
||||
public bool isGenericType;
|
||||
public bool isArray;
|
||||
public bool isByteArray;
|
||||
public bool isGuid;
|
||||
#if !SILVERLIGHT
|
||||
public bool isDataSet;
|
||||
public bool isDataTable;
|
||||
public bool isHashtable;
|
||||
#endif
|
||||
public GenericSetter setter;
|
||||
public bool isEnum;
|
||||
public bool isDateTime;
|
||||
public Type[] GenericTypes;
|
||||
public bool isInt;
|
||||
public bool isLong;
|
||||
public bool isString;
|
||||
public bool isBool;
|
||||
public bool isClass;
|
||||
public GenericGetter getter;
|
||||
public bool isStringDictionary;
|
||||
public string Name;
|
||||
#if CUSTOMTYPE
|
||||
public bool isCustomType;
|
||||
#endif
|
||||
public bool CanWrite;
|
||||
}
|
||||
|
||||
readonly SafeDictionary<string, SafeDictionary<string, myPropInfo>> _propertycache = new SafeDictionary<string, SafeDictionary<string, myPropInfo>>();
|
||||
private SafeDictionary<string, myPropInfo> Getproperties(Type type, string typename)
|
||||
{
|
||||
SafeDictionary<string, myPropInfo> sd = null;
|
||||
if (_propertycache.TryGetValue(typename, out sd))
|
||||
{
|
||||
return sd;
|
||||
}
|
||||
sd = new SafeDictionary<string, myPropInfo>();
|
||||
var pr = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var p in pr)
|
||||
{
|
||||
myPropInfo d = CreateMyProp(p.PropertyType, p.Name);
|
||||
d.CanWrite = p.CanWrite;
|
||||
d.setter = CreateSetMethod(p);
|
||||
d.getter = CreateGetMethod(p);
|
||||
sd.Add(p.Name, d);
|
||||
}
|
||||
|
||||
_propertycache.Add(typename, sd);
|
||||
return sd;
|
||||
}
|
||||
|
||||
private myPropInfo CreateMyProp(Type t, string name)
|
||||
{
|
||||
myPropInfo d = new myPropInfo();
|
||||
d.filled = true;
|
||||
d.CanWrite = true;
|
||||
d.pt = t;
|
||||
d.Name = name;
|
||||
d.isDictionary = t.Name.Contains("Dictionary");
|
||||
if (d.isDictionary)
|
||||
d.GenericTypes = t.GetGenericArguments();
|
||||
d.isValueType = t.IsValueType;
|
||||
d.isGenericType = t.IsGenericType;
|
||||
d.isArray = t.IsArray;
|
||||
if (d.isArray)
|
||||
d.bt = t.GetElementType();
|
||||
if (d.isGenericType)
|
||||
d.bt = t.GetGenericArguments()[0];
|
||||
d.isByteArray = t == typeof(byte[]);
|
||||
d.isGuid = (t == typeof(Guid) || t == typeof(Guid?));
|
||||
#if !SILVERLIGHT
|
||||
d.isHashtable = t == typeof(Hashtable);
|
||||
d.isDataSet = t == typeof(DataSet);
|
||||
d.isDataTable = t == typeof(DataTable);
|
||||
#endif
|
||||
|
||||
d.changeType = GetChangeType(t);
|
||||
d.isEnum = t.IsEnum;
|
||||
d.isDateTime = t == typeof(DateTime) || t == typeof(DateTime?);
|
||||
d.isInt = t == typeof(int) || t == typeof(int?);
|
||||
d.isLong = t == typeof(long) || t == typeof(long?);
|
||||
d.isString = t == typeof(string);
|
||||
d.isBool = t == typeof(bool) || t == typeof(bool?);
|
||||
d.isClass = t.IsClass;
|
||||
|
||||
if (d.isDictionary && d.GenericTypes.Length > 0 && d.GenericTypes[0] == typeof(string))
|
||||
d.isStringDictionary = true;
|
||||
|
||||
#if CUSTOMTYPE
|
||||
if (IsTypeRegistered(t))
|
||||
d.isCustomType = true;
|
||||
#endif
|
||||
return d;
|
||||
}
|
||||
|
||||
private delegate void GenericSetter(object target, object value);
|
||||
|
||||
private static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
|
||||
{
|
||||
MethodInfo setMethod = propertyInfo.GetSetMethod(nonPublic: true);
|
||||
if (setMethod == null)
|
||||
return null;
|
||||
|
||||
var arguments = new Type[2];
|
||||
arguments[0] = arguments[1] = typeof(object);
|
||||
|
||||
DynamicMethod setter = new DynamicMethod("_", typeof(void), arguments, true);
|
||||
ILGenerator il = setter.GetILGenerator();
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
if (propertyInfo.PropertyType.IsClass)
|
||||
il.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
|
||||
else
|
||||
il.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
|
||||
|
||||
il.EmitCall(OpCodes.Callvirt, setMethod, null);
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
|
||||
}
|
||||
|
||||
internal delegate object GenericGetter(object obj);
|
||||
|
||||
|
||||
private GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
|
||||
{
|
||||
MethodInfo getMethod = propertyInfo.GetGetMethod();
|
||||
if (getMethod == null)
|
||||
return null;
|
||||
|
||||
var arguments = new Type[1];
|
||||
arguments[0] = typeof(object);
|
||||
|
||||
DynamicMethod getter = new DynamicMethod("_", typeof(object), arguments, true);
|
||||
ILGenerator il = getter.GetILGenerator();
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
|
||||
il.EmitCall(OpCodes.Callvirt, getMethod, null);
|
||||
|
||||
if (!propertyInfo.PropertyType.IsClass)
|
||||
il.Emit(OpCodes.Box, propertyInfo.PropertyType);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
|
||||
}
|
||||
|
||||
readonly SafeDictionary<Type, List<Getters>> _getterscache = new SafeDictionary<Type, List<Getters>>();
|
||||
internal List<Getters> GetGetters(Type type)
|
||||
{
|
||||
List<Getters> val = null;
|
||||
if (_getterscache.TryGetValue(type, out val))
|
||||
return val;
|
||||
|
||||
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
var getters = new List<Getters>();
|
||||
foreach (var p in props)
|
||||
{
|
||||
if (!p.CanWrite && ShowReadOnlyProperties == false) continue;
|
||||
|
||||
var att = p.GetCustomAttributes(typeof(XmlIgnoreAttribute), false);
|
||||
if (att != null && att.Length > 0)
|
||||
continue;
|
||||
|
||||
GenericGetter g = CreateGetMethod(p);
|
||||
if (g != null)
|
||||
{
|
||||
Getters gg = new Getters();
|
||||
gg.Name = p.Name;
|
||||
gg.Getter = g;
|
||||
gg.propertyType = p.PropertyType;
|
||||
getters.Add(gg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_getterscache.Add(type, getters);
|
||||
return getters;
|
||||
}
|
||||
|
||||
private object ChangeType(object value, Type conversionType)
|
||||
{
|
||||
if (conversionType == typeof(int))
|
||||
return (int)CreateLong((string)value);
|
||||
|
||||
if (conversionType == typeof(long))
|
||||
return CreateLong((string)value);
|
||||
|
||||
if (conversionType == typeof(string))
|
||||
return value;
|
||||
|
||||
if (conversionType == typeof(Guid))
|
||||
return CreateGuid((string)value);
|
||||
|
||||
if (conversionType.IsEnum)
|
||||
return CreateEnum(conversionType, (string)value);
|
||||
|
||||
return Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
private object ParseDictionary(Dictionary<string, object> d, Dictionary<string, object> globaltypes, Type type)
|
||||
{
|
||||
object tn = "";
|
||||
if (d.TryGetValue("$types", out tn))
|
||||
{
|
||||
UsingGlobalTypes = true;
|
||||
globaltypes = new Dictionary<string, object>();
|
||||
foreach (var kv in (Dictionary<string, object>)tn)
|
||||
{
|
||||
globaltypes.Add((string)kv.Value, kv.Key);
|
||||
}
|
||||
}
|
||||
|
||||
bool found = d.TryGetValue("$type", out tn);
|
||||
#if !SILVERLIGHT
|
||||
if (found == false && type == typeof(Object))
|
||||
{
|
||||
return CreateDataset(d, globaltypes);
|
||||
}
|
||||
#endif
|
||||
if (found)
|
||||
{
|
||||
if (UsingGlobalTypes)
|
||||
{
|
||||
object tname = "";
|
||||
if (globaltypes.TryGetValue((string)tn, out tname))
|
||||
tn = tname;
|
||||
}
|
||||
type = GetTypeFromCache((string)tn);
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
throw new Exception("Cannot determine type");
|
||||
|
||||
string typename = type.FullName;
|
||||
object o = FastCreateInstance(type);
|
||||
var props = Getproperties(type, typename);
|
||||
foreach (var name in d.Keys)
|
||||
{
|
||||
if (name == "$map")
|
||||
{
|
||||
ProcessMap(o, props, (Dictionary<string, object>)d[name]);
|
||||
continue;
|
||||
}
|
||||
myPropInfo pi;
|
||||
if (props.TryGetValue(name, out pi) == false)
|
||||
continue;
|
||||
if (pi.filled)
|
||||
{
|
||||
object v = d[name];
|
||||
|
||||
if (v != null)
|
||||
{
|
||||
object oset = null;
|
||||
|
||||
if (pi.isInt)
|
||||
oset = (int)CreateLong((string)v);
|
||||
#if CUSTOMTYPE
|
||||
else if (pi.isCustomType)
|
||||
oset = CreateCustom((string)v, pi.pt);
|
||||
#endif
|
||||
else if (pi.isLong)
|
||||
oset = CreateLong((string)v);
|
||||
|
||||
else if (pi.isString)
|
||||
oset = v;
|
||||
|
||||
else if (pi.isBool)
|
||||
oset = (bool)v;
|
||||
|
||||
else if (pi.isGenericType && pi.isValueType == false && pi.isDictionary == false)
|
||||
#if SILVERLIGHT
|
||||
oset = CreateGenericList((List<object>)v, pi.pt, pi.bt, globaltypes);
|
||||
#else
|
||||
oset = CreateGenericList((ArrayList)v, pi.pt, pi.bt, globaltypes);
|
||||
#endif
|
||||
else if (pi.isByteArray)
|
||||
oset = Convert.FromBase64String((string)v);
|
||||
|
||||
else if (pi.isArray && pi.isValueType == false)
|
||||
#if SILVERLIGHT
|
||||
oset = CreateArray((List<object>)v, pi.pt, pi.bt, globaltypes);
|
||||
#else
|
||||
oset = CreateArray((ArrayList)v, pi.pt, pi.bt, globaltypes);
|
||||
#endif
|
||||
else if (pi.isGuid)
|
||||
oset = CreateGuid((string)v);
|
||||
#if !SILVERLIGHT
|
||||
else if (pi.isDataSet)
|
||||
oset = CreateDataset((Dictionary<string, object>)v, globaltypes);
|
||||
|
||||
else if (pi.isDataTable)
|
||||
oset = CreateDataTable((Dictionary<string, object>)v, globaltypes);
|
||||
#endif
|
||||
|
||||
else if (pi.isStringDictionary)
|
||||
oset = CreateStringKeyDictionary((Dictionary<string, object>)v, pi.pt, pi.GenericTypes, globaltypes);
|
||||
|
||||
#if !SILVERLIGHT
|
||||
else if (pi.isDictionary || pi.isHashtable)
|
||||
oset = CreateDictionary((ArrayList)v, pi.pt, pi.GenericTypes, globaltypes);
|
||||
#else
|
||||
else if (pi.isDictionary)
|
||||
oset = CreateDictionary((List<object>)v, pi.pt, pi.GenericTypes, globaltypes);
|
||||
#endif
|
||||
|
||||
else if (pi.isEnum)
|
||||
oset = CreateEnum(pi.pt, (string)v);
|
||||
|
||||
else if (pi.isDateTime)
|
||||
oset = CreateDateTime((string)v);
|
||||
|
||||
else if (pi.isClass && v is Dictionary<string, object>)
|
||||
oset = ParseDictionary((Dictionary<string, object>)v, globaltypes, pi.pt);
|
||||
|
||||
else if (pi.isValueType)
|
||||
oset = ChangeType(v, pi.changeType);
|
||||
|
||||
#if SILVERLIGHT
|
||||
else if (v is List<object>)
|
||||
oset = CreateArray((List<object>)v, pi.pt, typeof(object), globaltypes);
|
||||
#else
|
||||
else if (v is ArrayList)
|
||||
oset = CreateArray((ArrayList)v, pi.pt, typeof(object), globaltypes);
|
||||
#endif
|
||||
else
|
||||
oset = v;
|
||||
|
||||
if (pi.CanWrite)
|
||||
pi.setter(o, oset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
#if CUSTOMTYPE
|
||||
private object CreateCustom(string v, Type type)
|
||||
{
|
||||
Deserialize d;
|
||||
_customDeserializer.TryGetValue(type, out d);
|
||||
return d(v);
|
||||
}
|
||||
#endif
|
||||
|
||||
private void ProcessMap(object obj, SafeDictionary<string, myPropInfo> props, Dictionary<string, object> dic)
|
||||
{
|
||||
foreach (var kv in dic)
|
||||
{
|
||||
myPropInfo p = props[kv.Key];
|
||||
object o = p.getter(obj);
|
||||
Type t = Type.GetType((string)kv.Value);
|
||||
if (t == typeof(Guid))
|
||||
p.setter(obj, CreateGuid((string)o));
|
||||
}
|
||||
}
|
||||
|
||||
private long CreateLong(string s)
|
||||
{
|
||||
long num = 0;
|
||||
bool neg = false;
|
||||
foreach (var cc in s)
|
||||
{
|
||||
if (cc == '-')
|
||||
neg = true;
|
||||
else if (cc == '+')
|
||||
neg = false;
|
||||
else
|
||||
{
|
||||
num *= 10;
|
||||
num += (cc - '0');
|
||||
}
|
||||
}
|
||||
|
||||
return neg ? -num : num;
|
||||
}
|
||||
|
||||
private object CreateEnum(Type pt, string v)
|
||||
{
|
||||
// TODO : optimize create enum
|
||||
#if !SILVERLIGHT
|
||||
return Enum.Parse(pt, v);
|
||||
#else
|
||||
return Enum.Parse(pt, v, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
private Guid CreateGuid(string s)
|
||||
{
|
||||
if (s.Length > 30)
|
||||
return new Guid(s);
|
||||
return new Guid(Convert.FromBase64String(s));
|
||||
}
|
||||
|
||||
private DateTime CreateDateTime(string value)
|
||||
{
|
||||
bool utc = false;
|
||||
// 0123456789012345678
|
||||
// datetime format = yyyy-MM-dd HH:mm:ss
|
||||
int year = (int)CreateLong(value.Substring(0, 4));
|
||||
int month = (int)CreateLong(value.Substring(5, 2));
|
||||
int day = (int)CreateLong(value.Substring(8, 2));
|
||||
int hour = (int)CreateLong(value.Substring(11, 2));
|
||||
int min = (int)CreateLong(value.Substring(14, 2));
|
||||
int sec = (int)CreateLong(value.Substring(17, 2));
|
||||
|
||||
if (value.EndsWith("Z"))
|
||||
utc = true;
|
||||
|
||||
if (UseUTCDateTime == false && utc == false)
|
||||
return new DateTime(year, month, day, hour, min, sec);
|
||||
return new DateTime(year, month, day, hour, min, sec, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
|
||||
#if SILVERLIGHT
|
||||
private object CreateArray(List<object> data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
Array col = Array.CreateInstance(bt, data.Count);
|
||||
// create an array of objects
|
||||
for (int i = 0; i < data.Count; i++)// each (object ob in data)
|
||||
{
|
||||
object ob = data[i];
|
||||
if (ob is IDictionary)
|
||||
col.SetValue(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt), i);
|
||||
else
|
||||
col.SetValue(ChangeType(ob, bt), i);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
#else
|
||||
private object CreateArray(ArrayList data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
ArrayList col = new ArrayList();
|
||||
// create an array of objects
|
||||
foreach (var ob in data)
|
||||
{
|
||||
if (ob is IDictionary)
|
||||
col.Add(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt));
|
||||
else
|
||||
col.Add(ChangeType(ob, bt));
|
||||
}
|
||||
return col.ToArray(bt);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if SILVERLIGHT
|
||||
private object CreateGenericList(List<object> data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
#else
|
||||
private object CreateGenericList(ArrayList data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
#endif
|
||||
{
|
||||
IList col = (IList)FastCreateInstance(pt);
|
||||
// create an array of objects
|
||||
foreach (var ob in data)
|
||||
{
|
||||
if (ob is IDictionary)
|
||||
col.Add(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt));
|
||||
#if SILVERLIGHT
|
||||
else if (ob is List<object>)
|
||||
col.Add(((List<object>)ob).ToArray());
|
||||
#else
|
||||
else if (ob is ArrayList)
|
||||
col.Add(((ArrayList)ob).ToArray());
|
||||
#endif
|
||||
else
|
||||
col.Add(ChangeType(ob, bt));
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
private object CreateStringKeyDictionary(Dictionary<string, object> reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
var col = (IDictionary)FastCreateInstance(pt);
|
||||
Type t1 = null;
|
||||
Type t2 = null;
|
||||
if (types != null)
|
||||
{
|
||||
t1 = types[0];
|
||||
t2 = types[1];
|
||||
}
|
||||
|
||||
foreach (var values in reader)
|
||||
{
|
||||
var key = values.Key;//ChangeType(values.Key, t1);
|
||||
object val = null;
|
||||
if (values.Value is Dictionary<string, object>)
|
||||
val = ParseDictionary((Dictionary<string, object>)values.Value, globalTypes, t2);
|
||||
else
|
||||
val = ChangeType(values.Value, t2);
|
||||
col.Add(key, val);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
#if SILVERLIGHT
|
||||
private object CreateDictionary(List<object> reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
|
||||
#else
|
||||
private object CreateDictionary(ArrayList reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
|
||||
#endif
|
||||
{
|
||||
IDictionary col = (IDictionary)FastCreateInstance(pt);
|
||||
Type t1 = null;
|
||||
Type t2 = null;
|
||||
if (types != null)
|
||||
{
|
||||
t1 = types[0];
|
||||
t2 = types[1];
|
||||
}
|
||||
|
||||
foreach (Dictionary<string, object> values in reader)
|
||||
{
|
||||
object key = values["k"];
|
||||
object val = values["v"];
|
||||
|
||||
if (key is Dictionary<string, object>)
|
||||
key = ParseDictionary((Dictionary<string, object>)key, globalTypes, t1);
|
||||
else
|
||||
key = ChangeType(key, t1);
|
||||
|
||||
if (val is Dictionary<string, object>)
|
||||
val = ParseDictionary((Dictionary<string, object>)val, globalTypes, t2);
|
||||
else
|
||||
val = ChangeType(val, t2);
|
||||
|
||||
col.Add(key, val);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
private Type GetChangeType(Type conversionType)
|
||||
{
|
||||
if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
|
||||
return conversionType.GetGenericArguments()[0];
|
||||
|
||||
return conversionType;
|
||||
}
|
||||
#if !SILVERLIGHT
|
||||
private DataSet CreateDataset(Dictionary<string, object> reader, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
DataSet ds = new DataSet();
|
||||
ds.EnforceConstraints = false;
|
||||
ds.BeginInit();
|
||||
|
||||
// read dataset schema here
|
||||
ReadSchema(reader, ds, globalTypes);
|
||||
|
||||
foreach (var pair in reader)
|
||||
{
|
||||
if (pair.Key == "$type" || pair.Key == "$schema") continue;
|
||||
|
||||
ArrayList rows = (ArrayList)pair.Value;
|
||||
if (rows == null) continue;
|
||||
|
||||
DataTable dt = ds.Tables[pair.Key];
|
||||
ReadDataTable(rows, dt);
|
||||
}
|
||||
|
||||
ds.EndInit();
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
private void ReadSchema(Dictionary<string, object> reader, DataSet ds, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
var schema = reader["$schema"];
|
||||
|
||||
if (schema is string)
|
||||
{
|
||||
TextReader tr = new StringReader((string)schema);
|
||||
ds.ReadXmlSchema(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
DatasetSchema ms = (DatasetSchema)ParseDictionary((Dictionary<string, object>)schema, globalTypes, typeof(DatasetSchema));
|
||||
ds.DataSetName = ms.Name;
|
||||
for (int i = 0; i < ms.Info.Count; i += 3)
|
||||
{
|
||||
if (ds.Tables.Contains(ms.Info[i]) == false)
|
||||
ds.Tables.Add(ms.Info[i]);
|
||||
ds.Tables[ms.Info[i]].Columns.Add(ms.Info[i + 1], Type.GetType(ms.Info[i + 2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadDataTable(ArrayList rows, DataTable dt)
|
||||
{
|
||||
dt.BeginInit();
|
||||
dt.BeginLoadData();
|
||||
var guidcols = new List<int>();
|
||||
var datecol = new List<int>();
|
||||
|
||||
foreach (DataColumn c in dt.Columns)
|
||||
{
|
||||
if (c.DataType == typeof(Guid) || c.DataType == typeof(Guid?))
|
||||
guidcols.Add(c.Ordinal);
|
||||
if (UseUTCDateTime && (c.DataType == typeof(DateTime) || c.DataType == typeof(DateTime?)))
|
||||
datecol.Add(c.Ordinal);
|
||||
}
|
||||
|
||||
foreach (ArrayList row in rows)
|
||||
{
|
||||
var v = new object[row.Count];
|
||||
row.CopyTo(v, 0);
|
||||
foreach (var i in guidcols)
|
||||
{
|
||||
string s = (string)v[i];
|
||||
if (s != null && s.Length < 36)
|
||||
v[i] = new Guid(Convert.FromBase64String(s));
|
||||
}
|
||||
if (UseUTCDateTime)
|
||||
{
|
||||
foreach (var i in datecol)
|
||||
{
|
||||
string s = (string)v[i];
|
||||
if (s != null)
|
||||
v[i] = CreateDateTime(s);
|
||||
}
|
||||
}
|
||||
dt.Rows.Add(v);
|
||||
}
|
||||
|
||||
dt.EndLoadData();
|
||||
dt.EndInit();
|
||||
}
|
||||
|
||||
DataTable CreateDataTable(Dictionary<string, object> reader, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
var dt = new DataTable();
|
||||
|
||||
// read dataset schema here
|
||||
var schema = reader["$schema"];
|
||||
|
||||
if (schema is string)
|
||||
{
|
||||
TextReader tr = new StringReader((string)schema);
|
||||
dt.ReadXmlSchema(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ms = (DatasetSchema)ParseDictionary((Dictionary<string, object>)schema, globalTypes, typeof(DatasetSchema));
|
||||
dt.TableName = ms.Info[0];
|
||||
for (int i = 0; i < ms.Info.Count; i += 3)
|
||||
{
|
||||
dt.Columns.Add(ms.Info[i + 1], Type.GetType(ms.Info[i + 2]));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in reader)
|
||||
{
|
||||
if (pair.Key == "$type" || pair.Key == "$schema")
|
||||
continue;
|
||||
|
||||
var rows = (ArrayList)pair.Value;
|
||||
if (rows == null)
|
||||
continue;
|
||||
|
||||
if (!dt.TableName.Equals(pair.Key, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
ReadDataTable(rows, dt);
|
||||
}
|
||||
|
||||
return dt;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
409
src/Exceptron.Client/fastJSON/JsonParser.cs
Normal file
409
src/Exceptron.Client/fastJSON/JsonParser.cs
Normal file
|
@ -0,0 +1,409 @@
|
|||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Exceptron.Client.fastJSON
|
||||
{
|
||||
/// <summary>
|
||||
/// This class encodes and decodes JSON strings.
|
||||
/// Spec. details, see http://www.json.org/
|
||||
///
|
||||
/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
|
||||
/// All numbers are parsed to doubles.
|
||||
/// </summary>
|
||||
internal class JsonParser
|
||||
{
|
||||
enum Token
|
||||
{
|
||||
None = -1, // Used to denote no Lookahead available
|
||||
Curly_Open,
|
||||
Curly_Close,
|
||||
Squared_Open,
|
||||
Squared_Close,
|
||||
Colon,
|
||||
Comma,
|
||||
String,
|
||||
Number,
|
||||
True,
|
||||
False,
|
||||
Null
|
||||
}
|
||||
|
||||
readonly char[] json;
|
||||
readonly StringBuilder s = new StringBuilder();
|
||||
Token lookAheadToken = Token.None;
|
||||
int index;
|
||||
|
||||
internal JsonParser(string json)
|
||||
{
|
||||
this.json = json.ToCharArray();
|
||||
}
|
||||
|
||||
public object Decode()
|
||||
{
|
||||
return ParseValue();
|
||||
}
|
||||
|
||||
private Dictionary<string, object> ParseObject()
|
||||
{
|
||||
var table = new Dictionary<string, object>();
|
||||
|
||||
ConsumeToken(); // {
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (LookAhead())
|
||||
{
|
||||
|
||||
case Token.Comma:
|
||||
ConsumeToken();
|
||||
break;
|
||||
|
||||
case Token.Curly_Close:
|
||||
ConsumeToken();
|
||||
return table;
|
||||
|
||||
default:
|
||||
{
|
||||
|
||||
// name
|
||||
string name = ParseString();
|
||||
|
||||
// :
|
||||
if (NextToken() != Token.Colon)
|
||||
{
|
||||
throw new Exception("Expected colon at index " + index);
|
||||
}
|
||||
|
||||
// value
|
||||
object value = ParseValue();
|
||||
|
||||
table[name] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if SILVERLIGHT
|
||||
private List<object> ParseArray()
|
||||
{
|
||||
List<object> array = new List<object>();
|
||||
#else
|
||||
private ArrayList ParseArray()
|
||||
{
|
||||
ArrayList array = new ArrayList();
|
||||
#endif
|
||||
ConsumeToken(); // [
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (LookAhead())
|
||||
{
|
||||
|
||||
case Token.Comma:
|
||||
ConsumeToken();
|
||||
break;
|
||||
|
||||
case Token.Squared_Close:
|
||||
ConsumeToken();
|
||||
return array;
|
||||
|
||||
default:
|
||||
{
|
||||
array.Add(ParseValue());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object ParseValue()
|
||||
{
|
||||
switch (LookAhead())
|
||||
{
|
||||
case Token.Number:
|
||||
return ParseNumber();
|
||||
|
||||
case Token.String:
|
||||
return ParseString();
|
||||
|
||||
case Token.Curly_Open:
|
||||
return ParseObject();
|
||||
|
||||
case Token.Squared_Open:
|
||||
return ParseArray();
|
||||
|
||||
case Token.True:
|
||||
ConsumeToken();
|
||||
return true;
|
||||
|
||||
case Token.False:
|
||||
ConsumeToken();
|
||||
return false;
|
||||
|
||||
case Token.Null:
|
||||
ConsumeToken();
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Exception("Unrecognized token at index" + index);
|
||||
}
|
||||
|
||||
private string ParseString()
|
||||
{
|
||||
ConsumeToken(); // "
|
||||
|
||||
s.Length = 0;
|
||||
|
||||
int runIndex = -1;
|
||||
|
||||
while (index < json.Length)
|
||||
{
|
||||
var c = json[index++];
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
if (runIndex != -1)
|
||||
{
|
||||
if (s.Length == 0)
|
||||
return new string(json, runIndex, index - runIndex - 1);
|
||||
|
||||
s.Append(json, runIndex, index - runIndex - 1);
|
||||
}
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
if (c != '\\')
|
||||
{
|
||||
if (runIndex == -1)
|
||||
runIndex = index - 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index == json.Length) break;
|
||||
|
||||
if (runIndex != -1)
|
||||
{
|
||||
s.Append(json, runIndex, index - runIndex - 1);
|
||||
runIndex = -1;
|
||||
}
|
||||
|
||||
switch (json[index++])
|
||||
{
|
||||
case '"':
|
||||
s.Append('"');
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
s.Append('\\');
|
||||
break;
|
||||
|
||||
case '/':
|
||||
s.Append('/');
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
s.Append('\b');
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
s.Append('\f');
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
s.Append('\n');
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
s.Append('\r');
|
||||
break;
|
||||
|
||||
case 't':
|
||||
s.Append('\t');
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
{
|
||||
int remainingLength = json.Length - index;
|
||||
if (remainingLength < 4) break;
|
||||
|
||||
// parse the 32 bit hex into an integer codepoint
|
||||
uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]);
|
||||
s.Append((char)codePoint);
|
||||
|
||||
// skip 4 chars
|
||||
index += 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Unexpectedly reached end of string");
|
||||
}
|
||||
|
||||
private uint ParseSingleChar(char c1, uint multipliyer)
|
||||
{
|
||||
uint p1 = 0;
|
||||
if (c1 >= '0' && c1 <= '9')
|
||||
p1 = (uint)(c1 - '0') * multipliyer;
|
||||
else if (c1 >= 'A' && c1 <= 'F')
|
||||
p1 = (uint)((c1 - 'A') + 10) * multipliyer;
|
||||
else if (c1 >= 'a' && c1 <= 'f')
|
||||
p1 = (uint)((c1 - 'a') + 10) * multipliyer;
|
||||
return p1;
|
||||
}
|
||||
|
||||
private uint ParseUnicode(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
uint p1 = ParseSingleChar(c1, 0x1000);
|
||||
uint p2 = ParseSingleChar(c2, 0x100);
|
||||
uint p3 = ParseSingleChar(c3, 0x10);
|
||||
uint p4 = ParseSingleChar(c4, 1);
|
||||
|
||||
return p1 + p2 + p3 + p4;
|
||||
}
|
||||
|
||||
private string ParseNumber()
|
||||
{
|
||||
ConsumeToken();
|
||||
|
||||
// Need to start back one place because the first digit is also a token and would have been consumed
|
||||
var startIndex = index - 1;
|
||||
|
||||
do
|
||||
{
|
||||
var c = json[index];
|
||||
|
||||
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
|
||||
{
|
||||
if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number");
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
return new string(json, startIndex, index - startIndex);
|
||||
}
|
||||
|
||||
private Token LookAhead()
|
||||
{
|
||||
if (lookAheadToken != Token.None) return lookAheadToken;
|
||||
|
||||
return lookAheadToken = NextTokenCore();
|
||||
}
|
||||
|
||||
private void ConsumeToken()
|
||||
{
|
||||
lookAheadToken = Token.None;
|
||||
}
|
||||
|
||||
private Token NextToken()
|
||||
{
|
||||
var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore();
|
||||
|
||||
lookAheadToken = Token.None;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Token NextTokenCore()
|
||||
{
|
||||
char c;
|
||||
|
||||
// Skip past whitespace
|
||||
do
|
||||
{
|
||||
c = json[index];
|
||||
|
||||
if (c > ' ') break;
|
||||
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
|
||||
|
||||
} while (++index < json.Length);
|
||||
|
||||
if (index == json.Length)
|
||||
{
|
||||
throw new Exception("Reached end of string unexpectedly");
|
||||
}
|
||||
|
||||
c = json[index];
|
||||
|
||||
index++;
|
||||
|
||||
//if (c >= '0' && c <= '9')
|
||||
// return Token.Number;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '{':
|
||||
return Token.Curly_Open;
|
||||
|
||||
case '}':
|
||||
return Token.Curly_Close;
|
||||
|
||||
case '[':
|
||||
return Token.Squared_Open;
|
||||
|
||||
case ']':
|
||||
return Token.Squared_Close;
|
||||
|
||||
case ',':
|
||||
return Token.Comma;
|
||||
|
||||
case '"':
|
||||
return Token.String;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '-': case '+': case '.':
|
||||
return Token.Number;
|
||||
|
||||
case ':':
|
||||
return Token.Colon;
|
||||
|
||||
case 'f':
|
||||
if (json.Length - index >= 4 &&
|
||||
json[index + 0] == 'a' &&
|
||||
json[index + 1] == 'l' &&
|
||||
json[index + 2] == 's' &&
|
||||
json[index + 3] == 'e')
|
||||
{
|
||||
index += 4;
|
||||
return Token.False;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
if (json.Length - index >= 3 &&
|
||||
json[index + 0] == 'r' &&
|
||||
json[index + 1] == 'u' &&
|
||||
json[index + 2] == 'e')
|
||||
{
|
||||
index += 3;
|
||||
return Token.True;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (json.Length - index >= 3 &&
|
||||
json[index + 0] == 'u' &&
|
||||
json[index + 1] == 'l' &&
|
||||
json[index + 2] == 'l')
|
||||
{
|
||||
index += 3;
|
||||
return Token.Null;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
throw new Exception("Could not find token at index " + --index);
|
||||
}
|
||||
}
|
||||
}
|
519
src/Exceptron.Client/fastJSON/JsonSerializer.cs
Normal file
519
src/Exceptron.Client/fastJSON/JsonSerializer.cs
Normal file
|
@ -0,0 +1,519 @@
|
|||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Exceptron.Client.fastJSON
|
||||
{
|
||||
internal class JSONSerializer
|
||||
{
|
||||
private readonly StringBuilder _output = new StringBuilder();
|
||||
readonly bool useMinimalDataSetSchema;
|
||||
readonly bool fastguid = true;
|
||||
readonly bool useExtension = true;
|
||||
readonly bool serializeNulls = true;
|
||||
readonly int _MAX_DEPTH = 10;
|
||||
readonly bool _Indent;
|
||||
readonly bool _useGlobalTypes = true;
|
||||
int _current_depth;
|
||||
private readonly Dictionary<string, int> _globalTypes = new Dictionary<string, int>();
|
||||
|
||||
internal JSONSerializer(bool UseMinimalDataSetSchema, bool UseFastGuid, bool UseExtensions, bool SerializeNulls, bool IndentOutput)
|
||||
{
|
||||
useMinimalDataSetSchema = UseMinimalDataSetSchema;
|
||||
fastguid = UseFastGuid;
|
||||
useExtension = UseExtensions;
|
||||
_Indent = IndentOutput;
|
||||
serializeNulls = SerializeNulls;
|
||||
if (useExtension == false)
|
||||
_useGlobalTypes = false;
|
||||
}
|
||||
|
||||
internal string ConvertToJSON(object obj)
|
||||
{
|
||||
WriteValue(obj);
|
||||
|
||||
string str = "";
|
||||
if (_useGlobalTypes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("{\"$types\":{");
|
||||
bool pendingSeparator = false;
|
||||
foreach (var kv in _globalTypes)
|
||||
{
|
||||
if (pendingSeparator) sb.Append(',');
|
||||
pendingSeparator = true;
|
||||
sb.Append("\"");
|
||||
sb.Append(kv.Key);
|
||||
sb.Append("\":\"");
|
||||
sb.Append(kv.Value);
|
||||
sb.Append("\"");
|
||||
}
|
||||
sb.Append("},");
|
||||
str = sb + _output.ToString();
|
||||
}
|
||||
else
|
||||
str = _output.ToString();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private void WriteValue(object obj)
|
||||
{
|
||||
if (obj == null || obj is DBNull)
|
||||
_output.Append("null");
|
||||
|
||||
else if (obj is string || obj is char)
|
||||
WriteString((string)obj);
|
||||
|
||||
else if (obj is Guid)
|
||||
WriteGuid((Guid)obj);
|
||||
|
||||
else if (obj is bool)
|
||||
_output.Append(((bool)obj) ? "true" : "false"); // conform to standard
|
||||
|
||||
else if (
|
||||
obj is int || obj is long || obj is double ||
|
||||
obj is decimal || obj is float ||
|
||||
obj is byte || obj is short ||
|
||||
obj is sbyte || obj is ushort ||
|
||||
obj is uint || obj is ulong
|
||||
)
|
||||
_output.Append(((IConvertible)obj).ToString(NumberFormatInfo.InvariantInfo));
|
||||
|
||||
else if (obj is DateTime)
|
||||
WriteDateTime((DateTime)obj);
|
||||
|
||||
else if (obj is IDictionary && obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string))
|
||||
WriteStringDictionary((IDictionary)obj);
|
||||
|
||||
else if (obj is IDictionary)
|
||||
WriteDictionary((IDictionary)obj);
|
||||
#if !SILVERLIGHT
|
||||
else if (obj is DataSet)
|
||||
WriteDataset((DataSet)obj);
|
||||
|
||||
else if (obj is DataTable)
|
||||
WriteDataTable((DataTable)obj);
|
||||
#endif
|
||||
else if (obj is byte[])
|
||||
WriteBytes((byte[])obj);
|
||||
|
||||
else if (obj is Array || obj is IList || obj is ICollection)
|
||||
WriteArray((IEnumerable)obj);
|
||||
|
||||
else if (obj is Enum)
|
||||
WriteEnum((Enum)obj);
|
||||
|
||||
#if CUSTOMTYPE
|
||||
else if (JSON.Instance.IsTypeRegistered(obj.GetType()))
|
||||
WriteCustom(obj);
|
||||
#endif
|
||||
else
|
||||
WriteObject(obj);
|
||||
}
|
||||
|
||||
#if CUSTOMTYPE
|
||||
private void WriteCustom(object obj)
|
||||
{
|
||||
Serialize s;
|
||||
JSON.Instance._customSerializer.TryGetValue(obj.GetType(), out s);
|
||||
WriteStringFast(s(obj));
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteEnum(Enum e)
|
||||
{
|
||||
// TODO : optimize enum write
|
||||
WriteStringFast(e.ToString());
|
||||
}
|
||||
|
||||
private void WriteGuid(Guid g)
|
||||
{
|
||||
if (fastguid == false)
|
||||
WriteStringFast(g.ToString());
|
||||
else
|
||||
WriteBytes(g.ToByteArray());
|
||||
}
|
||||
|
||||
private void WriteBytes(byte[] bytes)
|
||||
{
|
||||
#if !SILVERLIGHT
|
||||
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length, Base64FormattingOptions.None));
|
||||
#else
|
||||
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length));
|
||||
#endif
|
||||
}
|
||||
|
||||
private void WriteDateTime(DateTime dateTime)
|
||||
{
|
||||
// datetime format standard : yyyy-MM-dd HH:mm:ss
|
||||
DateTime dt = dateTime;
|
||||
if (JSON.Instance.UseUTCDateTime)
|
||||
dt = dateTime.ToUniversalTime();
|
||||
|
||||
_output.Append("\"");
|
||||
_output.Append(dt.Year.ToString("0000", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append("-");
|
||||
_output.Append(dt.Month.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append("-");
|
||||
_output.Append(dt.Day.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append(" ");
|
||||
_output.Append(dt.Hour.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append(":");
|
||||
_output.Append(dt.Minute.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append(":");
|
||||
_output.Append(dt.Second.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
|
||||
if (JSON.Instance.UseUTCDateTime)
|
||||
_output.Append("Z");
|
||||
|
||||
_output.Append("\"");
|
||||
}
|
||||
#if !SILVERLIGHT
|
||||
private DatasetSchema GetSchema(DataTable ds)
|
||||
{
|
||||
if (ds == null) return null;
|
||||
|
||||
DatasetSchema m = new DatasetSchema();
|
||||
m.Info = new List<string>();
|
||||
m.Name = ds.TableName;
|
||||
|
||||
foreach (DataColumn c in ds.Columns)
|
||||
{
|
||||
m.Info.Add(ds.TableName);
|
||||
m.Info.Add(c.ColumnName);
|
||||
m.Info.Add(c.DataType.ToString());
|
||||
}
|
||||
// TODO : serialize relations and constraints here
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private DatasetSchema GetSchema(DataSet ds)
|
||||
{
|
||||
if (ds == null) return null;
|
||||
|
||||
DatasetSchema m = new DatasetSchema();
|
||||
m.Info = new List<string>();
|
||||
m.Name = ds.DataSetName;
|
||||
|
||||
foreach (DataTable t in ds.Tables)
|
||||
{
|
||||
foreach (DataColumn c in t.Columns)
|
||||
{
|
||||
m.Info.Add(t.TableName);
|
||||
m.Info.Add(c.ColumnName);
|
||||
m.Info.Add(c.DataType.ToString());
|
||||
}
|
||||
}
|
||||
// TODO : serialize relations and constraints here
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private string GetXmlSchema(DataTable dt)
|
||||
{
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
dt.WriteXmlSchema(writer);
|
||||
return dt.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteDataset(DataSet ds)
|
||||
{
|
||||
_output.Append('{');
|
||||
if (useExtension)
|
||||
{
|
||||
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(ds) : ds.GetXmlSchema());
|
||||
_output.Append(',');
|
||||
}
|
||||
bool tablesep = false;
|
||||
foreach (DataTable table in ds.Tables)
|
||||
{
|
||||
if (tablesep) _output.Append(",");
|
||||
tablesep = true;
|
||||
WriteDataTableData(table);
|
||||
}
|
||||
// end dataset
|
||||
_output.Append('}');
|
||||
}
|
||||
|
||||
private void WriteDataTableData(DataTable table)
|
||||
{
|
||||
_output.Append('\"');
|
||||
_output.Append(table.TableName);
|
||||
_output.Append("\":[");
|
||||
DataColumnCollection cols = table.Columns;
|
||||
bool rowseparator = false;
|
||||
foreach (DataRow row in table.Rows)
|
||||
{
|
||||
if (rowseparator) _output.Append(",");
|
||||
rowseparator = true;
|
||||
_output.Append('[');
|
||||
|
||||
bool pendingSeperator = false;
|
||||
foreach (DataColumn column in cols)
|
||||
{
|
||||
if (pendingSeperator) _output.Append(',');
|
||||
WriteValue(row[column]);
|
||||
pendingSeperator = true;
|
||||
}
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
void WriteDataTable(DataTable dt)
|
||||
{
|
||||
_output.Append('{');
|
||||
if (useExtension)
|
||||
{
|
||||
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(dt) : GetXmlSchema(dt));
|
||||
_output.Append(',');
|
||||
}
|
||||
|
||||
WriteDataTableData(dt);
|
||||
|
||||
// end datatable
|
||||
_output.Append('}');
|
||||
}
|
||||
#endif
|
||||
bool _firstWritten;
|
||||
private void WriteObject(object obj)
|
||||
{
|
||||
Indent();
|
||||
if (_useGlobalTypes == false)
|
||||
_output.Append('{');
|
||||
else
|
||||
{
|
||||
if (_firstWritten)
|
||||
_output.Append("{");
|
||||
}
|
||||
_firstWritten = true;
|
||||
_current_depth++;
|
||||
if (_current_depth > _MAX_DEPTH)
|
||||
throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH);
|
||||
|
||||
|
||||
var map = new Dictionary<string, string>();
|
||||
Type t = obj.GetType();
|
||||
bool append = false;
|
||||
if (useExtension)
|
||||
{
|
||||
if (_useGlobalTypes == false)
|
||||
WritePairFast("$type", JSON.Instance.GetTypeAssemblyName(t));
|
||||
else
|
||||
{
|
||||
int dt = 0;
|
||||
string ct = JSON.Instance.GetTypeAssemblyName(t);
|
||||
if (_globalTypes.TryGetValue(ct, out dt) == false)
|
||||
{
|
||||
dt = _globalTypes.Count + 1;
|
||||
_globalTypes.Add(ct, dt);
|
||||
}
|
||||
WritePairFast("$type", dt.ToString());
|
||||
}
|
||||
append = true;
|
||||
}
|
||||
|
||||
var g = JSON.Instance.GetGetters(t);
|
||||
foreach (var p in g)
|
||||
{
|
||||
if (append)
|
||||
_output.Append(',');
|
||||
object o = p.Getter(obj);
|
||||
if ((o == null || o is DBNull) && serializeNulls == false)
|
||||
append = false;
|
||||
else
|
||||
{
|
||||
WritePair(p.Name, o);
|
||||
if (o != null && useExtension)
|
||||
{
|
||||
Type tt = o.GetType();
|
||||
if (tt == typeof(Object))
|
||||
map.Add(p.Name, tt.ToString());
|
||||
}
|
||||
append = true;
|
||||
}
|
||||
}
|
||||
if (map.Count > 0 && useExtension)
|
||||
{
|
||||
_output.Append(",\"$map\":");
|
||||
WriteStringDictionary(map);
|
||||
}
|
||||
_current_depth--;
|
||||
Indent();
|
||||
_output.Append('}');
|
||||
_current_depth--;
|
||||
|
||||
}
|
||||
|
||||
private void Indent()
|
||||
{
|
||||
Indent(false);
|
||||
}
|
||||
|
||||
private void Indent(bool dec)
|
||||
{
|
||||
if (_Indent)
|
||||
{
|
||||
_output.Append("\r\n");
|
||||
for (int i = 0; i < _current_depth - (dec ? 1 : 0); i++)
|
||||
_output.Append("\t");
|
||||
}
|
||||
}
|
||||
|
||||
private void WritePairFast(string name, string value)
|
||||
{
|
||||
if ((value == null) && serializeNulls == false)
|
||||
return;
|
||||
Indent();
|
||||
WriteStringFast(name);
|
||||
|
||||
_output.Append(':');
|
||||
|
||||
WriteStringFast(value);
|
||||
}
|
||||
|
||||
private void WritePair(string name, object value)
|
||||
{
|
||||
if ((value == null || value is DBNull) && serializeNulls == false)
|
||||
return;
|
||||
Indent();
|
||||
WriteStringFast(name);
|
||||
|
||||
_output.Append(':');
|
||||
|
||||
WriteValue(value);
|
||||
}
|
||||
|
||||
private void WriteArray(IEnumerable array)
|
||||
{
|
||||
Indent();
|
||||
_output.Append('[');
|
||||
|
||||
bool pendingSeperator = false;
|
||||
|
||||
foreach (var obj in array)
|
||||
{
|
||||
Indent();
|
||||
if (pendingSeperator) _output.Append(',');
|
||||
|
||||
WriteValue(obj);
|
||||
|
||||
pendingSeperator = true;
|
||||
}
|
||||
Indent();
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
private void WriteStringDictionary(IDictionary dic)
|
||||
{
|
||||
Indent();
|
||||
_output.Append('{');
|
||||
|
||||
bool pendingSeparator = false;
|
||||
|
||||
foreach (DictionaryEntry entry in dic)
|
||||
{
|
||||
if (pendingSeparator) _output.Append(',');
|
||||
|
||||
WritePair((string)entry.Key, entry.Value);
|
||||
|
||||
pendingSeparator = true;
|
||||
}
|
||||
Indent();
|
||||
_output.Append('}');
|
||||
}
|
||||
|
||||
private void WriteDictionary(IDictionary dic)
|
||||
{
|
||||
Indent();
|
||||
_output.Append('[');
|
||||
|
||||
bool pendingSeparator = false;
|
||||
|
||||
foreach (DictionaryEntry entry in dic)
|
||||
{
|
||||
if (pendingSeparator) _output.Append(',');
|
||||
Indent();
|
||||
_output.Append('{');
|
||||
WritePair("k", entry.Key);
|
||||
_output.Append(",");
|
||||
WritePair("v", entry.Value);
|
||||
Indent();
|
||||
_output.Append('}');
|
||||
|
||||
pendingSeparator = true;
|
||||
}
|
||||
Indent();
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
private void WriteStringFast(string s)
|
||||
{
|
||||
//Indent();
|
||||
_output.Append('\"');
|
||||
_output.Append(s);
|
||||
_output.Append('\"');
|
||||
}
|
||||
|
||||
private void WriteString(string s)
|
||||
{
|
||||
//Indent();
|
||||
_output.Append('\"');
|
||||
|
||||
int runIndex = -1;
|
||||
|
||||
for (var index = 0; index < s.Length; ++index)
|
||||
{
|
||||
var c = s[index];
|
||||
|
||||
if (c >= ' ' && c < 128 && c != '\"' && c != '\\')
|
||||
{
|
||||
if (runIndex == -1)
|
||||
{
|
||||
runIndex = index;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runIndex != -1)
|
||||
{
|
||||
_output.Append(s, runIndex, index - runIndex);
|
||||
runIndex = -1;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\t': _output.Append("\\t"); break;
|
||||
case '\r': _output.Append("\\r"); break;
|
||||
case '\n': _output.Append("\\n"); break;
|
||||
case '"':
|
||||
case '\\': _output.Append('\\'); _output.Append(c); break;
|
||||
default:
|
||||
_output.Append("\\u");
|
||||
_output.Append(((int)c).ToString("X4", NumberFormatInfo.InvariantInfo));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (runIndex != -1)
|
||||
{
|
||||
_output.Append(s, runIndex, s.Length - runIndex);
|
||||
}
|
||||
|
||||
_output.Append('\"');
|
||||
}
|
||||
}
|
||||
}
|
36
src/Exceptron.Client/fastJSON/SafeDictionary.cs
Normal file
36
src/Exceptron.Client/fastJSON/SafeDictionary.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Exceptron.Client.fastJSON
|
||||
{
|
||||
internal class SafeDictionary<TKey, TValue>
|
||||
{
|
||||
private readonly object _Padlock = new object();
|
||||
private readonly Dictionary<TKey, TValue> _Dictionary = new Dictionary<TKey, TValue>();
|
||||
|
||||
|
||||
internal bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return _Dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
internal TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Dictionary[key];
|
||||
}
|
||||
}
|
||||
|
||||
internal void Add(TKey key, TValue value)
|
||||
{
|
||||
lock (_Padlock)
|
||||
{
|
||||
if (_Dictionary.ContainsKey(key) == false)
|
||||
_Dictionary.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/Exceptron.Client/fastJSON/license.txt
Normal file
87
src/Exceptron.Client/fastJSON/license.txt
Normal file
|
@ -0,0 +1,87 @@
|
|||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
BIN
src/Libraries/Interop.NetFwTypeLib.dll
Normal file
BIN
src/Libraries/Interop.NetFwTypeLib.dll
Normal file
Binary file not shown.
BIN
src/Libraries/Manifest Tool/mt.exe
Normal file
BIN
src/Libraries/Manifest Tool/mt.exe
Normal file
Binary file not shown.
9
src/Libraries/Manifest Tool/mt.exe.config
Normal file
9
src/Libraries/Manifest Tool/mt.exe.config
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version ="1.0"?>
|
||||
<!-- This allows mt.exe to run on machines with the CLR v4 installed but not 1.1 or 2.0 -->
|
||||
<configuration>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<supportedRuntime version="v4.0"/>
|
||||
<supportedRuntime version="v2.0.50727"/>
|
||||
<supportedRuntime version="v1.1.4322"/>
|
||||
</startup>
|
||||
</configuration>
|
BIN
src/Libraries/Sqlite/System.Data.SQLite.dll
Normal file
BIN
src/Libraries/Sqlite/System.Data.SQLite.dll
Normal file
Binary file not shown.
5370
src/Libraries/Sqlite/System.Data.SQLite.xml
Normal file
5370
src/Libraries/Sqlite/System.Data.SQLite.xml
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Libraries/Sqlite/sqlite3.dll
Normal file
BIN
src/Libraries/Sqlite/sqlite3.dll
Normal file
Binary file not shown.
74
src/Marr.Data/Converters/BooleanIntConverter.cs
Normal file
74
src/Marr.Data/Converters/BooleanIntConverter.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class BooleanIntConverter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
int val = (int)context.DbValue;
|
||||
|
||||
if (val == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (val == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
throw new ConversionException(
|
||||
string.Format(
|
||||
"The BooleanCharConverter could not convert the value '{0}' to a boolean.",
|
||||
context.DbValue));
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
bool? val = (bool?)clrValue;
|
||||
|
||||
if (val == true)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (val == false)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
public Type DbType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(int);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
src/Marr.Data/Converters/BooleanYNConverter.cs
Normal file
74
src/Marr.Data/Converters/BooleanYNConverter.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class BooleanYNConverter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
string val = context.DbValue.ToString();
|
||||
|
||||
if (val == "Y")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (val == "N")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
throw new ConversionException(
|
||||
string.Format(
|
||||
"The BooleanYNConverter could not convert the value '{0}' to a boolean.",
|
||||
context.DbValue));
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext {ColumnMap = map, DbValue = dbValue});
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
bool? val = (bool?)clrValue;
|
||||
|
||||
if (val == true)
|
||||
{
|
||||
return "Y";
|
||||
}
|
||||
if (val == false)
|
||||
{
|
||||
return "N";
|
||||
}
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
public Type DbType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
src/Marr.Data/Converters/CastConverter.cs
Normal file
53
src/Marr.Data/Converters/CastConverter.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class CastConverter<TClr, TDb> : IConverter
|
||||
where TClr : IConvertible
|
||||
where TDb : IConvertible
|
||||
{
|
||||
#region IConversion Members
|
||||
|
||||
public Type DbType
|
||||
{
|
||||
get { return typeof(TDb); }
|
||||
}
|
||||
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
TDb val = (TDb)context.DbValue;
|
||||
return val.ToType(typeof(TClr), CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
TClr val = (TClr)clrValue;
|
||||
return val.ToType(typeof(TDb), CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
26
src/Marr.Data/Converters/ConversionException.cs
Normal file
26
src/Marr.Data/Converters/ConversionException.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class ConversionException : Exception
|
||||
{
|
||||
public ConversionException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
}
|
||||
}
|
13
src/Marr.Data/Converters/ConverterContext.cs
Normal file
13
src/Marr.Data/Converters/ConverterContext.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.Data;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class ConverterContext
|
||||
{
|
||||
public ColumnMap ColumnMap { get; set; }
|
||||
public object DbValue { get; set; }
|
||||
public ColumnMapCollection MapCollection { get; set; }
|
||||
public IDataRecord DataRecord { get; set; }
|
||||
}
|
||||
}
|
50
src/Marr.Data/Converters/EnumIntConverter.cs
Normal file
50
src/Marr.Data/Converters/EnumIntConverter.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class EnumIntConverter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == null || context.DbValue == DBNull.Value)
|
||||
return null;
|
||||
return Enum.ToObject(context.ColumnMap.FieldType, (int)context.DbValue);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == null)
|
||||
return DBNull.Value;
|
||||
return (int)clrValue;
|
||||
}
|
||||
|
||||
public Type DbType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(int);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
src/Marr.Data/Converters/EnumStringConverter.cs
Normal file
50
src/Marr.Data/Converters/EnumStringConverter.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public class EnumStringConverter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == null || context.DbValue == DBNull.Value)
|
||||
return null;
|
||||
return Enum.Parse(context.ColumnMap.FieldType, (string)context.DbValue);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == null)
|
||||
return DBNull.Value;
|
||||
return clrValue.ToString();
|
||||
}
|
||||
|
||||
public Type DbType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
src/Marr.Data/Converters/IConverter.cs
Normal file
30
src/Marr.Data/Converters/IConverter.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace Marr.Data.Converters
|
||||
{
|
||||
public interface IConverter
|
||||
{
|
||||
object FromDB(ConverterContext context);
|
||||
|
||||
[Obsolete("use FromDB(ConverterContext context) instead")]
|
||||
object FromDB(ColumnMap map, object dbValue);
|
||||
object ToDB(object clrValue);
|
||||
Type DbType { get; }
|
||||
}
|
||||
}
|
166
src/Marr.Data/DataHelper.cs
Normal file
166
src/Marr.Data/DataHelper.cs
Normal file
|
@ -0,0 +1,166 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains misc. extension methods that are used throughout the project.
|
||||
/// </summary>
|
||||
internal static class DataHelper
|
||||
{
|
||||
public static bool HasColumn(this IDataReader dr, string columnName)
|
||||
{
|
||||
for (int i=0; i < dr.FieldCount; i++)
|
||||
{
|
||||
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string ParameterPrefix(this IDbCommand command)
|
||||
{
|
||||
string commandType = command.GetType().Name.ToLower();
|
||||
return commandType.Contains("oracle") ? ":" : "@";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mapped name, or the member name.
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetTableName(this MemberInfo member)
|
||||
{
|
||||
string tableName = MapRepository.Instance.GetTableName(member.DeclaringType);
|
||||
return tableName ?? member.DeclaringType.Name;
|
||||
}
|
||||
|
||||
public static string GetTableName(this Type memberType)
|
||||
{
|
||||
return MapRepository.Instance.GetTableName(memberType);
|
||||
}
|
||||
|
||||
public static string GetColumName(this IColumnInfo col, bool useAltName)
|
||||
{
|
||||
if (useAltName)
|
||||
{
|
||||
return col.TryGetAltName();
|
||||
}
|
||||
return col.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mapped column name, or the member name.
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetColumnName(Type declaringType, string propertyName, bool useAltName)
|
||||
{
|
||||
// Initialize column name as member name
|
||||
string columnName = propertyName;
|
||||
|
||||
var columnMap = MapRepository.Instance.GetColumns(declaringType).GetByFieldName(propertyName);
|
||||
|
||||
if (columnMap == null)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Column map missing for field {0}.{1}", declaringType.FullName, propertyName));
|
||||
}
|
||||
|
||||
if (useAltName)
|
||||
{
|
||||
columnName = columnMap.ColumnInfo.TryGetAltName();
|
||||
}
|
||||
else
|
||||
{
|
||||
columnName = columnMap.ColumnInfo.Name;
|
||||
}
|
||||
|
||||
return columnName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a property name from a passed in expression.
|
||||
/// Ex: p => p.FirstName -> "FirstName
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="member"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMemberName<T>(this Expression<Func<T, object>> member)
|
||||
{
|
||||
var memberExpression = (member.Body as MemberExpression);
|
||||
if (memberExpression == null)
|
||||
{
|
||||
memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression;
|
||||
}
|
||||
|
||||
return memberExpression.Member.Name;
|
||||
}
|
||||
|
||||
public static string GetMemberName(this LambdaExpression exp)
|
||||
{
|
||||
var memberExpression = (exp.Body as MemberExpression);
|
||||
if (memberExpression == null)
|
||||
{
|
||||
memberExpression = (exp.Body as UnaryExpression).Operand as MemberExpression;
|
||||
}
|
||||
|
||||
return memberExpression.Member.Name;
|
||||
}
|
||||
|
||||
public static bool ContainsMember(this List<MemberInfo> list, MemberInfo member)
|
||||
{
|
||||
foreach (var m in list)
|
||||
{
|
||||
if (m.EqualsMember(member))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool EqualsMember(this MemberInfo member, MemberInfo otherMember)
|
||||
{
|
||||
return member.Name == otherMember.Name && member.DeclaringType == otherMember.DeclaringType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a type is not a complex object.
|
||||
/// </summary>
|
||||
public static bool IsSimpleType(Type type)
|
||||
{
|
||||
Type underlyingType = !IsNullableType(type) ? type : type.GetGenericArguments()[0];
|
||||
|
||||
return
|
||||
underlyingType.IsPrimitive ||
|
||||
underlyingType.Equals(typeof(string)) ||
|
||||
underlyingType.Equals(typeof(DateTime)) ||
|
||||
underlyingType.Equals(typeof(decimal)) ||
|
||||
underlyingType.IsEnum;
|
||||
}
|
||||
|
||||
public static bool IsNullableType(Type theType)
|
||||
{
|
||||
return (theType.IsGenericType && theType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
967
src/Marr.Data/DataMapper.cs
Normal file
967
src/Marr.Data/DataMapper.cs
Normal file
|
@ -0,0 +1,967 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
using Marr.Data.Mapping;
|
||||
using Marr.Data.Parameters;
|
||||
using Marr.Data.QGen;
|
||||
using System.Linq.Expressions;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is the main access point for making database related calls.
|
||||
/// </summary>
|
||||
public class DataMapper : IDataMapper
|
||||
{
|
||||
|
||||
#region - Contructor, Members -
|
||||
|
||||
private DbCommand _command;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a DataMapper for the given provider type and connection string.
|
||||
/// </summary>
|
||||
/// <param name="providerName">Ex: </param>
|
||||
/// <param name="connectionString">The database connection string.</param>
|
||||
public DataMapper(string providerName, string connectionString)
|
||||
: this(DbProviderFactories.GetFactory(providerName), connectionString)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// A database provider agnostic initialization.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The database connection string.</param>
|
||||
public DataMapper(DbProviderFactory dbProviderFactory, string connectionString)
|
||||
{
|
||||
SqlMode = SqlModes.StoredProcedure;
|
||||
if (dbProviderFactory == null)
|
||||
throw new ArgumentNullException("dbProviderFactory");
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
throw new ArgumentNullException("connectionString");
|
||||
|
||||
ProviderFactory = dbProviderFactory;
|
||||
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
|
||||
public string ConnectionString { get; private set; }
|
||||
|
||||
public DbProviderFactory ProviderFactory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command utilizing the connection string.
|
||||
/// </summary>
|
||||
private DbCommand CreateNewCommand()
|
||||
{
|
||||
DbConnection conn = ProviderFactory.CreateConnection();
|
||||
conn.ConnectionString = ConnectionString;
|
||||
DbCommand cmd = conn.CreateCommand();
|
||||
SetSqlMode(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command utilizing the connection string with a given SQL command.
|
||||
/// </summary>
|
||||
private DbCommand CreateNewCommand(string sql)
|
||||
{
|
||||
DbCommand cmd = CreateNewCommand();
|
||||
cmd.CommandText = sql;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a DbCommand object.
|
||||
/// </summary>
|
||||
public DbCommand Command
|
||||
{
|
||||
get
|
||||
{
|
||||
// Lazy load
|
||||
if (_command == null)
|
||||
_command = CreateNewCommand();
|
||||
else
|
||||
SetSqlMode(_command); // Set SqlMode every time.
|
||||
|
||||
return _command;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Parameters -
|
||||
|
||||
public DbParameterCollection Parameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return Command.Parameters;
|
||||
}
|
||||
}
|
||||
|
||||
public ParameterChainMethods AddParameter(string name, object value)
|
||||
{
|
||||
return new ParameterChainMethods(Command, name, value);
|
||||
}
|
||||
|
||||
public IDbDataParameter AddParameter(IDbDataParameter parameter)
|
||||
{
|
||||
// Convert null values to DBNull.Value
|
||||
if (parameter.Value == null)
|
||||
parameter.Value = DBNull.Value;
|
||||
|
||||
Parameters.Add(parameter);
|
||||
return parameter;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - SP / SQL Mode -
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines whether the DataMapper will
|
||||
/// use a stored procedure or a sql text command to access
|
||||
/// the database. The default is stored procedure.
|
||||
/// </summary>
|
||||
public SqlModes SqlMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the DbCommand objects CommandType to the current SqlMode.
|
||||
/// </summary>
|
||||
/// <param name="command">The DbCommand object we are modifying.</param>
|
||||
/// <returns>Returns the same DbCommand that was passed in.</returns>
|
||||
private DbCommand SetSqlMode(DbCommand command)
|
||||
{
|
||||
if (SqlMode == SqlModes.StoredProcedure)
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
else
|
||||
command.CommandType = CommandType.Text;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - ExecuteScalar, ExecuteNonQuery, ExecuteReader -
|
||||
|
||||
/// <summary>
|
||||
/// Executes a stored procedure that returns a scalar value.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <returns>A scalar value</returns>
|
||||
public object ExecuteScalar(string sql)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
Command.CommandText = sql;
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
return Command.ExecuteScalar();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a non query that returns an integer.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <returns>An integer value</returns>
|
||||
public int ExecuteNonQuery(string sql)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
Command.CommandText = sql;
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
return Command.ExecuteNonQuery();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a DataReader that can be controlled using a Func delegate.
|
||||
/// (Note that reader.Read() will be called automatically).
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type that will be return in the result set.</typeparam>
|
||||
/// <param name="sql">The sql statement that will be executed.</param>
|
||||
/// <param name="func">The function that will build the the TResult set.</param>
|
||||
/// <returns>An IEnumerable of TResult.</returns>
|
||||
public IEnumerable<TResult> ExecuteReader<TResult>(string sql, Func<DbDataReader, TResult> func)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
Command.CommandText = sql;
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
|
||||
var list = new List<TResult>();
|
||||
DbDataReader reader = null;
|
||||
try
|
||||
{
|
||||
reader = Command.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
list.Add(func(reader));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (reader != null) reader.Close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a DataReader that can be controlled using an Action delegate.
|
||||
/// </summary>
|
||||
/// <param name="sql">The sql statement that will be executed.</param>
|
||||
/// <param name="action">The delegate that will work with the result set.</param>
|
||||
public void ExecuteReader(string sql, Action<DbDataReader> action)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
|
||||
Command.CommandText = sql;
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
|
||||
DbDataReader reader = null;
|
||||
try
|
||||
{
|
||||
reader = Command.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
action(reader);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (reader != null) reader.Close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - DataSets -
|
||||
|
||||
public DataSet GetDataSet(string sql)
|
||||
{
|
||||
return GetDataSet(sql, new DataSet(), null);
|
||||
}
|
||||
|
||||
public DataSet GetDataSet(string sql, DataSet ds, string tableName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
|
||||
try
|
||||
{
|
||||
using (DbDataAdapter adapter = ProviderFactory.CreateDataAdapter())
|
||||
{
|
||||
Command.CommandText = sql;
|
||||
adapter.SelectCommand = Command;
|
||||
|
||||
if (ds == null)
|
||||
ds = new DataSet();
|
||||
|
||||
OpenConnection();
|
||||
|
||||
if (string.IsNullOrEmpty(tableName))
|
||||
adapter.Fill(ds);
|
||||
else
|
||||
adapter.Fill(ds, tableName);
|
||||
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection(); // Clears parameters
|
||||
}
|
||||
}
|
||||
|
||||
public DataTable GetDataTable(string sql)
|
||||
{
|
||||
return GetDataTable(sql, null, null);
|
||||
}
|
||||
|
||||
public DataTable GetDataTable(string sql, DataTable dt, string tableName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
|
||||
try
|
||||
{
|
||||
using (DbDataAdapter adapter = ProviderFactory.CreateDataAdapter())
|
||||
{
|
||||
Command.CommandText = sql;
|
||||
adapter.SelectCommand = Command;
|
||||
|
||||
if (dt == null)
|
||||
dt = new DataTable();
|
||||
|
||||
adapter.Fill(dt);
|
||||
|
||||
if (!string.IsNullOrEmpty(tableName))
|
||||
dt.TableName = tableName;
|
||||
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection(); // Clears parameters
|
||||
}
|
||||
}
|
||||
|
||||
public int UpdateDataSet(DataSet ds, string sql)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
|
||||
if (ds == null)
|
||||
throw new ArgumentNullException("ds", "DataSet cannot be null.");
|
||||
|
||||
DbDataAdapter adapter = null;
|
||||
|
||||
try
|
||||
{
|
||||
adapter = ProviderFactory.CreateDataAdapter();
|
||||
|
||||
adapter.UpdateCommand = Command;
|
||||
adapter.UpdateCommand.CommandText = sql;
|
||||
|
||||
return adapter.Update(ds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (adapter.UpdateCommand != null)
|
||||
adapter.UpdateCommand.Dispose();
|
||||
|
||||
adapter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public int InsertDataTable(DataTable table, string insertSP)
|
||||
{
|
||||
return InsertDataTable(table, insertSP, UpdateRowSource.None);
|
||||
}
|
||||
|
||||
public int InsertDataTable(DataTable dt, string sql, UpdateRowSource updateRowSource)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
|
||||
if (dt == null)
|
||||
throw new ArgumentNullException("dt", "DataTable cannot be null.");
|
||||
|
||||
DbDataAdapter adapter = null;
|
||||
|
||||
try
|
||||
{
|
||||
adapter = ProviderFactory.CreateDataAdapter();
|
||||
|
||||
adapter.InsertCommand = Command;
|
||||
adapter.InsertCommand.CommandText = sql;
|
||||
|
||||
adapter.InsertCommand.UpdatedRowSource = updateRowSource;
|
||||
|
||||
return adapter.Update(dt);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (adapter.InsertCommand != null)
|
||||
adapter.InsertCommand.Dispose();
|
||||
|
||||
adapter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public int DeleteDataTable(DataTable dt, string sql)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required");
|
||||
|
||||
if (dt == null)
|
||||
throw new ArgumentNullException("dt", "DataSet cannot be null.");
|
||||
|
||||
DbDataAdapter adapter = null;
|
||||
|
||||
try
|
||||
{
|
||||
adapter = ProviderFactory.CreateDataAdapter();
|
||||
|
||||
adapter.DeleteCommand = Command;
|
||||
adapter.DeleteCommand.CommandText = sql;
|
||||
|
||||
return adapter.Update(dt);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (adapter.DeleteCommand != null)
|
||||
adapter.DeleteCommand.Dispose();
|
||||
|
||||
adapter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Find -
|
||||
|
||||
public T Find<T>(string sql)
|
||||
{
|
||||
return Find<T>(sql, default(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an entity of type T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of entity that is to be instantiated and loaded with values.</typeparam>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <returns>An instantiated and loaded entity of type T.</returns>
|
||||
public T Find<T>(string sql, T ent)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A stored procedure name has not been specified for 'Find'.");
|
||||
|
||||
Type entityType = typeof(T);
|
||||
Command.CommandText = sql;
|
||||
|
||||
MapRepository repository = MapRepository.Instance;
|
||||
ColumnMapCollection mappings = repository.GetColumns(entityType);
|
||||
|
||||
bool isSimpleType = DataHelper.IsSimpleType(typeof(T));
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
var mappingHelper = new MappingHelper(this);
|
||||
|
||||
using (DbDataReader reader = Command.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
if (isSimpleType)
|
||||
{
|
||||
return mappingHelper.LoadSimpleValueFromFirstColumn<T>(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent == null)
|
||||
ent = (T)mappingHelper.CreateAndLoadEntity<T>(mappings, reader, false);
|
||||
else
|
||||
mappingHelper.LoadExistingEntity(mappings, reader, ent, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Query -
|
||||
|
||||
/// <summary>
|
||||
/// Creates a QueryBuilder that allows you to build a query.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object that will be queried.</typeparam>
|
||||
/// <returns>Returns a QueryBuilder of T.</returns>
|
||||
public QueryBuilder<T> Query<T>()
|
||||
{
|
||||
var dialect = QueryFactory.CreateDialect(this);
|
||||
return new QueryBuilder<T>(this, dialect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the results of a query.
|
||||
/// Uses a List of type T to return the data.
|
||||
/// </summary>
|
||||
/// <returns>Returns a list of the specified type.</returns>
|
||||
public List<T> Query<T>(string sql)
|
||||
{
|
||||
return (List<T>)Query<T>(sql, new List<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the results of a SP query.
|
||||
/// </summary>
|
||||
/// <returns>Returns a list of the specified type.</returns>
|
||||
public ICollection<T> Query<T>(string sql, ICollection<T> entityList)
|
||||
{
|
||||
return Query<T>(sql, entityList, false);
|
||||
}
|
||||
|
||||
internal ICollection<T> Query<T>(string sql, ICollection<T> entityList, bool useAltName)
|
||||
{
|
||||
if (entityList == null)
|
||||
throw new ArgumentNullException("entityList", "ICollection instance cannot be null.");
|
||||
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "A query or stored procedure has not been specified for 'Query'.");
|
||||
|
||||
var mappingHelper = new MappingHelper(this);
|
||||
Type entityType = typeof(T);
|
||||
Command.CommandText = sql;
|
||||
ColumnMapCollection mappings = MapRepository.Instance.GetColumns(entityType);
|
||||
|
||||
bool isSimpleType = DataHelper.IsSimpleType(typeof(T));
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
using (DbDataReader reader = Command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
if (isSimpleType)
|
||||
{
|
||||
entityList.Add(mappingHelper.LoadSimpleValueFromFirstColumn<T>(reader));
|
||||
}
|
||||
else
|
||||
{
|
||||
entityList.Add((T)mappingHelper.CreateAndLoadEntity<T>(mappings, reader, useAltName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
return entityList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Query to Graph -
|
||||
|
||||
public List<T> QueryToGraph<T>(string sql)
|
||||
{
|
||||
return (List<T>)QueryToGraph<T>(sql, new List<T>());
|
||||
}
|
||||
|
||||
public ICollection<T> QueryToGraph<T>(string sql, ICollection<T> entityList)
|
||||
{
|
||||
EntityGraph graph = new EntityGraph(typeof(T), (IList)entityList);
|
||||
return QueryToGraph<T>(sql, graph, new List<MemberInfo>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries a view that joins multiple tables and returns an object graph.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="entityList"></param>
|
||||
/// <param name="entityGraph">Coordinates loading all objects in the graph..</param>
|
||||
/// <returns></returns>
|
||||
internal ICollection<T> QueryToGraph<T>(string sql, EntityGraph graph, List<MemberInfo> childrenToLoad)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sql))
|
||||
throw new ArgumentNullException("sql", "sql");
|
||||
|
||||
var mappingHelper = new MappingHelper(this);
|
||||
Type parentType = typeof(T);
|
||||
Command.CommandText = sql;
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
using (DbDataReader reader = Command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
// The entire EntityGraph is traversed for each record,
|
||||
// and multiple entities are created from each view record.
|
||||
foreach (EntityGraph lvl in graph)
|
||||
{
|
||||
if (lvl.IsParentReference)
|
||||
{
|
||||
// A child specified a circular reference to its previously loaded parent
|
||||
lvl.AddParentReference();
|
||||
}
|
||||
else if (childrenToLoad.Count > 0 && !lvl.IsRoot && !childrenToLoad.ContainsMember(lvl.Member))
|
||||
{
|
||||
// A list of relationships-to-load was specified and this relationship was not included
|
||||
continue;
|
||||
}
|
||||
else if (lvl.IsNewGroup(reader))
|
||||
{
|
||||
// Create a new entity with the data reader
|
||||
var newEntity = mappingHelper.CreateAndLoadEntity(lvl.EntityType, lvl.Columns, reader, true);
|
||||
|
||||
// Add entity to the appropriate place in the object graph
|
||||
lvl.AddEntity(newEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
return (ICollection<T>)graph.RootList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Update -
|
||||
|
||||
public UpdateQueryBuilder<T> Update<T>()
|
||||
{
|
||||
return new UpdateQueryBuilder<T>(this);
|
||||
}
|
||||
|
||||
public int Update<T>(T entity, Expression<Func<T, bool>> filter)
|
||||
{
|
||||
return Update<T>()
|
||||
.Entity(entity)
|
||||
.Where(filter)
|
||||
.Execute();
|
||||
}
|
||||
|
||||
public int Update<T>(string tableName, T entity, Expression<Func<T, bool>> filter)
|
||||
{
|
||||
return Update<T>()
|
||||
.TableName(tableName)
|
||||
.Entity(entity)
|
||||
.Where(filter)
|
||||
.Execute();
|
||||
}
|
||||
|
||||
public int Update<T>(T entity, string sql)
|
||||
{
|
||||
return Update<T>()
|
||||
.Entity(entity)
|
||||
.QueryText(sql)
|
||||
.Execute();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Insert -
|
||||
|
||||
/// <summary>
|
||||
/// Creates an InsertQueryBuilder that allows you to build an insert statement.
|
||||
/// This method gives you the flexibility to manually configure all options of your insert statement.
|
||||
/// Note: You must manually call the Execute() chaining method to run the query.
|
||||
/// </summary>
|
||||
public InsertQueryBuilder<T> Insert<T>()
|
||||
{
|
||||
return new InsertQueryBuilder<T>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates and executes an insert query for the given entity.
|
||||
/// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
|
||||
/// and if an identity query has been implemented for your current database dialect.
|
||||
/// </summary>
|
||||
public object Insert<T>(T entity)
|
||||
{
|
||||
var columns = MapRepository.Instance.GetColumns(typeof(T));
|
||||
var dialect = QueryFactory.CreateDialect(this);
|
||||
var builder = Insert<T>().Entity(entity);
|
||||
|
||||
// If an auto-increment column exists and this dialect has an identity query...
|
||||
if (columns.Exists(c => c.ColumnInfo.IsAutoIncrement) && dialect.HasIdentityQuery)
|
||||
{
|
||||
builder.GetIdentity();
|
||||
}
|
||||
|
||||
return builder.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates and executes an insert query for the given entity.
|
||||
/// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
|
||||
/// and if an identity query has been implemented for your current database dialect.
|
||||
/// </summary>
|
||||
public object Insert<T>(string tableName, T entity)
|
||||
{
|
||||
var columns = MapRepository.Instance.GetColumns(typeof(T));
|
||||
var dialect = QueryFactory.CreateDialect(this);
|
||||
var builder = Insert<T>().Entity(entity).TableName(tableName);
|
||||
|
||||
// If an auto-increment column exists and this dialect has an identity query...
|
||||
if (columns.Exists(c => c.ColumnInfo.IsAutoIncrement) && dialect.HasIdentityQuery)
|
||||
{
|
||||
builder.GetIdentity();
|
||||
}
|
||||
|
||||
return builder.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an insert query for the given entity using the given sql insert statement.
|
||||
/// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
|
||||
/// and if an identity query has been implemented for your current database dialect.
|
||||
/// </summary>
|
||||
public object Insert<T>(T entity, string sql)
|
||||
{
|
||||
var columns = MapRepository.Instance.GetColumns(typeof(T));
|
||||
var dialect = QueryFactory.CreateDialect(this);
|
||||
var builder = Insert<T>().Entity(entity).QueryText(sql);
|
||||
|
||||
// If an auto-increment column exists and this dialect has an identity query...
|
||||
if (columns.Exists(c => c.ColumnInfo.IsAutoIncrement) && dialect.HasIdentityQuery)
|
||||
{
|
||||
builder.GetIdentity();
|
||||
}
|
||||
|
||||
return builder.Execute();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Delete -
|
||||
|
||||
public int Delete<T>(Expression<Func<T, bool>> filter)
|
||||
{
|
||||
return Delete<T>(null, filter);
|
||||
}
|
||||
|
||||
public int Delete<T>(string tableName, Expression<Func<T, bool>> filter)
|
||||
{
|
||||
// Remember sql mode
|
||||
var previousSqlMode = SqlMode;
|
||||
SqlMode = SqlModes.Text;
|
||||
|
||||
var mappingHelper = new MappingHelper(this);
|
||||
if (tableName == null)
|
||||
{
|
||||
tableName = MapRepository.Instance.GetTableName(typeof(T));
|
||||
}
|
||||
var dialect = QueryFactory.CreateDialect(this);
|
||||
TableCollection tables = new TableCollection();
|
||||
tables.Add(new Table(typeof(T)));
|
||||
var where = new WhereBuilder<T>(Command, dialect, filter, tables, false, false);
|
||||
IQuery query = QueryFactory.CreateDeleteQuery(dialect, tables[0], where.ToString());
|
||||
Command.CommandText = query.Generate();
|
||||
|
||||
int rowsAffected = 0;
|
||||
|
||||
try
|
||||
{
|
||||
OpenConnection();
|
||||
rowsAffected = Command.ExecuteNonQuery();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
// Return to previous sql mode
|
||||
SqlMode = previousSqlMode;
|
||||
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Events -
|
||||
|
||||
public event EventHandler OpeningConnection;
|
||||
|
||||
public event EventHandler ClosingConnection;
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Connections / Transactions -
|
||||
|
||||
protected virtual void OnOpeningConnection()
|
||||
{
|
||||
if (OpeningConnection != null)
|
||||
OpeningConnection(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnClosingConnection()
|
||||
{
|
||||
WriteToTraceLog();
|
||||
|
||||
if (ClosingConnection != null)
|
||||
ClosingConnection(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected internal void OpenConnection()
|
||||
{
|
||||
OnOpeningConnection();
|
||||
|
||||
if (Command.Connection.State != ConnectionState.Open)
|
||||
Command.Connection.Open();
|
||||
}
|
||||
|
||||
protected internal void CloseConnection()
|
||||
{
|
||||
OnClosingConnection();
|
||||
|
||||
Command.Parameters.Clear();
|
||||
Command.CommandText = string.Empty;
|
||||
|
||||
if (Command.Transaction == null)
|
||||
Command.Connection.Close(); // Only close if no transaction is present
|
||||
|
||||
UnbindEvents();
|
||||
}
|
||||
|
||||
private void WriteToTraceLog()
|
||||
{
|
||||
if (MapRepository.Instance.EnableTraceLogging)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==== Begin Query Trace ====");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("QUERY TYPE:");
|
||||
sb.AppendLine(Command.CommandType.ToString());
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("QUERY TEXT:");
|
||||
sb.AppendLine(Command.CommandText);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("PARAMETERS:");
|
||||
foreach (IDbDataParameter p in Parameters)
|
||||
{
|
||||
object val = (p.Value != null && p.Value is string) ? string.Format("\"{0}\"", p.Value) : p.Value;
|
||||
sb.AppendFormat("{0} = [{1}]", p.ParameterName, val ?? "NULL").AppendLine();
|
||||
}
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==== End Query Trace ====");
|
||||
sb.AppendLine();
|
||||
|
||||
Trace.Write(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UnbindEvents()
|
||||
{
|
||||
OpeningConnection = null;
|
||||
ClosingConnection = null;
|
||||
}
|
||||
|
||||
public void BeginTransaction()
|
||||
{
|
||||
OpenConnection();
|
||||
DbTransaction trans = Command.Connection.BeginTransaction();
|
||||
Command.Transaction = trans;
|
||||
}
|
||||
|
||||
public void RollBack()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Command.Transaction != null)
|
||||
Command.Transaction.Rollback();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Command.Connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Command.Transaction != null)
|
||||
Command.Transaction.Commit();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Command.Connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - IDisposable Members -
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this); // In case a derived class implements a finalizer
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (Command.Transaction != null)
|
||||
{
|
||||
Command.Transaction.Dispose();
|
||||
Command.Transaction = null;
|
||||
}
|
||||
|
||||
if (Command.Connection != null)
|
||||
{
|
||||
Command.Connection.Dispose();
|
||||
Command.Connection = null;
|
||||
}
|
||||
|
||||
if (Command != null)
|
||||
{
|
||||
Command.Dispose();
|
||||
_command = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
22
src/Marr.Data/DataMappingException.cs
Normal file
22
src/Marr.Data/DataMappingException.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
public class DataMappingException : Exception
|
||||
{
|
||||
public DataMappingException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public DataMappingException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DataMappingException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
408
src/Marr.Data/EntityGraph.cs
Normal file
408
src/Marr.Data/EntityGraph.cs
Normal file
|
@ -0,0 +1,408 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds metadata about an object graph that is being queried and eagerly loaded.
|
||||
/// Contains all metadata needed to instantiate the object and fill it with data from a DataReader.
|
||||
/// Does not iterate through lazy loaded child relationships.
|
||||
/// </summary>
|
||||
internal class EntityGraph : IEnumerable<EntityGraph>
|
||||
{
|
||||
private MapRepository _repos;
|
||||
private EntityGraph _parent;
|
||||
private Type _entityType;
|
||||
private Relationship _relationship;
|
||||
private ColumnMapCollection _columns;
|
||||
private RelationshipCollection _relationships;
|
||||
private List<EntityGraph> _children;
|
||||
private object _entity;
|
||||
private GroupingKeyCollection _groupingKeyColumns;
|
||||
private Dictionary<string, EntityReference> _entityReferences;
|
||||
|
||||
internal IList RootList { get; private set; }
|
||||
internal bool IsParentReference { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Recursively builds an entity graph of the given parent type.
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
public EntityGraph(Type entityType, IList rootList)
|
||||
: this(entityType, null, null) // Recursively constructs hierarchy
|
||||
{
|
||||
RootList = rootList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively builds entity graph hierarchy.
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="relationship"></param>
|
||||
private EntityGraph(Type entityType, EntityGraph parent, Relationship relationship)
|
||||
{
|
||||
_repos = MapRepository.Instance;
|
||||
|
||||
_entityType = entityType;
|
||||
_parent = parent;
|
||||
_relationship = relationship;
|
||||
IsParentReference = !IsRoot && AnyParentsAreOfType(entityType);
|
||||
if (!IsParentReference)
|
||||
{
|
||||
_columns = _repos.GetColumns(entityType);
|
||||
}
|
||||
|
||||
_relationships = _repos.GetRelationships(entityType);
|
||||
_children = new List<EntityGraph>();
|
||||
Member = relationship != null ? relationship.Member : null;
|
||||
_entityReferences = new Dictionary<string, EntityReference>();
|
||||
|
||||
if (IsParentReference)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new EntityGraph for each child relationship that is not lazy loaded
|
||||
foreach (Relationship childRelationship in Relationships)
|
||||
{
|
||||
if (!childRelationship.IsLazyLoaded)
|
||||
{
|
||||
_children.Add(new EntityGraph(childRelationship.RelationshipInfo.EntityType, this, childRelationship));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MemberInfo Member { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this EntityGraph.
|
||||
/// </summary>
|
||||
public EntityGraph Parent
|
||||
{
|
||||
get
|
||||
{
|
||||
return _parent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Type of this EntityGraph.
|
||||
/// </summary>
|
||||
public Type EntityType
|
||||
{
|
||||
get { return _entityType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean than indicates whether this entity is the root node in the graph.
|
||||
/// </summary>
|
||||
public bool IsRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
return _parent == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean that indicates whether this entity is a child.
|
||||
/// </summary>
|
||||
public bool IsChild
|
||||
{
|
||||
get
|
||||
{
|
||||
return _parent != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the columns mapped to this entity.
|
||||
/// </summary>
|
||||
public ColumnMapCollection Columns
|
||||
{
|
||||
get { return _columns; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relationships mapped to this entity.
|
||||
/// </summary>
|
||||
public RelationshipCollection Relationships
|
||||
{
|
||||
get { return _relationships; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of EntityGraph objects that hold metadata about the child entities that will be loaded.
|
||||
/// </summary>
|
||||
public List<EntityGraph> Children
|
||||
{
|
||||
get { return _children; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entity to the appropriate place in the object graph.
|
||||
/// </summary>
|
||||
/// <param name="entityInstance"></param>
|
||||
public void AddEntity(object entityInstance)
|
||||
{
|
||||
_entity = entityInstance;
|
||||
|
||||
// Add newly created entityInstance to list (Many) or set it to field (One)
|
||||
if (IsRoot)
|
||||
{
|
||||
RootList.Add(entityInstance);
|
||||
}
|
||||
else if (_relationship.RelationshipInfo.RelationType == RelationshipTypes.Many)
|
||||
{
|
||||
var list = _parent._entityReferences[_parent.GroupingKeyColumns.GroupingKey]
|
||||
.ChildLists[_relationship.Member.Name];
|
||||
|
||||
list.Add(entityInstance);
|
||||
}
|
||||
else // RelationTypes.One
|
||||
{
|
||||
_relationship.Setter(_parent._entity, entityInstance);
|
||||
}
|
||||
|
||||
EntityReference entityRef = new EntityReference(entityInstance);
|
||||
_entityReferences.Add(GroupingKeyColumns.GroupingKey, entityRef);
|
||||
|
||||
InitOneToManyChildLists(entityRef);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a previously loaded parent entity and then sets that reference to the mapped Relationship property.
|
||||
/// </summary>
|
||||
public void AddParentReference()
|
||||
{
|
||||
var parentReference = FindParentReference();
|
||||
_relationship.Setter(_parent._entity, parentReference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates the values of the GroupingKeys property and compares them
|
||||
/// against the LastKeyGroup property. Returns true if the values are different,
|
||||
/// or false if the values are the same.
|
||||
/// The currently concatenated keys are saved in the LastKeyGroup property.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsNewGroup(DbDataReader reader)
|
||||
{
|
||||
bool isNewGroup = false;
|
||||
|
||||
// Get primary keys from parent entity and any one-to-one child entites
|
||||
GroupingKeyCollection groupingKeyColumns = GroupingKeyColumns;
|
||||
|
||||
// Concatenate column values
|
||||
KeyGroupInfo keyGroupInfo = groupingKeyColumns.CreateGroupingKey(reader);
|
||||
|
||||
if (!keyGroupInfo.HasNullKey && !_entityReferences.ContainsKey(keyGroupInfo.GroupingKey))
|
||||
{
|
||||
isNewGroup = true;
|
||||
}
|
||||
|
||||
return isNewGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GroupingKeys for this entity.
|
||||
/// GroupingKeys determine when to create and add a new entity to the graph.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A simple entity with no relationships will return only its PrimaryKey columns.
|
||||
/// A parent entity with one-to-one child relationships will include its own PrimaryKeys,
|
||||
/// and it will recursively traverse all Children with one-to-one relationships and add their PrimaryKeys.
|
||||
/// A child entity that has a one-to-one relationship with its parent will use the same
|
||||
/// GroupingKeys already defined by its parent.
|
||||
/// </remarks>
|
||||
public GroupingKeyCollection GroupingKeyColumns
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_groupingKeyColumns == null)
|
||||
_groupingKeyColumns = GetGroupingKeyColumns();
|
||||
|
||||
return _groupingKeyColumns;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AnyParentsAreOfType(Type type)
|
||||
{
|
||||
EntityGraph parent = _parent;
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent._entityType == type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
parent = parent._parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private object FindParentReference()
|
||||
{
|
||||
var parent = Parent.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent._entityType == _relationship.MemberType)
|
||||
{
|
||||
return parent._entity;
|
||||
}
|
||||
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the owning lists on many-to-many Children.
|
||||
/// </summary>
|
||||
/// <param name="entityInstance"></param>
|
||||
private void InitOneToManyChildLists(EntityReference entityRef)
|
||||
{
|
||||
// Get a reference to the parent's the childrens' OwningLists to the parent entity
|
||||
for (int i = 0; i < Relationships.Count; i++)
|
||||
{
|
||||
Relationship relationship = Relationships[i];
|
||||
if (relationship.RelationshipInfo.RelationType == RelationshipTypes.Many)
|
||||
{
|
||||
try
|
||||
{
|
||||
IList list = (IList)_repos.ReflectionStrategy.CreateInstance(relationship.MemberType);
|
||||
relationship.Setter(entityRef.Entity, list);
|
||||
|
||||
// Save a reference to each 1-M list
|
||||
entityRef.AddChildList(relationship.Member.Name, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DataMappingException(
|
||||
string.Format("{0}.{1} is a \"Many\" relationship type so it must derive from IList.",
|
||||
entityRef.Entity.GetType().Name, relationship.Member.Name),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of keys to group by.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When converting an unnormalized set of data from a database view,
|
||||
/// a new entity is only created when the grouping keys have changed.
|
||||
/// NOTE: This behavior works on the assumption that the view result set
|
||||
/// has been sorted by the root entity primary key(s), followed by the
|
||||
/// child entity primary keys.
|
||||
/// </remarks>
|
||||
/// <returns></returns>
|
||||
private GroupingKeyCollection GetGroupingKeyColumns()
|
||||
{
|
||||
// Get primary keys for this parent entity
|
||||
GroupingKeyCollection groupingKeyColumns = new GroupingKeyCollection();
|
||||
groupingKeyColumns.PrimaryKeys.AddRange(Columns.PrimaryKeys);
|
||||
|
||||
// The following conditions should fail with an exception:
|
||||
// 1) Any parent entity (entity with children) must have at least one PK specified or an exception will be thrown
|
||||
// 2) All 1-M relationship entities must have at least one PK specified
|
||||
// * Only 1-1 entities with no children are allowed to have 0 PKs specified.
|
||||
if ((groupingKeyColumns.PrimaryKeys.Count == 0 && _children.Count > 0) ||
|
||||
(groupingKeyColumns.PrimaryKeys.Count == 0 && !IsRoot && _relationship.RelationshipInfo.RelationType == RelationshipTypes.Many))
|
||||
throw new MissingPrimaryKeyException(string.Format("There are no primary key mappings defined for the following entity: '{0}'.", EntityType.Name));
|
||||
|
||||
// Add parent's keys
|
||||
if (IsChild)
|
||||
groupingKeyColumns.ParentPrimaryKeys.AddRange(Parent.GroupingKeyColumns);
|
||||
|
||||
return groupingKeyColumns;
|
||||
}
|
||||
|
||||
#region IEnumerable<EntityGraph> Members
|
||||
|
||||
public IEnumerator<EntityGraph> GetEnumerator()
|
||||
{
|
||||
return TraverseGraph(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively traverses through every entity in the EntityGraph.
|
||||
/// </summary>
|
||||
/// <param name="entityGraph"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerator<EntityGraph> TraverseGraph(EntityGraph entityGraph)
|
||||
{
|
||||
Stack<EntityGraph> stack = new Stack<EntityGraph>();
|
||||
stack.Push(entityGraph);
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
EntityGraph node = stack.Pop();
|
||||
yield return node;
|
||||
|
||||
foreach (EntityGraph childGraph in node.Children)
|
||||
{
|
||||
stack.Push(childGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
public struct KeyGroupInfo
|
||||
{
|
||||
private string _groupingKey;
|
||||
private bool _hasNullKey;
|
||||
|
||||
public KeyGroupInfo(string groupingKey, bool hasNullKey)
|
||||
{
|
||||
_groupingKey = groupingKey;
|
||||
_hasNullKey = hasNullKey;
|
||||
}
|
||||
|
||||
public string GroupingKey
|
||||
{
|
||||
get { return _groupingKey; }
|
||||
}
|
||||
|
||||
public bool HasNullKey
|
||||
{
|
||||
get { return _hasNullKey; }
|
||||
}
|
||||
}
|
34
src/Marr.Data/EntityMerger.cs
Normal file
34
src/Marr.Data/EntityMerger.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// This utility class allows you to join two existing entity collections.
|
||||
/// </summary>
|
||||
public class EntityMerger
|
||||
{
|
||||
/// <summary>
|
||||
/// Joines to existing entity collections.
|
||||
/// </summary>
|
||||
/// <typeparam name="TParent">The parent entity type.</typeparam>
|
||||
/// <typeparam name="TChild">The child entity type.</typeparam>
|
||||
/// <param name="parentList">The parent entities.</param>
|
||||
/// <param name="childList">The child entities</param>
|
||||
/// <param name="relationship">A predicate that defines the relationship between the parent and child entities. Returns true if they are related.</param>
|
||||
/// <param name="mergeAction">An action that adds a related child to the parent.</param>
|
||||
public static void Merge<TParent, TChild>(IEnumerable<TParent> parentList, IEnumerable<TChild> childList, Func<TParent, TChild, bool> relationship, Action<TParent, TChild> mergeAction)
|
||||
{
|
||||
foreach (TParent parent in parentList)
|
||||
{
|
||||
foreach (TChild child in childList)
|
||||
{
|
||||
if (relationship(parent, child))
|
||||
{
|
||||
mergeAction(parent, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
src/Marr.Data/EntityReference.cs
Normal file
28
src/Marr.Data/EntityReference.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores an entity along with all of its 1-M IList references.
|
||||
/// </summary>
|
||||
public class EntityReference
|
||||
{
|
||||
public EntityReference(object entity)
|
||||
{
|
||||
Entity = entity;
|
||||
ChildLists = new Dictionary<string, IList>();
|
||||
}
|
||||
|
||||
public object Entity { get; private set; }
|
||||
public Dictionary<string, IList> ChildLists { get; private set; }
|
||||
|
||||
public void AddChildList(string memberName, IList list)
|
||||
{
|
||||
if (ChildLists.ContainsKey(memberName))
|
||||
ChildLists[memberName] = list;
|
||||
else
|
||||
ChildLists.Add(memberName, list);
|
||||
}
|
||||
}
|
||||
}
|
19
src/Marr.Data/ExtensionMethods.cs
Normal file
19
src/Marr.Data/ExtensionMethods.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Data.Common;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains public extension methods.
|
||||
/// </summary>
|
||||
public static class ExtensionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value from a DbDataReader by using the column name;
|
||||
/// </summary>
|
||||
public static T GetValue<T>(this DbDataReader reader, string columnName)
|
||||
{
|
||||
int ordinal = reader.GetOrdinal(columnName);
|
||||
return (T)reader.GetValue(ordinal);
|
||||
}
|
||||
}
|
||||
}
|
85
src/Marr.Data/GroupingKeyCollection.cs
Normal file
85
src/Marr.Data/GroupingKeyCollection.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
public class GroupingKeyCollection : IEnumerable<ColumnMap>
|
||||
{
|
||||
public GroupingKeyCollection()
|
||||
{
|
||||
PrimaryKeys = new ColumnMapCollection();
|
||||
ParentPrimaryKeys = new ColumnMapCollection();
|
||||
}
|
||||
|
||||
public ColumnMapCollection PrimaryKeys { get; private set; }
|
||||
public ColumnMapCollection ParentPrimaryKeys { get; private set; }
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return PrimaryKeys.Count + ParentPrimaryKeys.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PK values that define this entity in the graph.
|
||||
/// </summary>
|
||||
internal string GroupingKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a concatented string containing the primary key values of the current record.
|
||||
/// </summary>
|
||||
/// <param name="reader">The open data reader.</param>
|
||||
/// <returns>Returns the primary key value(s) as a string.</returns>
|
||||
internal KeyGroupInfo CreateGroupingKey(DbDataReader reader)
|
||||
{
|
||||
StringBuilder pkValues = new StringBuilder();
|
||||
bool hasNullValue = false;
|
||||
|
||||
foreach (ColumnMap pkColumn in this)
|
||||
{
|
||||
object pkValue = reader[pkColumn.ColumnInfo.GetColumName(true)];
|
||||
|
||||
if (pkValue == DBNull.Value)
|
||||
hasNullValue = true;
|
||||
|
||||
pkValues.Append(pkValue.ToString());
|
||||
}
|
||||
|
||||
GroupingKey = pkValues.ToString();
|
||||
|
||||
return new KeyGroupInfo(GroupingKey, hasNullValue);
|
||||
}
|
||||
|
||||
#region IEnumerable<ColumnMap> Members
|
||||
|
||||
public IEnumerator<ColumnMap> GetEnumerator()
|
||||
{
|
||||
foreach (ColumnMap map in ParentPrimaryKeys)
|
||||
{
|
||||
yield return map;
|
||||
}
|
||||
|
||||
foreach (ColumnMap map in PrimaryKeys)
|
||||
{
|
||||
yield return map;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
219
src/Marr.Data/IDataMapper.cs
Normal file
219
src/Marr.Data/IDataMapper.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data.Parameters;
|
||||
using System.Linq.Expressions;
|
||||
using Marr.Data.QGen;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
public interface IDataMapper : IDisposable
|
||||
{
|
||||
#region - Contructor, Members -
|
||||
|
||||
string ConnectionString { get; }
|
||||
DbProviderFactory ProviderFactory { get; }
|
||||
DbCommand Command { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines whether the DataMapper will
|
||||
/// use a stored procedure or a sql text command to access
|
||||
/// the database. The default is stored procedure.
|
||||
/// </summary>
|
||||
SqlModes SqlMode { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Update -
|
||||
|
||||
UpdateQueryBuilder<T> Update<T>();
|
||||
int Update<T>(T entity, Expression<Func<T, bool>> filter);
|
||||
int Update<T>(string tableName, T entity, Expression<Func<T, bool>> filter);
|
||||
int Update<T>(T entity, string sql);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Insert -
|
||||
|
||||
/// <summary>
|
||||
/// Creates an InsertQueryBuilder that allows you to build an insert statement.
|
||||
/// This method gives you the flexibility to manually configure all options of your insert statement.
|
||||
/// Note: You must manually call the Execute() chaining method to run the query.
|
||||
/// </summary>
|
||||
InsertQueryBuilder<T> Insert<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Generates and executes an insert query for the given entity.
|
||||
/// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
|
||||
/// and if an identity query has been implemented for your current database dialect.
|
||||
/// </summary>
|
||||
object Insert<T>(T entity);
|
||||
|
||||
/// <summary>
|
||||
/// Generates and executes an insert query for the given entity.
|
||||
/// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
|
||||
/// and if an identity query has been implemented for your current database dialect.
|
||||
/// </summary>
|
||||
object Insert<T>(string tableName, T entity);
|
||||
|
||||
/// <summary>
|
||||
/// Executes an insert query for the given entity using the given sql insert statement.
|
||||
/// This overload will automatically run an identity query if you have mapped an auto-incrementing column,
|
||||
/// and if an identity query has been implemented for your current database dialect.
|
||||
/// </summary>
|
||||
object Insert<T>(T entity, string sql);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Delete -
|
||||
|
||||
int Delete<T>(Expression<Func<T, bool>> filter);
|
||||
int Delete<T>(string tableName, Expression<Func<T, bool>> filter);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Connections / Transactions -
|
||||
|
||||
void BeginTransaction();
|
||||
void RollBack();
|
||||
void Commit();
|
||||
event EventHandler OpeningConnection;
|
||||
|
||||
#endregion
|
||||
|
||||
#region - ExecuteScalar, ExecuteNonQuery, ExecuteReader -
|
||||
|
||||
/// <summary>
|
||||
/// Executes a non query that returns an integer.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <returns>An integer value</returns>
|
||||
int ExecuteNonQuery(string sql);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a stored procedure that returns a scalar value.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <returns>A scalar value</returns>
|
||||
object ExecuteScalar(string sql);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a DataReader that can be controlled using a Func delegate.
|
||||
/// (Note that reader.Read() will be called automatically).
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type that will be return in the result set.</typeparam>
|
||||
/// <param name="sql">The sql statement that will be executed.</param>
|
||||
/// <param name="func">The function that will build the the TResult set.</param>
|
||||
/// <returns>An IEnumerable of TResult.</returns>
|
||||
IEnumerable<TResult> ExecuteReader<TResult>(string sql, Func<DbDataReader, TResult> func);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a DataReader that can be controlled using an Action delegate.
|
||||
/// </summary>
|
||||
/// <param name="sql">The sql statement that will be executed.</param>
|
||||
/// <param name="action">The delegate that will work with the result set.</param>
|
||||
void ExecuteReader(string sql, Action<DbDataReader> action);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - DataSets -
|
||||
|
||||
DataSet GetDataSet(string sql);
|
||||
DataSet GetDataSet(string sql, DataSet ds, string tableName);
|
||||
DataTable GetDataTable(string sql, DataTable dt, string tableName);
|
||||
DataTable GetDataTable(string sql);
|
||||
int InsertDataTable(DataTable table, string insertSP);
|
||||
int InsertDataTable(DataTable table, string insertSP, UpdateRowSource updateRowSource);
|
||||
int UpdateDataSet(DataSet ds, string updateSP);
|
||||
int DeleteDataTable(DataTable dt, string deleteSP);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Parameters -
|
||||
|
||||
DbParameterCollection Parameters { get; }
|
||||
ParameterChainMethods AddParameter(string name, object value);
|
||||
IDbDataParameter AddParameter(IDbDataParameter parameter);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Find -
|
||||
|
||||
/// <summary>
|
||||
/// Returns an entity of type T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of entity that is to be instantiated and loaded with values.</typeparam>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <returns>An instantiated and loaded entity of type T.</returns>
|
||||
T Find<T>(string sql);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an entity of type T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of entity that is to be instantiated and loaded with values.</typeparam>
|
||||
/// <param name="sql">The SQL command to execute.</param>
|
||||
/// <param name="ent">A previously instantiated entity that will be loaded with values.</param>
|
||||
/// <returns>An instantiated and loaded entity of type T.</returns>
|
||||
T Find<T>(string sql, T ent);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Query -
|
||||
|
||||
/// <summary>
|
||||
/// Creates a QueryBuilder that allows you to build a query.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object that will be queried.</typeparam>
|
||||
/// <returns>Returns a QueryBuilder of T.</returns>
|
||||
QueryBuilder<T> Query<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the results of a query.
|
||||
/// Uses a List of type T to return the data.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object that will be queried.</typeparam>
|
||||
/// <returns>Returns a list of the specified type.</returns>
|
||||
List<T> Query<T>(string sql);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the results of a query or a stored procedure.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object that will be queried.</typeparam>
|
||||
/// <param name="sql">The sql query or stored procedure name to run.</param>
|
||||
/// <param name="entityList">A previously instantiated list to populate.</param>
|
||||
/// <returns>Returns a list of the specified type.</returns>
|
||||
ICollection<T> Query<T>(string sql, ICollection<T> entityList);
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Query to Graph -
|
||||
|
||||
/// <summary>
|
||||
/// Runs a query and then tries to instantiate the entire object graph with entites.
|
||||
/// </summary>
|
||||
List<T> QueryToGraph<T>(string sql);
|
||||
|
||||
/// <summary>
|
||||
/// Runs a query and then tries to instantiate the entire object graph with entites.
|
||||
/// </summary>
|
||||
ICollection<T> QueryToGraph<T>(string sql, ICollection<T> entityList);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
131
src/Marr.Data/LazyLoaded.cs
Normal file
131
src/Marr.Data/LazyLoaded.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
public interface ILazyLoaded : ICloneable
|
||||
{
|
||||
bool IsLoaded { get; }
|
||||
void Prepare(Func<IDataMapper> dataMapperFactory, object parent);
|
||||
void LazyLoad();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows a field to be lazy loaded.
|
||||
/// </summary>
|
||||
/// <typeparam name="TChild"></typeparam>
|
||||
public class LazyLoaded<TChild> : ILazyLoaded
|
||||
{
|
||||
protected TChild _value;
|
||||
|
||||
public LazyLoaded()
|
||||
{
|
||||
}
|
||||
|
||||
public LazyLoaded(TChild val)
|
||||
{
|
||||
_value = val;
|
||||
IsLoaded = true;
|
||||
}
|
||||
|
||||
public TChild Value
|
||||
{
|
||||
get
|
||||
{
|
||||
LazyLoad();
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoaded { get; protected set; }
|
||||
|
||||
public virtual void Prepare(Func<IDataMapper> dataMapperFactory, object parent)
|
||||
{ }
|
||||
|
||||
public virtual void LazyLoad()
|
||||
{ }
|
||||
|
||||
public static implicit operator LazyLoaded<TChild>(TChild val)
|
||||
{
|
||||
return new LazyLoaded<TChild>(val);
|
||||
}
|
||||
|
||||
public static implicit operator TChild(LazyLoaded<TChild> lazy)
|
||||
{
|
||||
return lazy.Value;
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return MemberwiseClone();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the lazy loading proxy.
|
||||
/// </summary>
|
||||
/// <typeparam name="TParent">The parent entity that contains the lazy loaded entity.</typeparam>
|
||||
/// <typeparam name="TChild">The child entity that is being lazy loaded.</typeparam>
|
||||
internal class LazyLoaded<TParent, TChild> : LazyLoaded<TChild>
|
||||
{
|
||||
private TParent _parent;
|
||||
private Func<IDataMapper> _dbMapperFactory;
|
||||
|
||||
private readonly Func<IDataMapper, TParent, TChild> _query;
|
||||
private readonly Func<TParent, bool> _condition;
|
||||
|
||||
internal LazyLoaded(Func<IDataMapper, TParent, TChild> query, Func<TParent, bool> condition = null)
|
||||
{
|
||||
_query = query;
|
||||
_condition = condition;
|
||||
}
|
||||
|
||||
public LazyLoaded(TChild val)
|
||||
: base(val)
|
||||
{
|
||||
_value = val;
|
||||
IsLoaded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The second part of the initialization happens when the entity is being built.
|
||||
/// </summary>
|
||||
/// <param name="dataMapperFactory">Knows how to instantiate a new IDataMapper.</param>
|
||||
/// <param name="parent">The parent entity.</param>
|
||||
public override void Prepare(Func<IDataMapper> dataMapperFactory, object parent)
|
||||
{
|
||||
_dbMapperFactory = dataMapperFactory;
|
||||
_parent = (TParent)parent;
|
||||
}
|
||||
|
||||
public override void LazyLoad()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
if (_condition != null && _condition(_parent))
|
||||
{
|
||||
using (IDataMapper db = _dbMapperFactory())
|
||||
{
|
||||
_value = _query(db, _parent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_value = default(TChild);
|
||||
}
|
||||
|
||||
IsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator LazyLoaded<TParent, TChild>(TChild val)
|
||||
{
|
||||
return new LazyLoaded<TParent, TChild>(val);
|
||||
}
|
||||
|
||||
public static implicit operator TChild(LazyLoaded<TParent, TChild> lazy)
|
||||
{
|
||||
return lazy.Value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
250
src/Marr.Data/MapRepository.cs
Normal file
250
src/Marr.Data/MapRepository.cs
Normal file
|
@ -0,0 +1,250 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Parameters;
|
||||
using Marr.Data.Mapping;
|
||||
using Marr.Data.Mapping.Strategies;
|
||||
using Marr.Data.Reflection;
|
||||
|
||||
namespace Marr.Data
|
||||
{
|
||||
public class MapRepository
|
||||
{
|
||||
private static readonly object _tablesLock = new object();
|
||||
private static readonly object _columnsLock = new object();
|
||||
private static readonly object _relationshipsLock = new object();
|
||||
|
||||
private IDbTypeBuilder _dbTypeBuilder;
|
||||
private Dictionary<Type, IMapStrategy> _columnMapStrategies;
|
||||
|
||||
internal Dictionary<Type, string> Tables { get; set; }
|
||||
internal Dictionary<Type, ColumnMapCollection> Columns { get; set; }
|
||||
internal Dictionary<Type, RelationshipCollection> Relationships { get; set; }
|
||||
public Dictionary<Type, IConverter> TypeConverters { get; private set; }
|
||||
|
||||
// Explicit static constructor to tell C# compiler
|
||||
// not to mark type as beforefieldinit
|
||||
static MapRepository()
|
||||
{ }
|
||||
|
||||
private MapRepository()
|
||||
{
|
||||
Tables = new Dictionary<Type, string>();
|
||||
Columns = new Dictionary<Type, ColumnMapCollection>();
|
||||
Relationships = new Dictionary<Type, RelationshipCollection>();
|
||||
TypeConverters = new Dictionary<Type, IConverter>();
|
||||
|
||||
// Register a default IReflectionStrategy
|
||||
ReflectionStrategy = new SimpleReflectionStrategy();
|
||||
|
||||
// Register a default type converter for Enums
|
||||
TypeConverters.Add(typeof(Enum), new EnumStringConverter());
|
||||
|
||||
// Register a default IDbTypeBuilder
|
||||
_dbTypeBuilder = new DbTypeBuilder();
|
||||
|
||||
_columnMapStrategies = new Dictionary<Type, IMapStrategy>();
|
||||
RegisterDefaultMapStrategy(new AttributeMapStrategy());
|
||||
|
||||
EnableTraceLogging = false;
|
||||
}
|
||||
|
||||
private readonly static MapRepository _instance = new MapRepository();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the singleton MapRepository.
|
||||
/// </summary>
|
||||
public static MapRepository Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean that determines whether debug information should be written to the trace log.
|
||||
/// The default is false.
|
||||
/// </summary>
|
||||
public bool EnableTraceLogging { get; set; }
|
||||
|
||||
#region - Column Map Strategies -
|
||||
|
||||
public void RegisterDefaultMapStrategy(IMapStrategy strategy)
|
||||
{
|
||||
RegisterMapStrategy(typeof(object), strategy);
|
||||
}
|
||||
|
||||
public void RegisterMapStrategy(Type entityType, IMapStrategy strategy)
|
||||
{
|
||||
if (_columnMapStrategies.ContainsKey(entityType))
|
||||
_columnMapStrategies[entityType] = strategy;
|
||||
else
|
||||
_columnMapStrategies.Add(entityType, strategy);
|
||||
}
|
||||
|
||||
private IMapStrategy GetMapStrategy(Type entityType)
|
||||
{
|
||||
if (_columnMapStrategies.ContainsKey(entityType))
|
||||
{
|
||||
// Return entity specific column map strategy
|
||||
return _columnMapStrategies[entityType];
|
||||
}
|
||||
// Return the default column map strategy
|
||||
return _columnMapStrategies[typeof(object)];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Table repository -
|
||||
|
||||
internal string GetTableName(Type entityType)
|
||||
{
|
||||
if (!Tables.ContainsKey(entityType))
|
||||
{
|
||||
lock (_tablesLock)
|
||||
{
|
||||
if (!Tables.ContainsKey(entityType))
|
||||
{
|
||||
string tableName = GetMapStrategy(entityType).MapTable(entityType);
|
||||
Tables.Add(entityType, tableName);
|
||||
return tableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Tables[entityType];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Columns repository -
|
||||
|
||||
public ColumnMapCollection GetColumns(Type entityType)
|
||||
{
|
||||
if (!Columns.ContainsKey(entityType))
|
||||
{
|
||||
lock (_columnsLock)
|
||||
{
|
||||
if (!Columns.ContainsKey(entityType))
|
||||
{
|
||||
ColumnMapCollection columnMaps = GetMapStrategy(entityType).MapColumns(entityType);
|
||||
Columns.Add(entityType, columnMaps);
|
||||
return columnMaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Columns[entityType];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Relationships repository -
|
||||
|
||||
public RelationshipCollection GetRelationships(Type type)
|
||||
{
|
||||
if (!Relationships.ContainsKey(type))
|
||||
{
|
||||
lock (_relationshipsLock)
|
||||
{
|
||||
if (!Relationships.ContainsKey(type))
|
||||
{
|
||||
RelationshipCollection relationships = GetMapStrategy(type).MapRelationships(type);
|
||||
Relationships.Add(type, relationships);
|
||||
return relationships;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Relationships[type];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Reflection Strategy -
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reflection strategy that the DataMapper will use to load entities.
|
||||
/// By default the CachedReflector will be used, which provides a performance increase over the SimpleReflector.
|
||||
/// However, the SimpleReflector can be used in Medium Trust enviroments.
|
||||
/// </summary>
|
||||
///
|
||||
public IReflectionStrategy ReflectionStrategy { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Type Converters -
|
||||
|
||||
/// <summary>
|
||||
/// Registers a converter for a given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The CLR data type that will be converted.</param>
|
||||
/// <param name="converter">An IConverter object that will handle the data conversion.</param>
|
||||
public void RegisterTypeConverter(Type type, IConverter converter)
|
||||
{
|
||||
TypeConverters[type] = converter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for a type converter (if one exists).
|
||||
/// 1) Checks for a converter registered for the current columns data type.
|
||||
/// 2) Checks to see if a converter is registered for all enums (type of Enum) if the current column is an enum.
|
||||
/// 3) Checks to see if a converter is registered for all objects (type of Object).
|
||||
/// </summary>
|
||||
/// <param name="dataMap">The current data map.</param>
|
||||
/// <returns>Returns an IConverter object or null if one does not exist.</returns>
|
||||
internal IConverter GetConverter(Type dataType)
|
||||
{
|
||||
if (TypeConverters.ContainsKey(dataType))
|
||||
{
|
||||
// User registered type converter
|
||||
return TypeConverters[dataType];
|
||||
}
|
||||
if (TypeConverters.ContainsKey(typeof(Enum)) && dataType.IsEnum)
|
||||
{
|
||||
// A converter is registered to handled enums
|
||||
return TypeConverters[typeof(Enum)];
|
||||
}
|
||||
if (TypeConverters.ContainsKey(typeof(object)))
|
||||
{
|
||||
// User registered default converter
|
||||
return TypeConverters[typeof(object)];
|
||||
}
|
||||
// No conversion
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - DbTypeBuilder -
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the IDBTypeBuilder that is responsible for converting parameter DbTypes based on the parameter value.
|
||||
/// Defaults to use the DbTypeBuilder.
|
||||
/// You can replace this with a more specific builder if you want more control over the way the parameter types are set.
|
||||
/// </summary>
|
||||
public IDbTypeBuilder DbTypeBuilder
|
||||
{
|
||||
get { return _dbTypeBuilder; }
|
||||
set { _dbTypeBuilder = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
115
src/Marr.Data/Mapping/ColumnAttribute.cs
Normal file
115
src/Marr.Data/Mapping/ColumnAttribute.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class ColumnAttribute : Attribute, IColumnInfo
|
||||
{
|
||||
private string _name;
|
||||
private string _altName;
|
||||
private int _size = 0;
|
||||
private bool _isPrimaryKey;
|
||||
private bool _isAutoIncrement;
|
||||
private bool _returnValue;
|
||||
private ParameterDirection _paramDirection = ParameterDirection.Input;
|
||||
|
||||
public ColumnAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
public ColumnAttribute(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column name.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { _name = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an alternate name that is used to define this column in views.
|
||||
/// If an AltName is present, it is used in the QueryViewToObjectGraph method.
|
||||
/// If an AltName is not present, it will return the Name property value.
|
||||
/// </summary>
|
||||
public string AltName
|
||||
{
|
||||
get { return _altName; }
|
||||
set { _altName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column size.
|
||||
/// </summary>
|
||||
public int Size
|
||||
{
|
||||
get { return _size; }
|
||||
set { _size = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines whether the column is the Primary Key.
|
||||
/// </summary>
|
||||
public bool IsPrimaryKey
|
||||
{
|
||||
get { return _isPrimaryKey; }
|
||||
set { _isPrimaryKey = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines whether the column is an auto-incrementing seed column.
|
||||
/// </summary>
|
||||
public bool IsAutoIncrement
|
||||
{
|
||||
get { return _isAutoIncrement; }
|
||||
set { _isAutoIncrement = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines whether the column has a return value.
|
||||
/// </summary>
|
||||
public bool ReturnValue
|
||||
{
|
||||
get { return _returnValue; }
|
||||
set { _returnValue = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ParameterDirection.
|
||||
/// </summary>
|
||||
public ParameterDirection ParamDirection
|
||||
{
|
||||
get { return _paramDirection; }
|
||||
set { _paramDirection = value; }
|
||||
}
|
||||
|
||||
public string TryGetAltName()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(AltName) && AltName != Name)
|
||||
{
|
||||
return AltName;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
32
src/Marr.Data/Mapping/ColumnInfo.cs
Normal file
32
src/Marr.Data/Mapping/ColumnInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System.Data;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public class ColumnInfo : IColumnInfo
|
||||
{
|
||||
public ColumnInfo()
|
||||
{
|
||||
IsPrimaryKey = false;
|
||||
IsAutoIncrement = false;
|
||||
ReturnValue = false;
|
||||
ParamDirection = ParameterDirection.Input;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string AltName { get; set; }
|
||||
public int Size { get; set; }
|
||||
public bool IsPrimaryKey { get; set; }
|
||||
public bool IsAutoIncrement { get; set; }
|
||||
public bool ReturnValue { get; set; }
|
||||
public ParameterDirection ParamDirection { get; set; }
|
||||
|
||||
public string TryGetAltName()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(AltName) && AltName != Name)
|
||||
{
|
||||
return AltName;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
70
src/Marr.Data/Mapping/ColumnMap.cs
Normal file
70
src/Marr.Data/Mapping/ColumnMap.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Reflection;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the class fields and their associated stored proc parameters
|
||||
/// </summary>
|
||||
public class ColumnMap
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Creates a column map with an empty ColumnInfo object.
|
||||
/// </summary>
|
||||
/// <param name="member">The .net member that is being mapped.</param>
|
||||
public ColumnMap(MemberInfo member)
|
||||
: this(member, new ColumnInfo())
|
||||
{ }
|
||||
|
||||
public ColumnMap(MemberInfo member, IColumnInfo columnInfo)
|
||||
{
|
||||
FieldName = member.Name;
|
||||
ColumnInfo = columnInfo;
|
||||
|
||||
// If the column name is not specified, the field name will be used.
|
||||
if (string.IsNullOrEmpty(columnInfo.Name))
|
||||
columnInfo.Name = member.Name;
|
||||
|
||||
FieldType = ReflectionHelper.GetMemberType(member);
|
||||
Type paramNetType = FieldType;
|
||||
|
||||
Converter = MapRepository.Instance.GetConverter(FieldType);
|
||||
if (Converter != null)
|
||||
{
|
||||
paramNetType = Converter.DbType;
|
||||
}
|
||||
|
||||
DBType = MapRepository.Instance.DbTypeBuilder.GetDbType(paramNetType);
|
||||
|
||||
Getter = MapRepository.Instance.ReflectionStrategy.BuildGetter(member.DeclaringType, FieldName);
|
||||
Setter = MapRepository.Instance.ReflectionStrategy.BuildSetter(member.DeclaringType, FieldName);
|
||||
}
|
||||
|
||||
public string FieldName { get; set; }
|
||||
public Type FieldType { get; set; }
|
||||
public Enum DBType { get; set; }
|
||||
public IColumnInfo ColumnInfo { get; set; }
|
||||
|
||||
public GetterDelegate Getter { get; private set; }
|
||||
public SetterDelegate Setter { get; private set; }
|
||||
public IConverter Converter { get; private set; }
|
||||
}
|
||||
}
|
235
src/Marr.Data/Mapping/ColumnMapBuilder.cs
Normal file
235
src/Marr.Data/Mapping/ColumnMapBuilder.cs
Normal file
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Data;
|
||||
using Marr.Data.Mapping.Strategies;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// This class has fluent methods that are used to easily configure column mappings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class ColumnMapBuilder<TEntity>
|
||||
{
|
||||
private FluentMappings.MappingsFluentEntity<TEntity> _fluentEntity;
|
||||
private string _currentPropertyName;
|
||||
|
||||
public ColumnMapBuilder(FluentMappings.MappingsFluentEntity<TEntity> fluentEntity, ColumnMapCollection mappedColumns)
|
||||
{
|
||||
_fluentEntity = fluentEntity;
|
||||
MappedColumns = mappedColumns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of column mappings that are being configured.
|
||||
/// </summary>
|
||||
public ColumnMapCollection MappedColumns { get; private set; }
|
||||
|
||||
#region - Fluent Methods -
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the configurator to configure the given property.
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
public ColumnMapBuilder<TEntity> For(Expression<Func<TEntity, object>> property)
|
||||
{
|
||||
For(property.GetMemberName());
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the configurator to configure the given property or field.
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
public ColumnMapBuilder<TEntity> For(string propertyName)
|
||||
{
|
||||
_currentPropertyName = propertyName;
|
||||
|
||||
// Try to add the column map if it doesn't exist
|
||||
if (MappedColumns.GetByFieldName(_currentPropertyName) == null)
|
||||
{
|
||||
TryAddColumnMapForField(_currentPropertyName);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetPrimaryKey()
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetPrimaryKey(_currentPropertyName);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetPrimaryKey(string propertyName)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsPrimaryKey = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetAutoIncrement()
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetAutoIncrement(_currentPropertyName);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetAutoIncrement(string propertyName)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsAutoIncrement = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetColumnName(string columnName)
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetColumnName(_currentPropertyName, columnName);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetColumnName(string propertyName, string columnName)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.Name = columnName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetReturnValue()
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetReturnValue(_currentPropertyName);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetReturnValue(string propertyName)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.ReturnValue = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetSize(int size)
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetSize(_currentPropertyName, size);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetSize(string propertyName, int size)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.Size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetAltName(string altName)
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetAltName(_currentPropertyName, altName);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetAltName(string propertyName, string altName)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.AltName = altName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetParamDirection(ParameterDirection direction)
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
return SetParamDirection(_currentPropertyName, direction);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SetParamDirection(string propertyName, ParameterDirection direction)
|
||||
{
|
||||
MappedColumns.GetByFieldName(propertyName).ColumnInfo.ParamDirection = direction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> Ignore(Expression<Func<TEntity, object>> property)
|
||||
{
|
||||
string propertyName = property.GetMemberName();
|
||||
return Ignore(propertyName);
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> Ignore(string propertyName)
|
||||
{
|
||||
var columnMap = MappedColumns.GetByFieldName(propertyName);
|
||||
MappedColumns.Remove(columnMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> PrefixAltNames(string prefix)
|
||||
{
|
||||
MappedColumns.PrefixAltNames(prefix);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapBuilder<TEntity> SuffixAltNames(string suffix)
|
||||
{
|
||||
MappedColumns.SuffixAltNames(suffix);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentTables<TEntity> Tables
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fluentEntity == null)
|
||||
{
|
||||
throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
|
||||
}
|
||||
|
||||
return _fluentEntity.Table;
|
||||
}
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentRelationships<TEntity> Relationships
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fluentEntity == null)
|
||||
{
|
||||
throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
|
||||
}
|
||||
|
||||
return _fluentEntity.Relationships;
|
||||
}
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentEntity<TNewEntity> Entity<TNewEntity>()
|
||||
{
|
||||
return new FluentMappings.MappingsFluentEntity<TNewEntity>(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a ColumnMap for the given field name.
|
||||
/// Throws and exception if field cannot be found.
|
||||
/// </summary>
|
||||
private void TryAddColumnMapForField(string fieldName)
|
||||
{
|
||||
// Set strategy to filter for public or private fields
|
||||
ConventionMapStrategy strategy = new ConventionMapStrategy(false);
|
||||
|
||||
// Find the field that matches the given field name
|
||||
strategy.ColumnPredicate = mi => mi.Name == fieldName;
|
||||
ColumnMap columnMap = strategy.MapColumns(typeof(TEntity)).FirstOrDefault();
|
||||
|
||||
if (columnMap == null)
|
||||
{
|
||||
throw new DataMappingException(string.Format("Could not find the field '{0}' in '{1}'.",
|
||||
fieldName,
|
||||
typeof(TEntity).Name));
|
||||
}
|
||||
MappedColumns.Add(columnMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the "current" property has not been set.
|
||||
/// </summary>
|
||||
private void AssertCurrentPropertyIsSet()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_currentPropertyName))
|
||||
{
|
||||
throw new DataMappingException("A property must first be specified using the 'For' method.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
172
src/Marr.Data/Mapping/ColumnMapCollection.cs
Normal file
172
src/Marr.Data/Mapping/ColumnMapCollection.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains a list of column mappings.
|
||||
/// It also provides various methods to filter the collection.
|
||||
/// </summary>
|
||||
public class ColumnMapCollection : List<ColumnMap>
|
||||
{
|
||||
#region - Filters -
|
||||
|
||||
public ColumnMap GetByColumnName(string columnName)
|
||||
{
|
||||
return Find(m => m.ColumnInfo.Name == columnName);
|
||||
}
|
||||
|
||||
public ColumnMap GetByFieldName(string fieldName)
|
||||
{
|
||||
return Find(m => m.FieldName == fieldName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through all fields marked as return values.
|
||||
/// </summary>
|
||||
public IEnumerable<ColumnMap> ReturnValues
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (ColumnMap map in this)
|
||||
if (map.ColumnInfo.ReturnValue)
|
||||
yield return map;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through all fields that are not return values.
|
||||
/// </summary>
|
||||
public ColumnMapCollection NonReturnValues
|
||||
{
|
||||
get
|
||||
{
|
||||
ColumnMapCollection collection = new ColumnMapCollection();
|
||||
|
||||
foreach (ColumnMap map in this)
|
||||
if (!map.ColumnInfo.ReturnValue)
|
||||
collection.Add(map);
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through all fields marked as Output parameters or InputOutput.
|
||||
/// </summary>
|
||||
public IEnumerable<ColumnMap> OutputFields
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (ColumnMap map in this)
|
||||
if (map.ColumnInfo.ParamDirection == ParameterDirection.InputOutput ||
|
||||
map.ColumnInfo.ParamDirection == ParameterDirection.Output)
|
||||
yield return map;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates through all fields marked as primary keys.
|
||||
/// </summary>
|
||||
public ColumnMapCollection PrimaryKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
ColumnMapCollection keys = new ColumnMapCollection();
|
||||
foreach (ColumnMap map in this)
|
||||
if (map.ColumnInfo.IsPrimaryKey)
|
||||
keys.Add(map);
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses and orders the parameters from the query text.
|
||||
/// Filters the list of mapped columns to match the parameters found in the sql query.
|
||||
/// All parameters starting with the '@' or ':' symbol are matched and returned.
|
||||
/// </summary>
|
||||
/// <param name="command">The command and parameters that are being parsed.</param>
|
||||
/// <returns>A list of mapped columns that are present in the sql statement as parameters.</returns>
|
||||
public ColumnMapCollection OrderParameters(DbCommand command)
|
||||
{
|
||||
if (command.CommandType == CommandType.Text && Count > 0)
|
||||
{
|
||||
string commandTypeString = command.GetType().ToString();
|
||||
if (commandTypeString.Contains("Oracle") || commandTypeString.Contains("OleDb"))
|
||||
{
|
||||
ColumnMapCollection columns = new ColumnMapCollection();
|
||||
|
||||
// Find all @Parameters contained in the sql statement
|
||||
string paramPrefix = commandTypeString.Contains("Oracle") ? ":" : "@";
|
||||
string regexString = string.Format(@"{0}[\w-]+", paramPrefix);
|
||||
Regex regex = new Regex(regexString);
|
||||
foreach (Match m in regex.Matches(command.CommandText))
|
||||
{
|
||||
ColumnMap matchingColumn = Find(c => string.Concat(paramPrefix, c.ColumnInfo.Name.ToLower()) == m.Value.ToLower());
|
||||
if (matchingColumn != null)
|
||||
columns.Add(matchingColumn);
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Actions -
|
||||
|
||||
/// <summary>
|
||||
/// Set's each column's altname as the given prefix + the column name.
|
||||
/// Ex:
|
||||
/// Original column name: "ID"
|
||||
/// Passed in prefix: "PRODUCT_"
|
||||
/// Generated AltName: "PRODUCT_ID"
|
||||
/// </summary>
|
||||
/// <param name="prefix">The given prefix.</param>
|
||||
/// <returns></returns>
|
||||
public ColumnMapCollection PrefixAltNames(string prefix)
|
||||
{
|
||||
ForEach(c => c.ColumnInfo.AltName = c.ColumnInfo.Name.Insert(0, prefix));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set's each column's altname as the column name + the given prefix.
|
||||
/// Ex:
|
||||
/// Original column name: "ID"
|
||||
/// Passed in suffix: "_PRODUCT"
|
||||
/// Generated AltName: "ID_PRODUCT"
|
||||
/// </summary>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public ColumnMapCollection SuffixAltNames(string suffix)
|
||||
{
|
||||
ForEach(c => c.ColumnInfo.AltName = c.ColumnInfo.Name + suffix);
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
24
src/Marr.Data/Mapping/EnumConversionType.cs
Normal file
24
src/Marr.Data/Mapping/EnumConversionType.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public enum EnumConversionType
|
||||
{
|
||||
NA,
|
||||
Int,
|
||||
String
|
||||
}
|
||||
}
|
233
src/Marr.Data/Mapping/FluentMappings.cs
Normal file
233
src/Marr.Data/Mapping/FluentMappings.cs
Normal file
|
@ -0,0 +1,233 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Marr.Data.Mapping.Strategies;
|
||||
using System.Collections;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a fluent interface for mapping domain entities and properties to database tables and columns.
|
||||
/// </summary>
|
||||
public class FluentMappings
|
||||
{
|
||||
private bool _publicOnly;
|
||||
|
||||
public FluentMappings()
|
||||
: this(true)
|
||||
{ }
|
||||
|
||||
public FluentMappings(bool publicOnly)
|
||||
{
|
||||
_publicOnly = publicOnly;
|
||||
|
||||
}
|
||||
|
||||
public MappingsFluentEntity<TEntity> Entity<TEntity>()
|
||||
{
|
||||
return new MappingsFluentEntity<TEntity>(_publicOnly);
|
||||
}
|
||||
|
||||
public class MappingsFluentEntity<TEntity>
|
||||
{
|
||||
public MappingsFluentEntity(bool publicOnly)
|
||||
{
|
||||
Columns = new MappingsFluentColumns<TEntity>(this, publicOnly);
|
||||
Table = new MappingsFluentTables<TEntity>(this);
|
||||
Relationships = new MappingsFluentRelationships<TEntity>(this, publicOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains methods that map entity properties to database table and view column names;
|
||||
/// </summary>
|
||||
public MappingsFluentColumns<TEntity> Columns { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains methods that map entity classes to database table names.
|
||||
/// </summary>
|
||||
public MappingsFluentTables<TEntity> Table { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains methods that map sub-entities with database table and view column names.
|
||||
/// </summary>
|
||||
public MappingsFluentRelationships<TEntity> Relationships { get; private set; }
|
||||
}
|
||||
|
||||
public class MappingsFluentColumns<TEntity>
|
||||
{
|
||||
private bool _publicOnly;
|
||||
private MappingsFluentEntity<TEntity> _fluentEntity;
|
||||
|
||||
public MappingsFluentColumns(MappingsFluentEntity<TEntity> fluentEntity, bool publicOnly)
|
||||
{
|
||||
_fluentEntity = fluentEntity;
|
||||
_publicOnly = publicOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type.
|
||||
/// Maps all properties except ICollection properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <returns><see cref="ColumnMapCollection"/></returns>
|
||||
public ColumnMapBuilder<TEntity> AutoMapAllProperties()
|
||||
{
|
||||
return AutoMapPropertiesWhere(m => m.MemberType == MemberTypes.Property &&
|
||||
!typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type.
|
||||
/// Maps all properties that are simple types (int, string, DateTime, etc).
|
||||
/// ICollection properties are not included.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <returns><see cref="ColumnMapCollection"/></returns>
|
||||
public ColumnMapBuilder<TEntity> AutoMapSimpleTypeProperties()
|
||||
{
|
||||
return AutoMapPropertiesWhere(m => m.MemberType == MemberTypes.Property &&
|
||||
DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) &&
|
||||
!typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type if they match the predicate.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <param name="predicate">Determines whether a mapping should be created based on the member info.</param>
|
||||
/// <returns><see cref="ColumnMapConfigurator"/></returns>
|
||||
public ColumnMapBuilder<TEntity> AutoMapPropertiesWhere(Func<MemberInfo, bool> predicate)
|
||||
{
|
||||
Type entityType = typeof(TEntity);
|
||||
ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
|
||||
strategy.ColumnPredicate = predicate;
|
||||
ColumnMapCollection columns = strategy.MapColumns(entityType);
|
||||
MapRepository.Instance.Columns[entityType] = columns;
|
||||
return new ColumnMapBuilder<TEntity>(_fluentEntity, columns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ColumnMapBuilder that starts out with no pre-populated columns.
|
||||
/// All columns must be added manually using the builder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public ColumnMapBuilder<TEntity> MapProperties()
|
||||
{
|
||||
Type entityType = typeof(TEntity);
|
||||
ColumnMapCollection columns = new ColumnMapCollection();
|
||||
MapRepository.Instance.Columns[entityType] = columns;
|
||||
return new ColumnMapBuilder<TEntity>(_fluentEntity, columns);
|
||||
}
|
||||
}
|
||||
|
||||
public class MappingsFluentTables<TEntity>
|
||||
{
|
||||
private MappingsFluentEntity<TEntity> _fluentEntity;
|
||||
|
||||
public MappingsFluentTables(MappingsFluentEntity<TEntity> fluentEntity)
|
||||
{
|
||||
_fluentEntity = fluentEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a fluent table mapping interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public TableBuilder<TEntity> AutoMapTable<T>()
|
||||
{
|
||||
return new TableBuilder<TEntity>(_fluentEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the table name for a given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="tableName"></param>
|
||||
public TableBuilder<TEntity> MapTable(string tableName)
|
||||
{
|
||||
return new TableBuilder<TEntity>(_fluentEntity).SetTableName(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
public class MappingsFluentRelationships<TEntity>
|
||||
{
|
||||
private MappingsFluentEntity<TEntity> _fluentEntity;
|
||||
private bool _publicOnly;
|
||||
|
||||
public MappingsFluentRelationships(MappingsFluentEntity<TEntity> fluentEntity, bool publicOnly)
|
||||
{
|
||||
_fluentEntity = fluentEntity;
|
||||
_publicOnly = publicOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type.
|
||||
/// Maps all properties that implement ICollection or are not "simple types".
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<TEntity> AutoMapICollectionOrComplexProperties()
|
||||
{
|
||||
return AutoMapPropertiesWhere(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
(
|
||||
typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) || !DataHelper.IsSimpleType((m as PropertyInfo).PropertyType)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type.
|
||||
/// Maps all properties that implement ICollection.
|
||||
/// </summary>
|
||||
/// <returns><see cref="RelationshipBuilder"/></returns>
|
||||
public RelationshipBuilder<TEntity> AutoMapICollectionProperties()
|
||||
{
|
||||
return AutoMapPropertiesWhere(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type.
|
||||
/// Maps all properties that are not "simple types".
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<TEntity> AutoMapComplexTypeProperties<T>()
|
||||
{
|
||||
return AutoMapPropertiesWhere(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
!DataHelper.IsSimpleType((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type if they match the predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">Determines whether a mapping should be created based on the member info.</param>
|
||||
/// <returns><see cref="RelationshipBuilder"/></returns>
|
||||
public RelationshipBuilder<TEntity> AutoMapPropertiesWhere(Func<MemberInfo, bool> predicate)
|
||||
{
|
||||
Type entityType = typeof(TEntity);
|
||||
ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
|
||||
strategy.RelationshipPredicate = predicate;
|
||||
RelationshipCollection relationships = strategy.MapRelationships(entityType);
|
||||
MapRepository.Instance.Relationships[entityType] = relationships;
|
||||
return new RelationshipBuilder<TEntity>(_fluentEntity, relationships);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RelationshipBuilder that starts out with no pre-populated relationships.
|
||||
/// All relationships must be added manually using the builder.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<TEntity> MapProperties<T>()
|
||||
{
|
||||
Type entityType = typeof(T);
|
||||
RelationshipCollection relationships = new RelationshipCollection();
|
||||
MapRepository.Instance.Relationships[entityType] = relationships;
|
||||
return new RelationshipBuilder<TEntity>(_fluentEntity, relationships);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
src/Marr.Data/Mapping/IColumnInfo.cs
Normal file
32
src/Marr.Data/Mapping/IColumnInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System.Data;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public interface IColumnInfo
|
||||
{
|
||||
string Name { get; set; }
|
||||
string AltName { get; set; }
|
||||
int Size { get; set; }
|
||||
bool IsPrimaryKey { get; set; }
|
||||
bool IsAutoIncrement { get; set; }
|
||||
bool ReturnValue { get; set; }
|
||||
ParameterDirection ParamDirection { get; set; }
|
||||
string TryGetAltName();
|
||||
}
|
||||
|
||||
}
|
32
src/Marr.Data/Mapping/IRelationshipInfo.cs
Normal file
32
src/Marr.Data/Mapping/IRelationshipInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public interface IRelationshipInfo
|
||||
{
|
||||
RelationshipTypes RelationType { get; set; }
|
||||
Type EntityType { get; set; }
|
||||
}
|
||||
|
||||
public enum RelationshipTypes
|
||||
{
|
||||
AutoDetect,
|
||||
One,
|
||||
Many
|
||||
}
|
||||
}
|
206
src/Marr.Data/Mapping/MapBuilder.cs
Normal file
206
src/Marr.Data/Mapping/MapBuilder.cs
Normal file
|
@ -0,0 +1,206 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Marr.Data.Mapping.Strategies;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
[Obsolete("This class is obsolete. Please use the 'Mappings' class.")]
|
||||
public class MapBuilder
|
||||
{
|
||||
private bool _publicOnly;
|
||||
|
||||
public MapBuilder()
|
||||
: this(true)
|
||||
{ }
|
||||
|
||||
public MapBuilder(bool publicOnly)
|
||||
{
|
||||
_publicOnly = publicOnly;
|
||||
}
|
||||
|
||||
#region - Columns -
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type.
|
||||
/// Maps all properties except ICollection properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <returns><see cref="ColumnMapCollection"/></returns>
|
||||
public ColumnMapBuilder<T> BuildColumns<T>()
|
||||
{
|
||||
return BuildColumns<T>(m => m.MemberType == MemberTypes.Property &&
|
||||
!typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type.
|
||||
/// Maps all properties that are simple types (int, string, DateTime, etc).
|
||||
/// ICollection properties are not included.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <returns><see cref="ColumnMapCollection"/></returns>
|
||||
public ColumnMapBuilder<T> BuildColumnsFromSimpleTypes<T>()
|
||||
{
|
||||
return BuildColumns<T>(m => m.MemberType == MemberTypes.Property &&
|
||||
DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) &&
|
||||
!typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type.
|
||||
/// Maps properties that are included in the include list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <param name="propertiesToInclude"></param>
|
||||
/// <returns><see cref="ColumnMapCollection"/></returns>
|
||||
public ColumnMapBuilder<T> BuildColumns<T>(params string[] propertiesToInclude)
|
||||
{
|
||||
return BuildColumns<T>(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
propertiesToInclude.Contains(m.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type.
|
||||
/// Maps all properties except the ones in the exclusion list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <param name="propertiesToExclude"></param>
|
||||
/// <returns><see cref="ColumnMapCollection"/></returns>
|
||||
public ColumnMapBuilder<T> BuildColumnsExcept<T>(params string[] propertiesToExclude)
|
||||
{
|
||||
return BuildColumns<T>(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
!propertiesToExclude.Contains(m.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates column mappings for the given type if they match the predicate.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <param name="predicate">Determines whether a mapping should be created based on the member info.</param>
|
||||
/// <returns><see cref="ColumnMapConfigurator"/></returns>
|
||||
public ColumnMapBuilder<T> BuildColumns<T>(Func<MemberInfo, bool> predicate)
|
||||
{
|
||||
Type entityType = typeof(T);
|
||||
ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
|
||||
strategy.ColumnPredicate = predicate;
|
||||
ColumnMapCollection columns = strategy.MapColumns(entityType);
|
||||
MapRepository.Instance.Columns[entityType] = columns;
|
||||
return new ColumnMapBuilder<T>(null, columns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ColumnMapBuilder that starts out with no pre-populated columns.
|
||||
/// All columns must be added manually using the builder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public ColumnMapBuilder<T> Columns<T>()
|
||||
{
|
||||
Type entityType = typeof(T);
|
||||
ColumnMapCollection columns = new ColumnMapCollection();
|
||||
MapRepository.Instance.Columns[entityType] = columns;
|
||||
return new ColumnMapBuilder<T>(null, columns);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Relationships -
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type.
|
||||
/// Maps all properties that implement ICollection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <returns><see cref="RelationshipBuilder"/></returns>
|
||||
public RelationshipBuilder<T> BuildRelationships<T>()
|
||||
{
|
||||
return BuildRelationships<T>(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type.
|
||||
/// Maps all properties that are listed in the include list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <param name="propertiesToInclude"></param>
|
||||
/// <returns><see cref="RelationshipBuilder"/></returns>
|
||||
public RelationshipBuilder<T> BuildRelationships<T>(params string[] propertiesToInclude)
|
||||
{
|
||||
Func<MemberInfo, bool> predicate = m =>
|
||||
(
|
||||
// ICollection properties
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) &&
|
||||
propertiesToInclude.Contains(m.Name)
|
||||
) || ( // Single entity properties
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
!typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) &&
|
||||
propertiesToInclude.Contains(m.Name)
|
||||
);
|
||||
|
||||
return BuildRelationships<T>(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates relationship mappings for the given type if they match the predicate.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is being built.</typeparam>
|
||||
/// <param name="predicate">Determines whether a mapping should be created based on the member info.</param>
|
||||
/// <returns><see cref="RelationshipBuilder"/></returns>
|
||||
public RelationshipBuilder<T> BuildRelationships<T>(Func<MemberInfo, bool> predicate)
|
||||
{
|
||||
Type entityType = typeof(T);
|
||||
ConventionMapStrategy strategy = new ConventionMapStrategy(_publicOnly);
|
||||
strategy.RelationshipPredicate = predicate;
|
||||
RelationshipCollection relationships = strategy.MapRelationships(entityType);
|
||||
MapRepository.Instance.Relationships[entityType] = relationships;
|
||||
return new RelationshipBuilder<T>(null, relationships);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RelationshipBuilder that starts out with no pre-populated relationships.
|
||||
/// All relationships must be added manually using the builder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<T> Relationships<T>()
|
||||
{
|
||||
Type entityType = typeof(T);
|
||||
RelationshipCollection relationships = new RelationshipCollection();
|
||||
MapRepository.Instance.Relationships[entityType] = relationships;
|
||||
return new RelationshipBuilder<T>(null, relationships);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Tables -
|
||||
|
||||
/// <summary>
|
||||
/// Provides a fluent table mapping interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public TableBuilder<T> BuildTable<T>()
|
||||
{
|
||||
return new TableBuilder<T>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the table name for a given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="tableName"></param>
|
||||
public TableBuilder<T> BuildTable<T>(string tableName)
|
||||
{
|
||||
return new TableBuilder<T>(null).SetTableName(tableName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
191
src/Marr.Data/Mapping/MappingHelper.cs
Normal file
191
src/Marr.Data/Mapping/MappingHelper.cs
Normal file
|
@ -0,0 +1,191 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Data.Common;
|
||||
using Marr.Data.Converters;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
internal class MappingHelper
|
||||
{
|
||||
private MapRepository _repos;
|
||||
private IDataMapper _db;
|
||||
|
||||
public MappingHelper(IDataMapper db)
|
||||
{
|
||||
_repos = MapRepository.Instance;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates an entity and loads its mapped fields with the data from the reader.
|
||||
/// </summary>
|
||||
public object CreateAndLoadEntity<T>(ColumnMapCollection mappings, DbDataReader reader, bool useAltName)
|
||||
{
|
||||
return CreateAndLoadEntity(typeof(T), mappings, reader, useAltName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates an entity and loads its mapped fields with the data from the reader.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity being created and loaded.</param>
|
||||
/// <param name="mappings">The field mappings for the passed in entity.</param>
|
||||
/// <param name="reader">The open data reader.</param>
|
||||
/// <param name="useAltNames">Determines if the column AltName should be used.</param>
|
||||
/// <returns>Returns an entity loaded with data.</returns>
|
||||
public object CreateAndLoadEntity(Type entityType, ColumnMapCollection mappings, DbDataReader reader, bool useAltName)
|
||||
{
|
||||
// Create new entity
|
||||
object ent = _repos.ReflectionStrategy.CreateInstance(entityType);
|
||||
return LoadExistingEntity(mappings, reader, ent, useAltName);
|
||||
}
|
||||
|
||||
public object LoadExistingEntity(ColumnMapCollection mappings, DbDataReader reader, object ent, bool useAltName)
|
||||
{
|
||||
// Populate entity fields from data reader
|
||||
foreach (ColumnMap dataMap in mappings)
|
||||
{
|
||||
try
|
||||
{
|
||||
string colName = dataMap.ColumnInfo.GetColumName(useAltName);
|
||||
int ordinal = reader.GetOrdinal(colName);
|
||||
object dbValue = reader.GetValue(ordinal);
|
||||
|
||||
// Handle conversions
|
||||
if (dataMap.Converter != null)
|
||||
{
|
||||
var convertContext = new ConverterContext
|
||||
{
|
||||
DbValue = dbValue,
|
||||
ColumnMap = dataMap,
|
||||
MapCollection = mappings,
|
||||
DataRecord = reader
|
||||
};
|
||||
|
||||
dbValue = dataMap.Converter.FromDB(convertContext);
|
||||
}
|
||||
|
||||
if (dbValue != DBNull.Value && dbValue != null)
|
||||
{
|
||||
dataMap.Setter(ent, dbValue);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string msg = string.Format("The DataMapper was unable to load the following field: '{0}'. {1}",
|
||||
dataMap.ColumnInfo.Name, ex.Message);
|
||||
|
||||
throw new DataMappingException(msg, ex);
|
||||
}
|
||||
}
|
||||
|
||||
PrepareLazyLoadedProperties(ent);
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
private void PrepareLazyLoadedProperties(object ent)
|
||||
{
|
||||
// Handle lazy loaded properties
|
||||
Type entType = ent.GetType();
|
||||
if (_repos.Relationships.ContainsKey(entType))
|
||||
{
|
||||
Func<IDataMapper> dbCreate = () =>
|
||||
{
|
||||
var db = new DataMapper(_db.ProviderFactory, _db.ConnectionString);
|
||||
db.SqlMode = SqlModes.Text;
|
||||
return db;
|
||||
};
|
||||
|
||||
var relationships = _repos.Relationships[entType];
|
||||
foreach (var rel in relationships.Where(r => r.IsLazyLoaded))
|
||||
{
|
||||
var lazyLoaded = (ILazyLoaded)rel.LazyLoaded.Clone();
|
||||
lazyLoaded.Prepare(dbCreate, ent);
|
||||
rel.Setter(ent, lazyLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T LoadSimpleValueFromFirstColumn<T>(DbDataReader reader)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T)reader.GetValue(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string firstColumnName = reader.GetName(0);
|
||||
string msg = string.Format("The DataMapper was unable to create a value of type '{0}' from the first column '{1}'.",
|
||||
typeof(T).Name, firstColumnName);
|
||||
|
||||
throw new DataMappingException(msg, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all parameters for a SP based on the mappings of the entity,
|
||||
/// and assigns them values based on the field values of the entity.
|
||||
/// </summary>
|
||||
public void CreateParameters<T>(T entity, ColumnMapCollection columnMapCollection, bool isAutoQuery)
|
||||
{
|
||||
ColumnMapCollection mappings = columnMapCollection;
|
||||
|
||||
if (!isAutoQuery)
|
||||
{
|
||||
// Order columns (applies to Oracle and OleDb only)
|
||||
mappings = columnMapCollection.OrderParameters(_db.Command);
|
||||
}
|
||||
|
||||
foreach (ColumnMap columnMap in mappings)
|
||||
{
|
||||
if (columnMap.ColumnInfo.IsAutoIncrement)
|
||||
continue;
|
||||
|
||||
var param = _db.Command.CreateParameter();
|
||||
param.ParameterName = columnMap.ColumnInfo.Name;
|
||||
param.Size = columnMap.ColumnInfo.Size;
|
||||
param.Direction = columnMap.ColumnInfo.ParamDirection;
|
||||
|
||||
object val = columnMap.Getter(entity);
|
||||
|
||||
param.Value = val ?? DBNull.Value; // Convert nulls to DBNulls
|
||||
|
||||
if (columnMap.Converter != null)
|
||||
{
|
||||
param.Value = columnMap.Converter.ToDB(param.Value);
|
||||
}
|
||||
|
||||
// Set the appropriate DbType property depending on the parameter type
|
||||
// Note: the columnMap.DBType property was set when the ColumnMap was created
|
||||
MapRepository.Instance.DbTypeBuilder.SetDbType(param, columnMap.DBType);
|
||||
|
||||
_db.Command.Parameters.Add(param);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns the SP result columns to the passed in 'mappings' fields.
|
||||
/// </summary>
|
||||
public void SetOutputValues<T>(T entity, IEnumerable<ColumnMap> mappings)
|
||||
{
|
||||
foreach (ColumnMap dataMap in mappings)
|
||||
{
|
||||
object output = _db.Command.Parameters[dataMap.ColumnInfo.Name].Value;
|
||||
dataMap.Setter(entity, output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns the passed in 'value' to the passed in 'mappings' fields.
|
||||
/// </summary>
|
||||
public void SetOutputValues<T>(T entity, IEnumerable<ColumnMap> mappings, object value)
|
||||
{
|
||||
foreach (ColumnMap dataMap in mappings)
|
||||
{
|
||||
dataMap.Setter(entity, Convert.ChangeType(value, dataMap.FieldType));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
98
src/Marr.Data/Mapping/Relationship.cs
Normal file
98
src/Marr.Data/Mapping/Relationship.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using Marr.Data.Reflection;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public class Relationship
|
||||
{
|
||||
|
||||
public Relationship(MemberInfo member)
|
||||
: this(member, new RelationshipInfo())
|
||||
{ }
|
||||
|
||||
public Relationship(MemberInfo member, IRelationshipInfo relationshipInfo)
|
||||
{
|
||||
Member = member;
|
||||
|
||||
MemberType = ReflectionHelper.GetMemberType(member);
|
||||
|
||||
// Try to determine the RelationshipType
|
||||
if (relationshipInfo.RelationType == RelationshipTypes.AutoDetect)
|
||||
{
|
||||
if (typeof(ICollection).IsAssignableFrom(MemberType))
|
||||
{
|
||||
relationshipInfo.RelationType = RelationshipTypes.Many;
|
||||
}
|
||||
else
|
||||
{
|
||||
relationshipInfo.RelationType = RelationshipTypes.One;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to determine the EntityType
|
||||
if (relationshipInfo.EntityType == null)
|
||||
{
|
||||
if (relationshipInfo.RelationType == RelationshipTypes.Many)
|
||||
{
|
||||
if (MemberType.IsGenericType)
|
||||
{
|
||||
// Assume a Collection<T> or List<T> and return T
|
||||
relationshipInfo.EntityType = MemberType.GetGenericArguments()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(string.Format(
|
||||
"The DataMapper could not determine the RelationshipAttribute EntityType for {0}.",
|
||||
MemberType.Name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
relationshipInfo.EntityType = MemberType;
|
||||
}
|
||||
}
|
||||
|
||||
RelationshipInfo = relationshipInfo;
|
||||
|
||||
|
||||
|
||||
Setter = MapRepository.Instance.ReflectionStrategy.BuildSetter(member.DeclaringType, member.Name);
|
||||
}
|
||||
|
||||
public IRelationshipInfo RelationshipInfo { get; private set; }
|
||||
|
||||
public MemberInfo Member { get; private set; }
|
||||
|
||||
public Type MemberType { get; private set; }
|
||||
|
||||
public bool IsLazyLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
return LazyLoaded != null;
|
||||
}
|
||||
}
|
||||
|
||||
public ILazyLoaded LazyLoaded { get; set; }
|
||||
|
||||
public GetterDelegate Getter { get; set; }
|
||||
public SetterDelegate Setter { get; set; }
|
||||
}
|
||||
}
|
75
src/Marr.Data/Mapping/RelationshipAttribute.cs
Normal file
75
src/Marr.Data/Mapping/RelationshipAttribute.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a field as a related entity that needs to be created at filled with data.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class RelationshipAttribute : Attribute, IRelationshipInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a data relationship.
|
||||
/// </summary>
|
||||
public RelationshipAttribute()
|
||||
: this(RelationshipTypes.AutoDetect)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a data relationship.
|
||||
/// </summary>
|
||||
/// <param name="relationType"></param>
|
||||
public RelationshipAttribute(RelationshipTypes relationType)
|
||||
{
|
||||
RelationType = relationType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a One-ToMany data relationship for a given type.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The type of the child entity.</param>
|
||||
public RelationshipAttribute(Type entityType)
|
||||
: this(entityType, RelationshipTypes.AutoDetect)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a data relationship.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The type of the child entity.</param>
|
||||
/// <param name="relationType">The relationship type can be "One" or "Many".</param>
|
||||
public RelationshipAttribute(Type entityType, RelationshipTypes relationType)
|
||||
{
|
||||
EntityType = entityType;
|
||||
RelationType = relationType;
|
||||
}
|
||||
|
||||
#region IRelationshipInfo Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the relationship type can be "One" or "Many".
|
||||
/// </summary>
|
||||
public RelationshipTypes RelationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the child entity.
|
||||
/// </summary>
|
||||
public Type EntityType { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
172
src/Marr.Data/Mapping/RelationshipBuilder.cs
Normal file
172
src/Marr.Data/Mapping/RelationshipBuilder.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Marr.Data.Mapping.Strategies;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// This class has fluent methods that are used to easily configure relationship mappings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class RelationshipBuilder<TEntity>
|
||||
{
|
||||
private FluentMappings.MappingsFluentEntity<TEntity> _fluentEntity;
|
||||
private string _currentPropertyName;
|
||||
|
||||
public RelationshipBuilder(FluentMappings.MappingsFluentEntity<TEntity> fluentEntity, RelationshipCollection relationships)
|
||||
{
|
||||
_fluentEntity = fluentEntity;
|
||||
Relationships = relationships;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of relationship mappings that are being configured.
|
||||
/// </summary>
|
||||
public RelationshipCollection Relationships { get; private set; }
|
||||
|
||||
#region - Fluent Methods -
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the configurator to configure the given property.
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<TEntity> For(Expression<Func<TEntity, object>> property)
|
||||
{
|
||||
return For(property.GetMemberName());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the configurator to configure the given property or field.
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<TEntity> For(string propertyName)
|
||||
{
|
||||
_currentPropertyName = propertyName;
|
||||
|
||||
// Try to add the relationship if it doesn't exist
|
||||
if (Relationships[_currentPropertyName] == null)
|
||||
{
|
||||
TryAddRelationshipForField(_currentPropertyName);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a property to be lazy loaded, with a given query.
|
||||
/// </summary>
|
||||
/// <typeparam name="TChild"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="condition">condition in which a child could exist. eg. avoid call to db if foreign key is 0 or null</param>
|
||||
/// <returns></returns>
|
||||
public RelationshipBuilder<TEntity> LazyLoad<TChild>(Func<IDataMapper, TEntity, TChild> query, Func<TEntity, bool> condition = null)
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
|
||||
Relationships[_currentPropertyName].LazyLoaded = new LazyLoaded<TEntity, TChild>(query, condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationshipBuilder<TEntity> SetOneToOne()
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
SetOneToOne(_currentPropertyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationshipBuilder<TEntity> SetOneToOne(string propertyName)
|
||||
{
|
||||
Relationships[propertyName].RelationshipInfo.RelationType = RelationshipTypes.One;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationshipBuilder<TEntity> SetOneToMany()
|
||||
{
|
||||
AssertCurrentPropertyIsSet();
|
||||
SetOneToMany(_currentPropertyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationshipBuilder<TEntity> SetOneToMany(string propertyName)
|
||||
{
|
||||
Relationships[propertyName].RelationshipInfo.RelationType = RelationshipTypes.Many;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationshipBuilder<TEntity> Ignore(Expression<Func<TEntity, object>> property)
|
||||
{
|
||||
string propertyName = property.GetMemberName();
|
||||
Relationships.RemoveAll(r => r.Member.Name == propertyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentTables<TEntity> Tables
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fluentEntity == null)
|
||||
{
|
||||
throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
|
||||
}
|
||||
|
||||
return _fluentEntity.Table;
|
||||
}
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentColumns<TEntity> Columns
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fluentEntity == null)
|
||||
{
|
||||
throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
|
||||
}
|
||||
|
||||
return _fluentEntity.Columns;
|
||||
}
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentEntity<TNewEntity> Entity<TNewEntity>()
|
||||
{
|
||||
return new FluentMappings.MappingsFluentEntity<TNewEntity>(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a Relationship for the given field name.
|
||||
/// Throws and exception if field cannot be found.
|
||||
/// </summary>
|
||||
private void TryAddRelationshipForField(string fieldName)
|
||||
{
|
||||
// Set strategy to filter for public or private fields
|
||||
ConventionMapStrategy strategy = new ConventionMapStrategy(false);
|
||||
|
||||
// Find the field that matches the given field name
|
||||
strategy.RelationshipPredicate = mi => mi.Name == fieldName;
|
||||
Relationship relationship = strategy.MapRelationships(typeof(TEntity)).FirstOrDefault();
|
||||
|
||||
if (relationship == null)
|
||||
{
|
||||
throw new DataMappingException(string.Format("Could not find the field '{0}' in '{1}'.",
|
||||
fieldName,
|
||||
typeof(TEntity).Name));
|
||||
}
|
||||
Relationships.Add(relationship);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the "current" property has not been set.
|
||||
/// </summary>
|
||||
private void AssertCurrentPropertyIsSet()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_currentPropertyName))
|
||||
{
|
||||
throw new DataMappingException("A property must first be specified using the 'For' method.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
35
src/Marr.Data/Mapping/RelationshipCollection.cs
Normal file
35
src/Marr.Data/Mapping/RelationshipCollection.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public class RelationshipCollection : List<Relationship>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a ColumnMap by its field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName"></param>
|
||||
/// <returns></returns>
|
||||
public Relationship this[string fieldName]
|
||||
{
|
||||
get
|
||||
{
|
||||
return Find(m => m.Member.Name == fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/Marr.Data/Mapping/RelationshipInfo.cs
Normal file
11
src/Marr.Data/Mapping/RelationshipInfo.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
public class RelationshipInfo : IRelationshipInfo
|
||||
{
|
||||
public RelationshipTypes RelationType { get; set; }
|
||||
|
||||
public Type EntityType { get; set; }
|
||||
}
|
||||
}
|
70
src/Marr.Data/Mapping/Strategies/AttributeMapStrategy.cs
Normal file
70
src/Marr.Data/Mapping/Strategies/AttributeMapStrategy.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Marr.Data.Mapping.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps fields or properties that are marked with the ColumnAttribute.
|
||||
/// </summary>
|
||||
public class AttributeMapStrategy : ReflectionMapStrategyBase
|
||||
{
|
||||
public AttributeMapStrategy()
|
||||
: base()
|
||||
{ }
|
||||
|
||||
public AttributeMapStrategy(bool publicOnly)
|
||||
: base(publicOnly)
|
||||
{ }
|
||||
|
||||
public AttributeMapStrategy(BindingFlags flags)
|
||||
: base(flags)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Registers any member with a ColumnAttribute as a ColumnMap.
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
/// <param name="member">The current member that is being inspected.</param>
|
||||
/// <param name="columnAtt">A ColumnAttribute (is null of one does not exist).</param>
|
||||
/// <param name="columnMaps">A list of ColumnMaps.</param>
|
||||
/// </summary>
|
||||
protected override void CreateColumnMap(Type entityType, MemberInfo member, ColumnAttribute columnAtt, ColumnMapCollection columnMaps)
|
||||
{
|
||||
if (columnAtt != null)
|
||||
{
|
||||
ColumnMap columnMap = new ColumnMap(member, columnAtt);
|
||||
columnMaps.Add(columnMap);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers any member with a RelationshipAttribute as a relationship.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
/// <param name="member">The current member that is being inspected.</param>
|
||||
/// <param name="relationshipAtt">A RelationshipAttribute (is null if one does not exist).</param>
|
||||
/// <param name="relationships">A list of Relationships.</param>
|
||||
protected override void CreateRelationship(Type entityType, MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships)
|
||||
{
|
||||
if (relationshipAtt != null)
|
||||
{
|
||||
Relationship relationship = new Relationship(member, relationshipAtt);
|
||||
relationships.Add(relationship);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
src/Marr.Data/Mapping/Strategies/ConventionMapStrategy.cs
Normal file
48
src/Marr.Data/Mapping/Strategies/ConventionMapStrategy.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
|
||||
namespace Marr.Data.Mapping.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to specify a member based filter by defining predicates that filter the members that are mapped.
|
||||
/// </summary>
|
||||
public class ConventionMapStrategy : ReflectionMapStrategyBase
|
||||
{
|
||||
public ConventionMapStrategy(bool publicOnly)
|
||||
: base(publicOnly)
|
||||
{
|
||||
// Default: Only map members that are properties
|
||||
ColumnPredicate = m => m.MemberType == MemberTypes.Property;
|
||||
|
||||
// Default: Only map members that are properties and that are ICollection types
|
||||
RelationshipPredicate = m =>
|
||||
{
|
||||
return m.MemberType == MemberTypes.Property && typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType);
|
||||
};
|
||||
}
|
||||
|
||||
public Func<MemberInfo, bool> ColumnPredicate;
|
||||
public Func<MemberInfo, bool> RelationshipPredicate;
|
||||
|
||||
|
||||
|
||||
protected override void CreateColumnMap(Type entityType, MemberInfo member, ColumnAttribute columnAtt, ColumnMapCollection columnMaps)
|
||||
{
|
||||
if (ColumnPredicate(member))
|
||||
{
|
||||
// Map public property to DB column
|
||||
columnMaps.Add(new ColumnMap(member));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CreateRelationship(Type entityType, MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships)
|
||||
{
|
||||
if (RelationshipPredicate(member))
|
||||
{
|
||||
relationships.Add(new Relationship(member));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
45
src/Marr.Data/Mapping/Strategies/IMapStrategy.cs
Normal file
45
src/Marr.Data/Mapping/Strategies/IMapStrategy.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
|
||||
namespace Marr.Data.Mapping.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// A strategy for creating mappings for a given entity.
|
||||
/// </summary>
|
||||
public interface IMapStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table map for a given entity type.
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <returns></returns>
|
||||
string MapTable(Type entityType);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ColumnMapCollection for a given entity type.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
ColumnMapCollection MapColumns(Type entityType);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RelationshpCollection for a given entity type.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
/// <returns></returns>
|
||||
RelationshipCollection MapRelationships(Type entityType);
|
||||
}
|
||||
}
|
83
src/Marr.Data/Mapping/Strategies/PropertyMapStrategy.cs
Normal file
83
src/Marr.Data/Mapping/Strategies/PropertyMapStrategy.cs
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Marr.Data.Mapping.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps all public properties to DB columns.
|
||||
/// </summary>
|
||||
public class PropertyMapStrategy : AttributeMapStrategy
|
||||
{
|
||||
public PropertyMapStrategy(bool publicOnly)
|
||||
: base(publicOnly)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Maps properties to DB columns if a ColumnAttribute is not present.
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
/// <param name="member">The current member that is being inspected.</param>
|
||||
/// <param name="columnAtt">A ColumnAttribute (is null of one does not exist).</param>
|
||||
/// <param name="columnMaps">A list of ColumnMaps.</param>
|
||||
/// </summary>
|
||||
protected override void CreateColumnMap(Type entityType, MemberInfo member, ColumnAttribute columnAtt, ColumnMapCollection columnMaps)
|
||||
{
|
||||
if (columnAtt != null)
|
||||
{
|
||||
// Add columns with ColumnAttribute
|
||||
base.CreateColumnMap(entityType, member, columnAtt, columnMaps);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (member.MemberType == MemberTypes.Property)
|
||||
{
|
||||
// Map public property to DB column
|
||||
columnMaps.Add(new ColumnMap(member));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a relationship if a RelationshipAttribute is present.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
/// <param name="member">The current member that is being inspected.</param>
|
||||
/// <param name="relationshipAtt">A RelationshipAttribute (is null if one does not exist).</param>
|
||||
/// <param name="relationships">A list of Relationships.</param>
|
||||
protected override void CreateRelationship(Type entityType, MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships)
|
||||
{
|
||||
if (relationshipAtt != null)
|
||||
{
|
||||
// Add relationships by RelationshipAttribute
|
||||
base.CreateRelationship(entityType, member, relationshipAtt, relationships);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (member.MemberType == MemberTypes.Property)
|
||||
{
|
||||
PropertyInfo propertyInfo = member as PropertyInfo;
|
||||
if (typeof(ICollection).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
Relationship relationship = new Relationship(member);
|
||||
relationships.Add(relationship);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
145
src/Marr.Data/Mapping/Strategies/ReflectionMapStrategyBase.cs
Normal file
145
src/Marr.Data/Mapping/Strategies/ReflectionMapStrategyBase.cs
Normal file
|
@ -0,0 +1,145 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Marr.Data.Mapping.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Iterates through the members of an entity based on the BindingFlags, and provides an abstract method for adding ColumnMaps for each member.
|
||||
/// </summary>
|
||||
public abstract class ReflectionMapStrategyBase : IMapStrategy
|
||||
{
|
||||
private BindingFlags _bindingFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Loops through members with the following BindingFlags:
|
||||
/// Instance | NonPublic | Public | FlattenHierarchy
|
||||
/// </summary>
|
||||
public ReflectionMapStrategyBase()
|
||||
{
|
||||
_bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through members with the following BindingFlags:
|
||||
/// Instance | Public | FlattenHierarchy | NonPublic (optional)
|
||||
/// </summary>
|
||||
/// <param name="publicOnly"></param>
|
||||
public ReflectionMapStrategyBase(bool publicOnly)
|
||||
{
|
||||
if (publicOnly)
|
||||
{
|
||||
_bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through members based on the passed in BindingFlags.
|
||||
/// </summary>
|
||||
/// <param name="bindingFlags"></param>
|
||||
public ReflectionMapStrategyBase(BindingFlags bindingFlags)
|
||||
{
|
||||
_bindingFlags = bindingFlags;
|
||||
}
|
||||
|
||||
public string MapTable(Type entityType)
|
||||
{
|
||||
object[] atts = entityType.GetCustomAttributes(typeof(TableAttribute), true);
|
||||
if (atts.Length > 0)
|
||||
{
|
||||
return (atts[0] as TableAttribute).Name;
|
||||
}
|
||||
return entityType.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IMapStrategy.
|
||||
/// Loops through filtered members and calls the virtual "CreateColumnMap" void for each member.
|
||||
/// Subclasses can override CreateColumnMap to customize adding ColumnMaps.
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <returns></returns>
|
||||
public ColumnMapCollection MapColumns(Type entityType)
|
||||
{
|
||||
ColumnMapCollection columnMaps = new ColumnMapCollection();
|
||||
|
||||
MemberInfo[] members = entityType.GetMembers(_bindingFlags);
|
||||
foreach (var member in members)
|
||||
{
|
||||
ColumnAttribute columnAtt = GetColumnAttribute(member);
|
||||
CreateColumnMap(entityType, member, columnAtt, columnMaps);
|
||||
}
|
||||
|
||||
return columnMaps;
|
||||
}
|
||||
|
||||
public RelationshipCollection MapRelationships(Type entityType)
|
||||
{
|
||||
RelationshipCollection relationships = new RelationshipCollection();
|
||||
|
||||
MemberInfo[] members = entityType.GetMembers(_bindingFlags);
|
||||
foreach (MemberInfo member in members)
|
||||
{
|
||||
RelationshipAttribute relationshipAtt = GetRelationshipAttribute(member);
|
||||
CreateRelationship(entityType, member, relationshipAtt, relationships);
|
||||
}
|
||||
|
||||
return relationships;
|
||||
}
|
||||
|
||||
protected ColumnAttribute GetColumnAttribute(MemberInfo member)
|
||||
{
|
||||
if (member.IsDefined(typeof(ColumnAttribute), false))
|
||||
{
|
||||
return (ColumnAttribute)member.GetCustomAttributes(typeof(ColumnAttribute), false)[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected RelationshipAttribute GetRelationshipAttribute(MemberInfo member)
|
||||
{
|
||||
if (member.IsDefined(typeof(RelationshipAttribute), false))
|
||||
{
|
||||
return (RelationshipAttribute)member.GetCustomAttributes(typeof(RelationshipAttribute), false)[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspect a member and optionally add a ColumnMap.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity type that is being mapped.</param>
|
||||
/// <param name="member">The member that is being mapped.</param>
|
||||
/// <param name="columnMaps">The ColumnMapCollection that is being created.</param>
|
||||
protected abstract void CreateColumnMap(Type entityType, MemberInfo member, ColumnAttribute columnAtt, ColumnMapCollection columnMaps);
|
||||
|
||||
/// <summary>
|
||||
/// Inspect a member and optionally add a Relationship.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity that is being mapped.</param>
|
||||
/// <param name="member">The current member that is being inspected.</param>
|
||||
/// <param name="relationshipAtt">A RelationshipAttribute (is null if one does not exist).</param>
|
||||
/// <param name="relationships">A list of Relationships.</param>
|
||||
protected abstract void CreateRelationship(Type entityType, MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships);
|
||||
}
|
||||
}
|
40
src/Marr.Data/Mapping/TableAttribute.cs
Normal file
40
src/Marr.Data/Mapping/TableAttribute.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class TableAttribute : Attribute
|
||||
{
|
||||
private string _name;
|
||||
|
||||
public TableAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
public TableAttribute(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { _name = value; }
|
||||
}
|
||||
}
|
||||
}
|
58
src/Marr.Data/Mapping/TableBuilder.cs
Normal file
58
src/Marr.Data/Mapping/TableBuilder.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
|
||||
namespace Marr.Data.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// This class has fluent methods that are used to easily configure the table mapping.
|
||||
/// </summary>
|
||||
public class TableBuilder<TEntity>
|
||||
{
|
||||
private FluentMappings.MappingsFluentEntity<TEntity> _fluentEntity;
|
||||
|
||||
public TableBuilder(FluentMappings.MappingsFluentEntity<TEntity> fluentEntity)
|
||||
{
|
||||
_fluentEntity = fluentEntity;
|
||||
}
|
||||
|
||||
#region - Fluent Methods -
|
||||
|
||||
public TableBuilder<TEntity> SetTableName(string tableName)
|
||||
{
|
||||
MapRepository.Instance.Tables[typeof(TEntity)] = tableName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentColumns<TEntity> Columns
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fluentEntity == null)
|
||||
{
|
||||
throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
|
||||
}
|
||||
|
||||
return _fluentEntity.Columns;
|
||||
}
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentRelationships<TEntity> Relationships
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fluentEntity == null)
|
||||
{
|
||||
throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
|
||||
}
|
||||
|
||||
return _fluentEntity.Relationships;
|
||||
}
|
||||
}
|
||||
|
||||
public FluentMappings.MappingsFluentEntity<TNewEntity> Entity<TNewEntity>()
|
||||
{
|
||||
return new FluentMappings.MappingsFluentEntity<TNewEntity>(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
152
src/Marr.Data/Marr.Data.csproj
Normal file
152
src/Marr.Data/Marr.Data.csproj
Normal file
|
@ -0,0 +1,152 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{F6FC6BE7-0847-4817-A1ED-223DC647C3D7}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Marr.Data</RootNamespace>
|
||||
<AssemblyName>Marr.Data</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<UpgradeBackupLocation />
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>..\..\_output\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>..\..\_output\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Converters\BooleanIntConverter.cs" />
|
||||
<Compile Include="Converters\BooleanYNConverter.cs" />
|
||||
<Compile Include="Converters\CastConverter.cs" />
|
||||
<Compile Include="Converters\ConversionException.cs" />
|
||||
<Compile Include="Converters\ConverterContext.cs" />
|
||||
<Compile Include="Converters\EnumIntConverter.cs" />
|
||||
<Compile Include="Converters\EnumStringConverter.cs" />
|
||||
<Compile Include="Converters\IConverter.cs" />
|
||||
<Compile Include="DataHelper.cs" />
|
||||
<Compile Include="DataMapper.cs" />
|
||||
<Compile Include="DataMappingException.cs" />
|
||||
<Compile Include="EntityGraph.cs" />
|
||||
<Compile Include="EntityMerger.cs" />
|
||||
<Compile Include="EntityReference.cs" />
|
||||
<Compile Include="ExtensionMethods.cs" />
|
||||
<Compile Include="GroupingKeyCollection.cs" />
|
||||
<Compile Include="IDataMapper.cs" />
|
||||
<Compile Include="LazyLoaded.cs" />
|
||||
<Compile Include="Mapping\FluentMappings.cs" />
|
||||
<Compile Include="QGen\SqliteRowCountQueryDecorator.cs" />
|
||||
<Compile Include="QGen\SqlitePagingQueryDecorator.cs" />
|
||||
<Compile Include="UnitOfWork.cs" />
|
||||
<Compile Include="UnitOfWorkSharedContext.cs" />
|
||||
<Compile Include="Mapping\ColumnMapBuilder.cs" />
|
||||
<Compile Include="Mapping\MapBuilder.cs" />
|
||||
<Compile Include="Mapping\RelationshipBuilder.cs" />
|
||||
<Compile Include="Mapping\RelationshipInfo.cs" />
|
||||
<Compile Include="Mapping\Strategies\AttributeMapStrategy.cs" />
|
||||
<Compile Include="Mapping\ColumnAttribute.cs" />
|
||||
<Compile Include="Mapping\ColumnInfo.cs" />
|
||||
<Compile Include="Mapping\ColumnMap.cs" />
|
||||
<Compile Include="Mapping\ColumnMapCollection.cs" />
|
||||
<Compile Include="Mapping\Strategies\ConventionMapStrategy.cs" />
|
||||
<Compile Include="Mapping\Strategies\PropertyMapStrategy.cs" />
|
||||
<Compile Include="Mapping\Strategies\ReflectionMapStrategyBase.cs" />
|
||||
<Compile Include="Mapping\EnumConversionType.cs" />
|
||||
<Compile Include="Mapping\IColumnInfo.cs" />
|
||||
<Compile Include="Mapping\IRelationshipInfo.cs" />
|
||||
<Compile Include="Mapping\MappingHelper.cs" />
|
||||
<Compile Include="Mapping\Relationship.cs" />
|
||||
<Compile Include="Mapping\RelationshipAttribute.cs" />
|
||||
<Compile Include="Mapping\RelationshipCollection.cs" />
|
||||
<Compile Include="Mapping\Strategies\IMapStrategy.cs" />
|
||||
<Compile Include="Mapping\TableAttribute.cs" />
|
||||
<Compile Include="Mapping\TableBuilder.cs" />
|
||||
<Compile Include="MapRepository.cs" />
|
||||
<Compile Include="Parameters\DbTypeBuilder.cs" />
|
||||
<Compile Include="Parameters\IDbTypeBuilder.cs" />
|
||||
<Compile Include="Parameters\OleDbTypeBuilder.cs" />
|
||||
<Compile Include="Parameters\ParameterChainMethods.cs" />
|
||||
<Compile Include="Parameters\SqlDbTypeBuilder.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="QGen\DeleteQuery.cs" />
|
||||
<Compile Include="QGen\Dialects\Dialect.cs" />
|
||||
<Compile Include="QGen\Dialects\FirebirdDialect.cs" />
|
||||
<Compile Include="QGen\Dialects\OracleDialect.cs" />
|
||||
<Compile Include="QGen\Dialects\SqliteDialect.cs" />
|
||||
<Compile Include="QGen\Dialects\SqlServerCeDialect.cs" />
|
||||
<Compile Include="QGen\Dialects\SqlServerDialect.cs" />
|
||||
<Compile Include="QGen\ExpressionVisitor.cs" />
|
||||
<Compile Include="QGen\InsertQuery.cs" />
|
||||
<Compile Include="QGen\InsertQueryBuilder.cs" />
|
||||
<Compile Include="QGen\IQuery.cs" />
|
||||
<Compile Include="QGen\IQueryBuilder.cs" />
|
||||
<Compile Include="QGen\JoinBuilder.cs" />
|
||||
<Compile Include="QGen\PagingQueryDecorator.cs" />
|
||||
<Compile Include="QGen\QueryBuilder.cs" />
|
||||
<Compile Include="QGen\QueryFactory.cs" />
|
||||
<Compile Include="QGen\QueryQueueItem.cs" />
|
||||
<Compile Include="QGen\RowCountQueryDecorator.cs" />
|
||||
<Compile Include="QGen\SelectQuery.cs" />
|
||||
<Compile Include="QGen\SortBuilder.cs" />
|
||||
<Compile Include="QGen\SortColumn.cs" />
|
||||
<Compile Include="QGen\Table.cs" />
|
||||
<Compile Include="QGen\TableCollection.cs" />
|
||||
<Compile Include="QGen\UpdateQuery.cs" />
|
||||
<Compile Include="QGen\UpdateQueryBuilder.cs" />
|
||||
<Compile Include="QGen\View.cs" />
|
||||
<Compile Include="QGen\WhereBuilder.cs" />
|
||||
<Compile Include="Reflection\IReflectionStrategy.cs" />
|
||||
<Compile Include="Reflection\SimpleReflectionStrategy.cs" />
|
||||
<Compile Include="Reflection\ReflectionHelper.cs" />
|
||||
<Compile Include="SqlModesEnum.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
69
src/Marr.Data/Parameters/DbTypeBuilder.cs
Normal file
69
src/Marr.Data/Parameters/DbTypeBuilder.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace Marr.Data.Parameters
|
||||
{
|
||||
public class DbTypeBuilder : IDbTypeBuilder
|
||||
{
|
||||
public Enum GetDbType(Type type)
|
||||
{
|
||||
if (type == typeof(String))
|
||||
return DbType.String;
|
||||
|
||||
if (type == typeof(Int32))
|
||||
return DbType.Int32;
|
||||
|
||||
if (type == typeof(Decimal))
|
||||
return DbType.Decimal;
|
||||
|
||||
if (type == typeof(DateTime))
|
||||
return DbType.DateTime;
|
||||
|
||||
if (type == typeof(Boolean))
|
||||
return DbType.Boolean;
|
||||
|
||||
if (type == typeof(Int16))
|
||||
return DbType.Int16;
|
||||
|
||||
if (type == typeof(Single))
|
||||
return DbType.Single;
|
||||
|
||||
if (type == typeof(Int64))
|
||||
return DbType.Int64;
|
||||
|
||||
if (type == typeof(Double))
|
||||
return DbType.Double;
|
||||
|
||||
if (type == typeof(Byte))
|
||||
return DbType.Byte;
|
||||
|
||||
if (type == typeof(Byte[]))
|
||||
return DbType.Binary;
|
||||
|
||||
if (type == typeof(Guid))
|
||||
return DbType.Guid;
|
||||
|
||||
return DbType.Object;
|
||||
}
|
||||
|
||||
public void SetDbType(IDbDataParameter param, Enum dbType)
|
||||
{
|
||||
param.DbType = (DbType)dbType;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Marr.Data/Parameters/IDbTypeBuilder.cs
Normal file
30
src/Marr.Data/Parameters/IDbTypeBuilder.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace Marr.Data.Parameters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts from a .NET datatype to the appropriate DB type enum,
|
||||
/// and then adds the value to the appropriate property on the parameter.
|
||||
/// </summary>
|
||||
public interface IDbTypeBuilder
|
||||
{
|
||||
Enum GetDbType(Type type);
|
||||
void SetDbType(IDbDataParameter param, Enum dbType);
|
||||
}
|
||||
}
|
68
src/Marr.Data/Parameters/OleDbTypeBuilder.cs
Normal file
68
src/Marr.Data/Parameters/OleDbTypeBuilder.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.OleDb;
|
||||
|
||||
namespace Marr.Data.Parameters
|
||||
{
|
||||
public class OleDbTypeBuilder : IDbTypeBuilder
|
||||
{
|
||||
public Enum GetDbType(Type type)
|
||||
{
|
||||
if (type == typeof(String))
|
||||
return OleDbType.VarChar;
|
||||
|
||||
if (type == typeof(Int32))
|
||||
return OleDbType.Integer;
|
||||
|
||||
if (type == typeof(Decimal))
|
||||
return OleDbType.Decimal;
|
||||
|
||||
if (type == typeof(DateTime))
|
||||
return OleDbType.DBTimeStamp;
|
||||
|
||||
if (type == typeof(Boolean))
|
||||
return OleDbType.Boolean;
|
||||
|
||||
if (type == typeof(Int16))
|
||||
return OleDbType.SmallInt;
|
||||
|
||||
if (type == typeof(Int64))
|
||||
return OleDbType.BigInt;
|
||||
|
||||
if (type == typeof(Double))
|
||||
return OleDbType.Double;
|
||||
|
||||
if (type == typeof(Byte))
|
||||
return OleDbType.Binary;
|
||||
|
||||
if (type == typeof(Byte[]))
|
||||
return OleDbType.VarBinary;
|
||||
|
||||
if (type == typeof(Guid))
|
||||
return OleDbType.Guid;
|
||||
|
||||
return OleDbType.Variant;
|
||||
}
|
||||
|
||||
public void SetDbType(IDbDataParameter param, Enum dbType)
|
||||
{
|
||||
var oleDbParam = (OleDbParameter)param;
|
||||
oleDbParam.OleDbType = (OleDbType)dbType;
|
||||
}
|
||||
}
|
||||
}
|
121
src/Marr.Data/Parameters/ParameterChainMethods.cs
Normal file
121
src/Marr.Data/Parameters/ParameterChainMethods.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using Marr.Data.Converters;
|
||||
|
||||
namespace Marr.Data.Parameters
|
||||
{
|
||||
/// <summary>
|
||||
/// This class allows chaining methods to be called for convenience when adding a parameter.
|
||||
/// </summary>
|
||||
public class ParameterChainMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new parameter and adds it to the command's Parameters collection.
|
||||
/// </summary>
|
||||
/// <param name="command">The command that the parameter will be added to.</param>
|
||||
/// <param name="parameterName">The parameter name.</param>
|
||||
public ParameterChainMethods(DbCommand command, string parameterName, object value)
|
||||
{
|
||||
Parameter = command.CreateParameter();
|
||||
Parameter.ParameterName = parameterName;
|
||||
|
||||
// Convert null to DBNull.Value
|
||||
if (value == null)
|
||||
value = DBNull.Value;
|
||||
|
||||
Type valueType = value.GetType();
|
||||
|
||||
// Check for a registered IConverter
|
||||
IConverter converter = MapRepository.Instance.GetConverter(valueType);
|
||||
if (converter != null)
|
||||
{
|
||||
Parameter.Value = converter.ToDB(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parameter.Value = value;
|
||||
}
|
||||
|
||||
//// Determine the correct DbType based on the passed in value type
|
||||
//IDbTypeBuilder typeBuilder = MapRepository.Instance.DbTypeBuilder;
|
||||
//Enum dbType = typeBuilder.GetDbType(valueType);
|
||||
|
||||
//// Set the appropriate DbType property depending on the parameter type
|
||||
//typeBuilder.SetDbType(Parameter, dbType);
|
||||
|
||||
command.Parameters.Add(Parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the parameter.
|
||||
/// </summary>
|
||||
public IDbDataParameter Parameter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the direction of a parameter.
|
||||
/// </summary>
|
||||
/// <param name="direction">Determines the direction of a parameter.</param>
|
||||
/// <returns>Return a ParameterChainMethods object.</returns>
|
||||
public ParameterChainMethods Direction(ParameterDirection direction)
|
||||
{
|
||||
Parameter.Direction = direction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the direction of a parameter to 'Output'.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ParameterChainMethods Output()
|
||||
{
|
||||
Parameter.Direction = ParameterDirection.Output;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterChainMethods DBType(DbType dbType)
|
||||
{
|
||||
Parameter.DbType = dbType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterChainMethods Size(int size)
|
||||
{
|
||||
Parameter.Size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterChainMethods Precision(byte precision)
|
||||
{
|
||||
Parameter.Precision = precision;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterChainMethods Scale(byte scale)
|
||||
{
|
||||
Parameter.Scale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterChainMethods Name(string name)
|
||||
{
|
||||
Parameter.ParameterName = name;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
71
src/Marr.Data/Parameters/SqlDbTypeBuilder.cs
Normal file
71
src/Marr.Data/Parameters/SqlDbTypeBuilder.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* Copyright (C) 2008 - 2011 Jordan Marr
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace Marr.Data.Parameters
|
||||
{
|
||||
public class SqlDbTypeBuilder : IDbTypeBuilder
|
||||
{
|
||||
public Enum GetDbType(Type type)
|
||||
{
|
||||
if (type == typeof(String))
|
||||
return SqlDbType.VarChar;
|
||||
|
||||
if (type == typeof(Int32))
|
||||
return SqlDbType.Int;
|
||||
|
||||
if (type == typeof(Decimal))
|
||||
return SqlDbType.Decimal;
|
||||
|
||||
if (type == typeof(DateTime))
|
||||
return SqlDbType.DateTime;
|
||||
|
||||
if (type == typeof(Boolean))
|
||||
return SqlDbType.Bit;
|
||||
|
||||
if (type == typeof(Int16))
|
||||
return SqlDbType.SmallInt;
|
||||
|
||||
if (type == typeof(Int64))
|
||||
return SqlDbType.BigInt;
|
||||
|
||||
if (type == typeof(Double))
|
||||
return SqlDbType.Float;
|
||||
|
||||
if (type == typeof(Char))
|
||||
return SqlDbType.Char;
|
||||
|
||||
if (type == typeof(Byte))
|
||||
return SqlDbType.Binary;
|
||||
|
||||
if (type == typeof(Byte[]))
|
||||
return SqlDbType.VarBinary;
|
||||
|
||||
if (type == typeof(Guid))
|
||||
return SqlDbType.UniqueIdentifier;
|
||||
|
||||
return SqlDbType.Variant;
|
||||
}
|
||||
|
||||
public void SetDbType(IDbDataParameter param, Enum dbType)
|
||||
{
|
||||
var sqlDbParam = (SqlParameter)param;
|
||||
sqlDbParam.SqlDbType = (SqlDbType)dbType;
|
||||
}
|
||||
}
|
||||
}
|
41
src/Marr.Data/Properties/AssemblyInfo.cs
Normal file
41
src/Marr.Data/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 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("Marr.Data")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Marr.Data")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2011")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Share internals
|
||||
[assembly: InternalsVisibleTo("Marr.Data.Relationships")]
|
||||
[assembly: InternalsVisibleTo("Marr.Data.Tests")]
|
||||
|
||||
|
||||
// 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("6864f4d2-cd0f-4720-9c15-3085f1aa8293")]
|
||||
|
||||
// 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("3.17.*")]
|
||||
[assembly: AssemblyInformationalVersion("3.17")]
|
28
src/Marr.Data/QGen/DeleteQuery.cs
Normal file
28
src/Marr.Data/QGen/DeleteQuery.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class creates a SQL delete query.
|
||||
/// </summary>
|
||||
public class DeleteQuery : IQuery
|
||||
{
|
||||
protected Table TargetTable { get; set; }
|
||||
protected string WhereClause { get; set; }
|
||||
protected Dialect Dialect { get; set; }
|
||||
|
||||
public DeleteQuery(Dialect dialect, Table targetTable, string whereClause)
|
||||
{
|
||||
Dialect = dialect;
|
||||
TargetTable = targetTable;
|
||||
WhereClause = whereClause;
|
||||
}
|
||||
|
||||
public string Generate()
|
||||
{
|
||||
return string.Format("DELETE FROM {0} {1} ",
|
||||
Dialect.CreateToken(TargetTable.Name),
|
||||
WhereClause);
|
||||
}
|
||||
}
|
||||
}
|
72
src/Marr.Data/QGen/Dialects/Dialect.cs
Normal file
72
src/Marr.Data/QGen/Dialects/Dialect.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Marr.Data.QGen.Dialects
|
||||
{
|
||||
public class Dialect
|
||||
{
|
||||
/// <summary>
|
||||
/// The default token is surrounded by brackets.
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
public virtual string CreateToken(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string[] parts = token.Replace('[', new Char()).Replace(']', new Char()).Split('.');
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (string part in parts)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append(".");
|
||||
|
||||
sb.Append("[").Append(part).Append("]");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public virtual string IdentityQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasIdentityQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(IdentityQuery);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool SupportsBatchQueries
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string StartsWithFormat
|
||||
{
|
||||
get { return "({0} LIKE {1} + '%')"; }
|
||||
}
|
||||
|
||||
public virtual string EndsWithFormat
|
||||
{
|
||||
get { return "({0} LIKE '%' + {1})"; }
|
||||
}
|
||||
|
||||
public virtual string ContainsFormat
|
||||
{
|
||||
get { return "({0} LIKE '%' + {1} + '%')"; }
|
||||
}
|
||||
}
|
||||
}
|
17
src/Marr.Data/QGen/Dialects/FirebirdDialect.cs
Normal file
17
src/Marr.Data/QGen/Dialects/FirebirdDialect.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
namespace Marr.Data.QGen.Dialects
|
||||
{
|
||||
public class FirebirdDialect : Dialect
|
||||
{
|
||||
public override string CreateToken(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return token.Replace('[', new Char()).Replace(']', new Char());
|
||||
}
|
||||
}
|
||||
}
|
35
src/Marr.Data/QGen/Dialects/OracleDialect.cs
Normal file
35
src/Marr.Data/QGen/Dialects/OracleDialect.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Marr.Data.QGen.Dialects
|
||||
{
|
||||
public class OracleDialect : Dialect
|
||||
{
|
||||
public override string CreateToken(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string[] parts = token.Replace('[', new Char()).Replace(']', new Char()).Split('.');
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (string part in parts)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append(".");
|
||||
|
||||
bool hasSpaces = part.Contains(' ');
|
||||
|
||||
if (hasSpaces)
|
||||
sb.Append("[").Append(part).Append("]");
|
||||
else
|
||||
sb.Append(part);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
21
src/Marr.Data/QGen/Dialects/SqlServerCeDialect.cs
Normal file
21
src/Marr.Data/QGen/Dialects/SqlServerCeDialect.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace Marr.Data.QGen.Dialects
|
||||
{
|
||||
public class SqlServerCeDialect : Dialect
|
||||
{
|
||||
public override string IdentityQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
return "SELECT @@IDENTITY;";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool SupportsBatchQueries
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
src/Marr.Data/QGen/Dialects/SqlServerDialect.cs
Normal file
13
src/Marr.Data/QGen/Dialects/SqlServerDialect.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Marr.Data.QGen.Dialects
|
||||
{
|
||||
public class SqlServerDialect : Dialect
|
||||
{
|
||||
public override string IdentityQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
return "SELECT SCOPE_IDENTITY();";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
src/Marr.Data/QGen/Dialects/SqliteDialect.cs
Normal file
28
src/Marr.Data/QGen/Dialects/SqliteDialect.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
namespace Marr.Data.QGen.Dialects
|
||||
{
|
||||
public class SqliteDialect : Dialect
|
||||
{
|
||||
public override string IdentityQuery
|
||||
{
|
||||
get
|
||||
{
|
||||
return "SELECT last_insert_rowid();";
|
||||
}
|
||||
}
|
||||
|
||||
public override string StartsWithFormat
|
||||
{
|
||||
get { return "({0} LIKE {1} || '%')"; }
|
||||
}
|
||||
|
||||
public override string EndsWithFormat
|
||||
{
|
||||
get { return "({0} LIKE '%' || {1})"; }
|
||||
}
|
||||
|
||||
public override string ContainsFormat
|
||||
{
|
||||
get { return "({0} LIKE '%' || {1} || '%')"; }
|
||||
}
|
||||
}
|
||||
}
|
146
src/Marr.Data/QGen/ExpressionVisitor.cs
Normal file
146
src/Marr.Data/QGen/ExpressionVisitor.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
/* This class was copied from Mehfuz's LinqExtender project, which is available from github.
|
||||
* http://mehfuzh.github.com/LinqExtender/
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
///<summary>
|
||||
/// Expression visitor
|
||||
///</summary>
|
||||
public class ExpressionVisitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Visits expression and delegates call to different to branch.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression Visit(Expression expression)
|
||||
{
|
||||
if (expression == null)
|
||||
return null;
|
||||
|
||||
switch (expression.NodeType)
|
||||
{
|
||||
case ExpressionType.Lambda:
|
||||
return VisitLamda((LambdaExpression)expression);
|
||||
case ExpressionType.ArrayLength:
|
||||
case ExpressionType.Convert:
|
||||
case ExpressionType.ConvertChecked:
|
||||
case ExpressionType.Negate:
|
||||
case ExpressionType.UnaryPlus:
|
||||
case ExpressionType.NegateChecked:
|
||||
case ExpressionType.Not:
|
||||
case ExpressionType.Quote:
|
||||
case ExpressionType.TypeAs:
|
||||
return VisitUnary((UnaryExpression)expression);
|
||||
case ExpressionType.Add:
|
||||
case ExpressionType.AddChecked:
|
||||
case ExpressionType.And:
|
||||
case ExpressionType.AndAlso:
|
||||
case ExpressionType.ArrayIndex:
|
||||
case ExpressionType.Coalesce:
|
||||
case ExpressionType.Divide:
|
||||
case ExpressionType.Equal:
|
||||
case ExpressionType.ExclusiveOr:
|
||||
case ExpressionType.GreaterThan:
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
case ExpressionType.LeftShift:
|
||||
case ExpressionType.LessThan:
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
case ExpressionType.Modulo:
|
||||
case ExpressionType.Multiply:
|
||||
case ExpressionType.MultiplyChecked:
|
||||
case ExpressionType.NotEqual:
|
||||
case ExpressionType.Or:
|
||||
case ExpressionType.OrElse:
|
||||
case ExpressionType.Power:
|
||||
case ExpressionType.RightShift:
|
||||
case ExpressionType.Subtract:
|
||||
case ExpressionType.SubtractChecked:
|
||||
return VisitBinary((BinaryExpression)expression);
|
||||
case ExpressionType.Call:
|
||||
return VisitMethodCall((MethodCallExpression)expression);
|
||||
case ExpressionType.Constant:
|
||||
return VisitConstant((ConstantExpression)expression);
|
||||
case ExpressionType.MemberAccess:
|
||||
return VisitMemberAccess((MemberExpression)expression);
|
||||
case ExpressionType.Parameter:
|
||||
return VisitParameter((ParameterExpression)expression);
|
||||
|
||||
}
|
||||
throw new ArgumentOutOfRangeException("expression", expression.NodeType.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the constance expression. To be implemented by user.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitConstant(ConstantExpression expression)
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the memeber access expression. To be implemented by user.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitMemberAccess(MemberExpression expression)
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the method call expression. To be implemented by user.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitMethodCall(MethodCallExpression expression)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the binary expression.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitBinary(BinaryExpression expression)
|
||||
{
|
||||
Visit(expression.Left);
|
||||
Visit(expression.Right);
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the unary expression.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitUnary(UnaryExpression expression)
|
||||
{
|
||||
Visit(expression.Operand);
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the lamda expression.
|
||||
/// </summary>
|
||||
/// <param name="lambdaExpression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitLamda(LambdaExpression lambdaExpression)
|
||||
{
|
||||
Visit(lambdaExpression.Body);
|
||||
return lambdaExpression;
|
||||
}
|
||||
|
||||
private Expression VisitParameter(ParameterExpression expression)
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
11
src/Marr.Data/QGen/IQuery.cs
Normal file
11
src/Marr.Data/QGen/IQuery.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Marr.Data.QGen
|
||||
{
|
||||
internal interface IQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a SQL query for a given entity.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string Generate();
|
||||
}
|
||||
}
|
12
src/Marr.Data/QGen/IQueryBuilder.cs
Normal file
12
src/Marr.Data/QGen/IQueryBuilder.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Marr.Data.QGen
|
||||
{
|
||||
public interface IQueryBuilder
|
||||
{
|
||||
string BuildQuery();
|
||||
}
|
||||
|
||||
public interface ISortQueryBuilder : IQueryBuilder
|
||||
{
|
||||
string BuildQuery(bool useAltNames);
|
||||
}
|
||||
}
|
67
src/Marr.Data/QGen/InsertQuery.cs
Normal file
67
src/Marr.Data/QGen/InsertQuery.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System.Text;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Data.Common;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class creates an insert query.
|
||||
/// </summary>
|
||||
public class InsertQuery : IQuery
|
||||
{
|
||||
protected Dialect Dialect { get; set; }
|
||||
protected string Target { get; set; }
|
||||
protected ColumnMapCollection Columns { get; set; }
|
||||
protected DbCommand Command { get; set; }
|
||||
|
||||
public InsertQuery(Dialect dialect, ColumnMapCollection columns, DbCommand command, string target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(target))
|
||||
{
|
||||
throw new DataMappingException("A target table must be passed in or set in a TableAttribute.");
|
||||
}
|
||||
Dialect = dialect;
|
||||
Target = target;
|
||||
Columns = columns;
|
||||
Command = command;
|
||||
}
|
||||
|
||||
public virtual string Generate()
|
||||
{
|
||||
StringBuilder sql = new StringBuilder();
|
||||
StringBuilder values = new StringBuilder(") VALUES (");
|
||||
|
||||
sql.AppendFormat("INSERT INTO {0} (", Dialect.CreateToken(Target));
|
||||
|
||||
int sqlStartIndex = sql.Length;
|
||||
int valuesStartIndex = values.Length;
|
||||
|
||||
foreach (DbParameter p in Command.Parameters)
|
||||
{
|
||||
var c = Columns.GetByColumnName(p.ParameterName);
|
||||
|
||||
if (c == null)
|
||||
break; // All insert columns have been added
|
||||
|
||||
if (sql.Length > sqlStartIndex)
|
||||
sql.Append(",");
|
||||
|
||||
if (values.Length > valuesStartIndex)
|
||||
values.Append(",");
|
||||
|
||||
if (!c.ColumnInfo.IsAutoIncrement)
|
||||
{
|
||||
sql.AppendFormat(Dialect.CreateToken(c.ColumnInfo.Name));
|
||||
values.AppendFormat("{0}{1}", Command.ParameterPrefix(), p.ParameterName);
|
||||
}
|
||||
}
|
||||
|
||||
values.Append(")");
|
||||
|
||||
sql.Append(values);
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
}
|
||||
}
|
203
src/Marr.Data/QGen/InsertQueryBuilder.cs
Normal file
203
src/Marr.Data/QGen/InsertQueryBuilder.cs
Normal file
|
@ -0,0 +1,203 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Linq.Expressions;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
public class InsertQueryBuilder<T> : IQueryBuilder
|
||||
{
|
||||
private DataMapper _db;
|
||||
private string _tableName;
|
||||
private T _entity;
|
||||
private MappingHelper _mappingHelper;
|
||||
private ColumnMapCollection _mappings;
|
||||
private SqlModes _previousSqlMode;
|
||||
private bool _generateQuery = true;
|
||||
private bool _getIdentityValue;
|
||||
private Dialect _dialect;
|
||||
private ColumnMapCollection _columnsToInsert;
|
||||
|
||||
public InsertQueryBuilder()
|
||||
{
|
||||
// Used only for unit testing with mock frameworks
|
||||
}
|
||||
|
||||
public InsertQueryBuilder(DataMapper db)
|
||||
{
|
||||
_db = db;
|
||||
_tableName = MapRepository.Instance.GetTableName(typeof(T));
|
||||
_previousSqlMode = _db.SqlMode;
|
||||
_mappingHelper = new MappingHelper(_db);
|
||||
_mappings = MapRepository.Instance.GetColumns(typeof(T));
|
||||
_dialect = QueryFactory.CreateDialect(_db);
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> TableName(string tableName)
|
||||
{
|
||||
_tableName = tableName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> QueryText(string queryText)
|
||||
{
|
||||
_generateQuery = false;
|
||||
_db.Command.CommandText = queryText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> Entity(T entity)
|
||||
{
|
||||
_entity = entity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an identity query to get the value of an autoincrement field.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual InsertQueryBuilder<T> GetIdentity()
|
||||
{
|
||||
if (!_dialect.HasIdentityQuery)
|
||||
{
|
||||
string err = string.Format("The current dialect '{0}' does not have an identity query implemented.", _dialect.ToString());
|
||||
throw new DataMappingException(err);
|
||||
}
|
||||
|
||||
_getIdentityValue = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> ColumnsIncluding(params Expression<Func<T, object>>[] properties)
|
||||
{
|
||||
List<string> columnList = new List<string>();
|
||||
|
||||
foreach (var column in properties)
|
||||
{
|
||||
columnList.Add(column.GetMemberName());
|
||||
}
|
||||
|
||||
return ColumnsIncluding(columnList.ToArray());
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> ColumnsIncluding(params string[] properties)
|
||||
{
|
||||
_columnsToInsert = new ColumnMapCollection();
|
||||
|
||||
foreach (string propertyName in properties)
|
||||
{
|
||||
_columnsToInsert.Add(_mappings.GetByFieldName(propertyName));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> ColumnsExcluding(params Expression<Func<T, object>>[] properties)
|
||||
{
|
||||
List<string> columnList = new List<string>();
|
||||
|
||||
foreach (var column in properties)
|
||||
{
|
||||
columnList.Add(column.GetMemberName());
|
||||
}
|
||||
|
||||
return ColumnsExcluding(columnList.ToArray());
|
||||
}
|
||||
|
||||
public virtual InsertQueryBuilder<T> ColumnsExcluding(params string[] properties)
|
||||
{
|
||||
_columnsToInsert = new ColumnMapCollection();
|
||||
|
||||
_columnsToInsert.AddRange(_mappings);
|
||||
|
||||
foreach (string propertyName in properties)
|
||||
{
|
||||
_columnsToInsert.RemoveAll(c => c.FieldName == propertyName);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual object Execute()
|
||||
{
|
||||
if (_generateQuery)
|
||||
{
|
||||
BuildQuery();
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAppendIdentityQuery();
|
||||
_mappingHelper.CreateParameters<T>(_entity, _mappings.NonReturnValues, _generateQuery);
|
||||
}
|
||||
|
||||
object scalar = null;
|
||||
|
||||
try
|
||||
{
|
||||
_db.OpenConnection();
|
||||
|
||||
scalar = _db.Command.ExecuteScalar();
|
||||
|
||||
if (_getIdentityValue && !_dialect.SupportsBatchQueries)
|
||||
{
|
||||
// Run identity query as a separate query
|
||||
_db.Command.CommandText = _dialect.IdentityQuery;
|
||||
scalar = _db.Command.ExecuteScalar();
|
||||
}
|
||||
|
||||
_mappingHelper.SetOutputValues<T>(_entity, _mappings.OutputFields);
|
||||
if (scalar != null)
|
||||
{
|
||||
_mappingHelper.SetOutputValues<T>(_entity, _mappings.ReturnValues, scalar);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db.CloseConnection();
|
||||
}
|
||||
|
||||
|
||||
if (_generateQuery)
|
||||
{
|
||||
// Return to previous sql mode
|
||||
_db.SqlMode = _previousSqlMode;
|
||||
}
|
||||
|
||||
return scalar;
|
||||
}
|
||||
|
||||
public virtual string BuildQuery()
|
||||
{
|
||||
if (_entity == null)
|
||||
throw new ArgumentNullException("You must specify an entity to insert.");
|
||||
|
||||
// Override SqlMode since we know this will be a text query
|
||||
_db.SqlMode = SqlModes.Text;
|
||||
|
||||
var columns = _columnsToInsert ?? _mappings;
|
||||
|
||||
_mappingHelper.CreateParameters<T>(_entity, columns, _generateQuery);
|
||||
IQuery query = QueryFactory.CreateInsertQuery(columns, _db, _tableName);
|
||||
|
||||
_db.Command.CommandText = query.Generate();
|
||||
|
||||
TryAppendIdentityQuery();
|
||||
|
||||
return _db.Command.CommandText;
|
||||
}
|
||||
|
||||
private void TryAppendIdentityQuery()
|
||||
{
|
||||
if (_getIdentityValue && _dialect.SupportsBatchQueries)
|
||||
{
|
||||
// Append a batched identity query
|
||||
if (!_db.Command.CommandText.EndsWith(";"))
|
||||
{
|
||||
_db.Command.CommandText += ";";
|
||||
}
|
||||
_db.Command.CommandText += _dialect.IdentityQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
src/Marr.Data/QGen/JoinBuilder.cs
Normal file
36
src/Marr.Data/QGen/JoinBuilder.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Data.Common;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class overrides the WhereBuilder which utilizes the ExpressionVisitor base class,
|
||||
/// and it is responsible for translating the lambda expression into a "JOIN ON" clause.
|
||||
/// It populates the protected string builder, which outputs the "JOIN ON" clause when the ToString method is called.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The entity that is on the left side of the join.</typeparam>
|
||||
/// <typeparam name="T2">The entity that is on the right side of the join.</typeparam>
|
||||
public class JoinBuilder<T, T2> : WhereBuilder<T>
|
||||
{
|
||||
public JoinBuilder(DbCommand command, Dialect dialect, Expression<Func<T, T2, bool>> filter, TableCollection tables)
|
||||
: base(command, dialect, filter.Body, tables, false, true)
|
||||
{ }
|
||||
|
||||
protected override string PrefixText
|
||||
{
|
||||
get
|
||||
{
|
||||
return "ON";
|
||||
}
|
||||
}
|
||||
|
||||
protected override Expression VisitMemberAccess(MemberExpression expression)
|
||||
{
|
||||
string fqColumn = GetFullyQualifiedColumnName(expression.Member, expression.Expression.Type);
|
||||
_sb.Append(fqColumn);
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
232
src/Marr.Data/QGen/PagingQueryDecorator.cs
Normal file
232
src/Marr.Data/QGen/PagingQueryDecorator.cs
Normal file
|
@ -0,0 +1,232 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// Decorates the SelectQuery by wrapping it in a paging query.
|
||||
/// </summary>
|
||||
public class PagingQueryDecorator : IQuery
|
||||
{
|
||||
private SelectQuery _innerQuery;
|
||||
private int _firstRow;
|
||||
private int _lastRow;
|
||||
|
||||
public PagingQueryDecorator(SelectQuery innerQuery, int skip, int take)
|
||||
{
|
||||
if (string.IsNullOrEmpty(innerQuery.OrderBy.ToString()))
|
||||
{
|
||||
throw new DataMappingException("A paged query must specify an order by clause.");
|
||||
}
|
||||
|
||||
_innerQuery = innerQuery;
|
||||
_firstRow = skip + 1;
|
||||
_lastRow = skip + take;
|
||||
}
|
||||
|
||||
public string Generate()
|
||||
{
|
||||
// Decide which type of paging query to create
|
||||
|
||||
if (_innerQuery.IsView || _innerQuery.IsJoin)
|
||||
{
|
||||
return ComplexPaging();
|
||||
}
|
||||
return SimplePaging();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a query that pages a simple inner query.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string SimplePaging()
|
||||
{
|
||||
// Create paged query
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
sql.AppendLine("WITH RowNumCTE AS");
|
||||
sql.AppendLine("(");
|
||||
_innerQuery.BuildSelectClause(sql);
|
||||
BuildRowNumberColumn(sql);
|
||||
_innerQuery.BuildFromClause(sql);
|
||||
_innerQuery.BuildJoinClauses(sql);
|
||||
_innerQuery.BuildWhereClause(sql);
|
||||
sql.AppendLine(")");
|
||||
BuildSimpleOuterSelect(sql);
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a query that pages a view or joined inner query.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string ComplexPaging()
|
||||
{
|
||||
// Create paged query
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
sql.AppendLine("WITH GroupCTE AS (");
|
||||
BuildSelectClause(sql);
|
||||
BuildGroupColumn(sql);
|
||||
_innerQuery.BuildFromClause(sql);
|
||||
_innerQuery.BuildJoinClauses(sql);
|
||||
_innerQuery.BuildWhereClause(sql);
|
||||
sql.AppendLine("),");
|
||||
sql.AppendLine("RowNumCTE AS (");
|
||||
sql.AppendLine("SELECT *");
|
||||
BuildRowNumberColumn(sql);
|
||||
sql.AppendLine("FROM GroupCTE");
|
||||
sql.AppendLine("WHERE GroupRow = 1");
|
||||
sql.AppendLine(")");
|
||||
_innerQuery.BuildSelectClause(sql);
|
||||
_innerQuery.BuildFromClause(sql);
|
||||
_innerQuery.BuildJoinClauses(sql);
|
||||
BuildJoinBackToCTE(sql);
|
||||
sql.AppendFormat("WHERE RowNumber BETWEEN {0} AND {1}", _firstRow, _lastRow);
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
|
||||
private void BuildJoinBackToCTE(StringBuilder sql)
|
||||
{
|
||||
Table baseTable = GetBaseTable();
|
||||
sql.AppendLine("INNER JOIN RowNumCTE cte");
|
||||
int pksAdded = 0;
|
||||
foreach (var pk in baseTable.Columns.PrimaryKeys)
|
||||
{
|
||||
if (pksAdded > 0)
|
||||
sql.Append(" AND ");
|
||||
|
||||
string cteQueryPkName = _innerQuery.NameOrAltName(pk.ColumnInfo);
|
||||
string outerQueryPkName = _innerQuery.IsJoin ? pk.ColumnInfo.Name : _innerQuery.NameOrAltName(pk.ColumnInfo);
|
||||
sql.AppendFormat("ON cte.{0} = {1} ", cteQueryPkName, _innerQuery.Dialect.CreateToken(string.Concat("t0", ".", outerQueryPkName)));
|
||||
pksAdded++;
|
||||
}
|
||||
sql.AppendLine();
|
||||
}
|
||||
|
||||
private void BuildSimpleOuterSelect(StringBuilder sql)
|
||||
{
|
||||
sql.Append("SELECT ");
|
||||
int startIndex = sql.Length;
|
||||
|
||||
// COLUMNS
|
||||
foreach (Table join in _innerQuery.Tables)
|
||||
{
|
||||
for (int i = 0; i < join.Columns.Count; i++)
|
||||
{
|
||||
var c = join.Columns[i];
|
||||
|
||||
if (sql.Length > startIndex)
|
||||
sql.Append(",");
|
||||
|
||||
string token = _innerQuery.NameOrAltName(c.ColumnInfo);
|
||||
sql.Append(_innerQuery.Dialect.CreateToken(token));
|
||||
}
|
||||
}
|
||||
|
||||
sql.AppendLine("FROM RowNumCTE");
|
||||
sql.AppendFormat("WHERE RowNumber BETWEEN {0} AND {1}", _firstRow, _lastRow).AppendLine();
|
||||
sql.AppendLine("ORDER BY RowNumber ASC;");
|
||||
}
|
||||
|
||||
private void BuildGroupColumn(StringBuilder sql)
|
||||
{
|
||||
bool isView = _innerQuery.IsView;
|
||||
sql.AppendFormat(", ROW_NUMBER() OVER (PARTITION BY {0} {1}) As GroupRow ", BuildBaseTablePKColumns(isView), _innerQuery.OrderBy.BuildQuery(isView));
|
||||
}
|
||||
|
||||
private string BuildBaseTablePKColumns(bool useAltName = true)
|
||||
{
|
||||
Table baseTable = GetBaseTable();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var col in baseTable.Columns.PrimaryKeys)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.AppendLine(", ");
|
||||
|
||||
string columnName = useAltName ?
|
||||
_innerQuery.NameOrAltName(col.ColumnInfo) :
|
||||
col.ColumnInfo.Name;
|
||||
|
||||
sb.AppendFormat(_innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", columnName)));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void BuildRowNumberColumn(StringBuilder sql)
|
||||
{
|
||||
string orderBy = _innerQuery.OrderBy.ToString();
|
||||
// Remove table prefixes from order columns
|
||||
foreach (Table t in _innerQuery.Tables)
|
||||
{
|
||||
orderBy = orderBy.Replace(string.Format("[{0}].", t.Alias), "");
|
||||
}
|
||||
|
||||
sql.AppendFormat(", ROW_NUMBER() OVER ({0}) As RowNumber ", orderBy);
|
||||
}
|
||||
|
||||
private Table GetBaseTable()
|
||||
{
|
||||
Table baseTable = null;
|
||||
if (_innerQuery.Tables[0] is View)
|
||||
{
|
||||
baseTable = (_innerQuery.Tables[0] as View).Tables[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
baseTable = _innerQuery.Tables[0];
|
||||
}
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
public void BuildSelectClause(StringBuilder sql)
|
||||
{
|
||||
List<string> appended = new List<string>();
|
||||
|
||||
sql.Append("SELECT ");
|
||||
|
||||
int startIndex = sql.Length;
|
||||
|
||||
// COLUMNS
|
||||
foreach (Table join in _innerQuery.Tables)
|
||||
{
|
||||
for (int i = 0; i < join.Columns.Count; i++)
|
||||
{
|
||||
var c = join.Columns[i];
|
||||
|
||||
if (sql.Length > startIndex && sql[sql.Length - 1] != ',')
|
||||
sql.Append(",");
|
||||
|
||||
if (join is View)
|
||||
{
|
||||
string token = _innerQuery.Dialect.CreateToken(string.Concat(join.Alias, ".", _innerQuery.NameOrAltName(c.ColumnInfo)));
|
||||
if (appended.Contains(token))
|
||||
continue;
|
||||
|
||||
sql.Append(token);
|
||||
appended.Add(token);
|
||||
}
|
||||
else
|
||||
{
|
||||
string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
|
||||
if (appended.Contains(token))
|
||||
continue;
|
||||
|
||||
sql.Append(_innerQuery.Dialect.CreateToken(token));
|
||||
|
||||
if (_innerQuery.UseAltName && c.ColumnInfo.AltName != null && c.ColumnInfo.AltName != c.ColumnInfo.Name)
|
||||
{
|
||||
string altName = c.ColumnInfo.AltName;
|
||||
sql.AppendFormat(" AS {0}", altName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
618
src/Marr.Data/QGen/QueryBuilder.cs
Normal file
618
src/Marr.Data/QGen/QueryBuilder.cs
Normal file
|
@ -0,0 +1,618 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Data.Common;
|
||||
using System.Collections;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is responsible for building a select query.
|
||||
/// It uses chaining methods to provide a fluent interface for creating select queries.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class QueryBuilder<T> : ExpressionVisitor, IEnumerable<T>, IQueryBuilder
|
||||
{
|
||||
#region - Private Members -
|
||||
|
||||
private DataMapper _db;
|
||||
private Dialect _dialect;
|
||||
private TableCollection _tables;
|
||||
private WhereBuilder<T> _whereBuilder;
|
||||
private SortBuilder<T> _sortBuilder;
|
||||
private bool _isGraph = false;
|
||||
private bool _isFromView = false;
|
||||
private bool _isFromTable = false;
|
||||
private bool _isJoin = false;
|
||||
private bool _isManualQuery = false;
|
||||
private bool _enablePaging = false;
|
||||
private int _skip;
|
||||
private int _take;
|
||||
private string _queryText;
|
||||
private List<MemberInfo> _childrenToLoad;
|
||||
private SortBuilder<T> SortBuilder
|
||||
{
|
||||
get
|
||||
{
|
||||
// Lazy load
|
||||
if (_sortBuilder == null)
|
||||
{
|
||||
bool useAltNames = _isFromView || _isGraph || _isJoin;
|
||||
_sortBuilder = new SortBuilder<T>(this, _db, _whereBuilder, _dialect, _tables, useAltNames);
|
||||
}
|
||||
|
||||
return _sortBuilder;
|
||||
}
|
||||
}
|
||||
private List<T> _results = new List<T>();
|
||||
private EntityGraph _entityGraph;
|
||||
private EntityGraph EntGraph
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_entityGraph == null)
|
||||
{
|
||||
_entityGraph = new EntityGraph(typeof(T), _results);
|
||||
}
|
||||
|
||||
return _entityGraph;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Constructor -
|
||||
|
||||
public QueryBuilder()
|
||||
{
|
||||
// Used only for unit testing with mock frameworks
|
||||
}
|
||||
|
||||
public QueryBuilder(DataMapper db, Dialect dialect)
|
||||
{
|
||||
_db = db;
|
||||
_dialect = dialect;
|
||||
_tables = new TableCollection();
|
||||
_tables.Add(new Table(typeof(T)));
|
||||
_childrenToLoad = new List<MemberInfo>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Fluent Methods -
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the base table name that will be used in the query.
|
||||
/// </summary>
|
||||
[Obsolete("This method is obsolete. Use either the FromTable or FromView method instead.", true)]
|
||||
public virtual QueryBuilder<T> From(string tableName)
|
||||
{
|
||||
return FromView(tableName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the base view name that will be used in the query.
|
||||
/// Will try to use the mapped "AltName" values when loading the columns.
|
||||
/// </summary>
|
||||
public virtual QueryBuilder<T> FromView(string viewName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(viewName))
|
||||
throw new ArgumentNullException("view");
|
||||
|
||||
_isFromView = true;
|
||||
|
||||
// Replace the base table with a view with tables
|
||||
if (_tables[0] is View)
|
||||
{
|
||||
(_tables[0] as View).Name = viewName;
|
||||
}
|
||||
else
|
||||
{
|
||||
View view = new View(viewName, _tables.ToArray());
|
||||
_tables.ReplaceBaseTable(view);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the base table name that will be used in the query.
|
||||
/// Will not try to use the mapped "AltName" values when loading the columns.
|
||||
/// </summary>
|
||||
public virtual QueryBuilder<T> FromTable(string table)
|
||||
{
|
||||
if (string.IsNullOrEmpty(table))
|
||||
throw new ArgumentNullException("view");
|
||||
|
||||
_isFromTable = true;
|
||||
|
||||
// Override the base table name
|
||||
_tables[0].Name = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to manually specify the query text.
|
||||
/// </summary>
|
||||
public virtual QueryBuilder<T> QueryText(string queryText)
|
||||
{
|
||||
_isManualQuery = true;
|
||||
_queryText = queryText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If no parameters are passed in, this method instructs the DataMapper to load all related entities in the graph.
|
||||
/// If specific entities are passed in, only these relationships will be loaded.
|
||||
/// </summary>
|
||||
/// <param name="childrenToLoad">A list of related child entites to load (passed in as properties / lambda expressions).</param>
|
||||
public virtual QueryBuilder<T> Graph(params Expression<Func<T, object>>[] childrenToLoad)
|
||||
{
|
||||
TableCollection tablesInView = new TableCollection();
|
||||
if (childrenToLoad.Length > 0)
|
||||
{
|
||||
// Add base table
|
||||
tablesInView.Add(_tables[0]);
|
||||
|
||||
foreach (var exp in childrenToLoad)
|
||||
{
|
||||
MemberInfo child = (exp.Body as MemberExpression).Member;
|
||||
|
||||
var node = EntGraph.Where(g => g.Member != null && g.Member.EqualsMember(child)).FirstOrDefault();
|
||||
if (node != null)
|
||||
{
|
||||
tablesInView.Add(new Table(node.EntityType, JoinType.None));
|
||||
}
|
||||
|
||||
if (!_childrenToLoad.ContainsMember(child))
|
||||
{
|
||||
_childrenToLoad.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add all tables in the graph
|
||||
foreach (var node in EntGraph)
|
||||
{
|
||||
tablesInView.Add(new Table(node.EntityType, JoinType.None));
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the base table with a view with tables
|
||||
View view = new View(_tables[0].Name, tablesInView.ToArray());
|
||||
_tables.ReplaceBaseTable(view);
|
||||
|
||||
_isGraph = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Page(int pageNumber, int pageSize)
|
||||
{
|
||||
_enablePaging = true;
|
||||
_skip = (pageNumber - 1) * pageSize;
|
||||
_take = pageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
private string[] ParseChildrenToLoad(Expression<Func<T, object>>[] childrenToLoad)
|
||||
{
|
||||
List<string> entitiesToLoad = new List<string>();
|
||||
|
||||
// Parse relationship member names from expression array
|
||||
foreach (var exp in childrenToLoad)
|
||||
{
|
||||
MemberInfo member = (exp.Body as MemberExpression).Member;
|
||||
entitiesToLoad.Add(member.Name);
|
||||
|
||||
}
|
||||
|
||||
return entitiesToLoad.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to interact with the DbDataReader to manually load entities.
|
||||
/// </summary>
|
||||
/// <param name="readerAction">An action that takes a DbDataReader.</param>
|
||||
public virtual void DataReader(Action<DbDataReader> readerAction)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_queryText))
|
||||
throw new ArgumentNullException("The query text cannot be blank.");
|
||||
|
||||
var mappingHelper = new MappingHelper(_db);
|
||||
_db.Command.CommandText = _queryText;
|
||||
|
||||
try
|
||||
{
|
||||
_db.OpenConnection();
|
||||
using (DbDataReader reader = _db.Command.ExecuteReader())
|
||||
{
|
||||
readerAction.Invoke(reader);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db.CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetRowCount()
|
||||
{
|
||||
SqlModes previousSqlMode = _db.SqlMode;
|
||||
|
||||
// Generate a row count query
|
||||
string where = _whereBuilder != null ? _whereBuilder.ToString() : string.Empty;
|
||||
|
||||
bool useAltNames = _isFromView || _isGraph || _isJoin;
|
||||
IQuery query = QueryFactory.CreateRowCountSelectQuery(_tables, _db, where, SortBuilder, useAltNames);
|
||||
string queryText = query.Generate();
|
||||
|
||||
_db.SqlMode = SqlModes.Text;
|
||||
int count = Convert.ToInt32(_db.ExecuteScalar(queryText));
|
||||
|
||||
_db.SqlMode = previousSqlMode;
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the query and returns a list of results.
|
||||
/// </summary>
|
||||
/// <returns>A list of query results of type T.</returns>
|
||||
public virtual List<T> ToList()
|
||||
{
|
||||
SqlModes previousSqlMode = _db.SqlMode;
|
||||
|
||||
ValidateQuery();
|
||||
|
||||
BuildQueryOrAppendClauses();
|
||||
|
||||
if (_isGraph || _isJoin)
|
||||
{
|
||||
_results = (List<T>)_db.QueryToGraph<T>(_queryText, EntGraph, _childrenToLoad);
|
||||
}
|
||||
else
|
||||
{
|
||||
_results = (List<T>)_db.Query<T>(_queryText, _results, _isFromView);
|
||||
}
|
||||
|
||||
// Return to previous sql mode
|
||||
_db.SqlMode = previousSqlMode;
|
||||
|
||||
return _results;
|
||||
}
|
||||
|
||||
private void ValidateQuery()
|
||||
{
|
||||
if (_isManualQuery && _isFromView)
|
||||
throw new InvalidOperationException("Cannot use FromView in conjunction with QueryText");
|
||||
|
||||
if (_isManualQuery && _isFromTable)
|
||||
throw new InvalidOperationException("Cannot use FromTable in conjunction with QueryText");
|
||||
|
||||
if (_isManualQuery && _isJoin)
|
||||
throw new InvalidOperationException("Cannot use Join in conjuntion with QueryText");
|
||||
|
||||
if (_isManualQuery && _enablePaging)
|
||||
throw new InvalidOperationException("Cannot use Page, Skip or Take in conjunction with QueryText");
|
||||
|
||||
if (_isJoin && _isFromView)
|
||||
throw new InvalidOperationException("Cannot use FromView in conjunction with Join");
|
||||
|
||||
if (_isJoin && _isFromTable)
|
||||
throw new InvalidOperationException("Cannot use FromView in conjunction with Join");
|
||||
|
||||
if (_isJoin && _isGraph)
|
||||
throw new InvalidOperationException("Cannot use Graph in conjunction with Join");
|
||||
|
||||
if (_isFromView && _isFromTable)
|
||||
throw new InvalidOperationException("Cannot use FromView in conjunction with FromTable");
|
||||
}
|
||||
|
||||
private void BuildQueryOrAppendClauses()
|
||||
{
|
||||
if (_queryText == null)
|
||||
{
|
||||
// Build entire query
|
||||
_db.SqlMode = SqlModes.Text;
|
||||
BuildQuery();
|
||||
}
|
||||
else if (_whereBuilder != null || _sortBuilder != null)
|
||||
{
|
||||
_db.SqlMode = SqlModes.Text;
|
||||
if (_whereBuilder != null)
|
||||
{
|
||||
// Append a where clause to an existing query
|
||||
_queryText = string.Concat(_queryText, " ", _whereBuilder.ToString());
|
||||
}
|
||||
|
||||
if (_sortBuilder != null)
|
||||
{
|
||||
// Append an order clause to an existing query
|
||||
_queryText = string.Concat(_queryText, " ", _sortBuilder.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string BuildQuery()
|
||||
{
|
||||
// Generate a query
|
||||
string where = _whereBuilder != null ? _whereBuilder.ToString() : string.Empty;
|
||||
|
||||
bool useAltNames = _isFromView || _isGraph || _isJoin;
|
||||
|
||||
IQuery query = null;
|
||||
if (_enablePaging)
|
||||
{
|
||||
query = QueryFactory.CreatePagingSelectQuery(_tables, _db, where, SortBuilder, useAltNames, _skip, _take);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = QueryFactory.CreateSelectQuery(_tables, _db, where, SortBuilder, useAltNames);
|
||||
}
|
||||
|
||||
_queryText = query.Generate();
|
||||
|
||||
return _queryText;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Helper Methods -
|
||||
|
||||
private ColumnMapCollection GetColumns(IEnumerable<string> entitiesToLoad)
|
||||
{
|
||||
// If QueryToGraph<T> and no child load entities are specified, load all children
|
||||
bool useAltNames = _isFromView || _isGraph || _isJoin;
|
||||
bool loadAllChildren = useAltNames && entitiesToLoad == null;
|
||||
|
||||
// If Query<T>
|
||||
if (!useAltNames)
|
||||
{
|
||||
return MapRepository.Instance.GetColumns(typeof(T));
|
||||
}
|
||||
|
||||
ColumnMapCollection columns = new ColumnMapCollection();
|
||||
|
||||
Type baseEntityType = typeof(T);
|
||||
EntityGraph graph = new EntityGraph(baseEntityType, null);
|
||||
|
||||
foreach (var lvl in graph)
|
||||
{
|
||||
if (loadAllChildren || lvl.IsRoot || entitiesToLoad.Contains(lvl.Member.Name))
|
||||
{
|
||||
columns.AddRange(lvl.Columns);
|
||||
}
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
public static implicit operator List<T>(QueryBuilder<T> builder)
|
||||
{
|
||||
return builder.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Linq Support -
|
||||
|
||||
public virtual SortBuilder<T> Where<TObj>(Expression<Func<TObj, bool>> filterExpression)
|
||||
{
|
||||
bool useAltNames = _isFromView || _isGraph;
|
||||
bool addTablePrefixToColumns = true;
|
||||
_whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, useAltNames, addTablePrefixToColumns);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> Where(Expression<Func<T, bool>> filterExpression)
|
||||
{
|
||||
bool useAltNames = _isFromView || _isGraph;
|
||||
bool addTablePrefixToColumns = true;
|
||||
_whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, useAltNames, addTablePrefixToColumns);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> Where(string whereClause)
|
||||
{
|
||||
if (string.IsNullOrEmpty(whereClause))
|
||||
throw new ArgumentNullException("whereClause");
|
||||
|
||||
if (!whereClause.ToUpper().Contains("WHERE "))
|
||||
{
|
||||
whereClause = whereClause.Insert(0, " WHERE ");
|
||||
}
|
||||
|
||||
bool useAltNames = _isFromView || _isGraph || _isJoin;
|
||||
_whereBuilder = new WhereBuilder<T>(whereClause, useAltNames);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderBy(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
SortBuilder.OrderBy(sortExpression);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderBy(Expression<Func<T, object>> sortExpression, SortDirection sortDirection)
|
||||
{
|
||||
SortBuilder.OrderBy(sortExpression, sortDirection);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> ThenBy(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
SortBuilder.OrderBy(sortExpression);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> ThenBy(Expression<Func<T, object>> sortExpression, SortDirection sortDirection)
|
||||
{
|
||||
SortBuilder.OrderBy(sortExpression, sortDirection);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderByDescending(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
SortBuilder.OrderByDescending(sortExpression);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> ThenByDescending(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
SortBuilder.OrderByDescending(sortExpression);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderBy(string orderByClause)
|
||||
{
|
||||
if (string.IsNullOrEmpty(orderByClause))
|
||||
throw new ArgumentNullException("orderByClause");
|
||||
|
||||
if (!orderByClause.ToUpper().Contains("ORDER BY "))
|
||||
{
|
||||
orderByClause = orderByClause.Insert(0, " ORDER BY ");
|
||||
}
|
||||
|
||||
SortBuilder.OrderBy(orderByClause);
|
||||
return SortBuilder;
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Take(int count)
|
||||
{
|
||||
_enablePaging = true;
|
||||
_take = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Skip(int count)
|
||||
{
|
||||
_enablePaging = true;
|
||||
_skip = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected override Expression Visit(Expression expression)
|
||||
{
|
||||
return base.Visit(expression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles Where.
|
||||
/// </summary>
|
||||
/// <param name="lambdaExpression"></param>
|
||||
/// <returns></returns>
|
||||
protected override Expression VisitLamda(LambdaExpression lambdaExpression)
|
||||
{
|
||||
_sortBuilder = Where(lambdaExpression as Expression<Func<T, bool>>);
|
||||
return base.VisitLamda(lambdaExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles OrderBy.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected override Expression VisitMethodCall(MethodCallExpression expression)
|
||||
{
|
||||
if (expression.Method.Name == "OrderBy" || expression.Method.Name == "ThenBy")
|
||||
{
|
||||
var memberExp = ((expression.Arguments[1] as UnaryExpression).Operand as LambdaExpression).Body as MemberExpression;
|
||||
_sortBuilder.Order(memberExp.Expression.Type, memberExp.Member.Name);
|
||||
}
|
||||
if (expression.Method.Name == "OrderByDescending" || expression.Method.Name == "ThenByDescending")
|
||||
{
|
||||
var memberExp = ((expression.Arguments[1] as UnaryExpression).Operand as LambdaExpression).Body as MemberExpression;
|
||||
_sortBuilder.OrderByDescending(memberExp.Expression.Type, memberExp.Member.Name);
|
||||
}
|
||||
|
||||
return base.VisitMethodCall(expression);
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Join<TLeft, TRight>(JoinType joinType, Expression<Func<TLeft, IEnumerable<TRight>>> rightEntity, Expression<Func<TLeft, TRight, bool>> filterExpression)
|
||||
{
|
||||
_isJoin = true;
|
||||
MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member;
|
||||
return Join(joinType, rightMember, filterExpression);
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Join<TLeft, TRight>(JoinType joinType, Expression<Func<TLeft, TRight>> rightEntity, Expression<Func<TLeft, TRight, bool>> filterExpression)
|
||||
{
|
||||
_isJoin = true;
|
||||
MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member;
|
||||
return Join(joinType, rightMember, filterExpression);
|
||||
}
|
||||
|
||||
public virtual QueryBuilder<T> Join<TLeft, TRight>(JoinType joinType, MemberInfo rightMember, Expression<Func<TLeft, TRight, bool>> filterExpression)
|
||||
{
|
||||
_isJoin = true;
|
||||
|
||||
if (!_childrenToLoad.ContainsMember(rightMember))
|
||||
_childrenToLoad.Add(rightMember);
|
||||
|
||||
Table table = new Table(typeof(TRight), joinType);
|
||||
_tables.Add(table);
|
||||
|
||||
var builder = new JoinBuilder<TLeft, TRight>(_db.Command, _dialect, filterExpression, _tables);
|
||||
|
||||
table.JoinClause = builder.ToString();
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual bool Any(Expression<Func<T, bool>> filterExpression)
|
||||
{
|
||||
bool useAltNames = _isFromView || _isGraph;
|
||||
bool addTablePrefixToColumns = true;
|
||||
_whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, useAltNames, addTablePrefixToColumns);
|
||||
return Any();
|
||||
}
|
||||
|
||||
public virtual bool Any()
|
||||
{
|
||||
SqlModes previousSqlMode = _db.SqlMode;
|
||||
|
||||
// Generate a row count query
|
||||
string where = _whereBuilder != null ? _whereBuilder.ToString() : string.Empty;
|
||||
|
||||
bool useAltNames = _isFromView || _isGraph || _isJoin;
|
||||
IQuery query = QueryFactory.CreateRowCountSelectQuery(_tables, _db, where, SortBuilder, useAltNames);
|
||||
string queryText = query.Generate();
|
||||
|
||||
_db.SqlMode = SqlModes.Text;
|
||||
int count = Convert.ToInt32(_db.ExecuteScalar(queryText));
|
||||
|
||||
_db.SqlMode = previousSqlMode;
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable<T> Members
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
var list = ToList();
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
var list = ToList();
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
113
src/Marr.Data/QGen/QueryFactory.cs
Normal file
113
src/Marr.Data/QGen/QueryFactory.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using Marr.Data.Mapping;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains the factory logic that determines which type of IQuery object should be created.
|
||||
/// </summary>
|
||||
internal class QueryFactory
|
||||
{
|
||||
private const string DB_SqlClient = "System.Data.SqlClient.SqlClientFactory";
|
||||
private const string DB_OleDb = "System.Data.OleDb.OleDbFactory";
|
||||
private const string DB_SqlCeClient = "System.Data.SqlServerCe.SqlCeProviderFactory";
|
||||
private const string DB_SystemDataOracleClient = "System.Data.OracleClientFactory";
|
||||
private const string DB_OracleDataAccessClient = "Oracle.DataAccess.Client.OracleClientFactory";
|
||||
private const string DB_FireBirdClient = "FirebirdSql.Data.FirebirdClient.FirebirdClientFactory";
|
||||
private const string DB_SQLiteClient = "System.Data.SQLite.SQLiteFactory";
|
||||
|
||||
public static IQuery CreateUpdateQuery(ColumnMapCollection columns, IDataMapper dataMapper, string target, string whereClause)
|
||||
{
|
||||
Dialect dialect = CreateDialect(dataMapper);
|
||||
return new UpdateQuery(dialect, columns, dataMapper.Command, target, whereClause);
|
||||
}
|
||||
|
||||
public static IQuery CreateInsertQuery(ColumnMapCollection columns, IDataMapper dataMapper, string target)
|
||||
{
|
||||
Dialect dialect = CreateDialect(dataMapper);
|
||||
return new InsertQuery(dialect, columns, dataMapper.Command, target);
|
||||
}
|
||||
|
||||
public static IQuery CreateDeleteQuery(Dialect dialect, Table targetTable, string whereClause)
|
||||
{
|
||||
return new DeleteQuery(dialect, targetTable, whereClause);
|
||||
}
|
||||
|
||||
public static IQuery CreateSelectQuery(TableCollection tables, IDataMapper dataMapper, string where, ISortQueryBuilder orderBy, bool useAltName)
|
||||
{
|
||||
Dialect dialect = CreateDialect(dataMapper);
|
||||
return new SelectQuery(dialect, tables, where, orderBy, useAltName);
|
||||
}
|
||||
|
||||
public static IQuery CreateRowCountSelectQuery(TableCollection tables, IDataMapper dataMapper, string where, ISortQueryBuilder orderBy, bool useAltName)
|
||||
{
|
||||
SelectQuery innerQuery = (SelectQuery)CreateSelectQuery(tables, dataMapper, where, orderBy, useAltName);
|
||||
|
||||
string providerString = dataMapper.ProviderFactory.ToString();
|
||||
switch (providerString)
|
||||
{
|
||||
case DB_SqlClient:
|
||||
return new RowCountQueryDecorator(innerQuery);
|
||||
|
||||
case DB_SqlCeClient:
|
||||
return new RowCountQueryDecorator(innerQuery);
|
||||
|
||||
case DB_SQLiteClient:
|
||||
return new SqliteRowCountQueryDecorator(innerQuery);
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Row count has not yet been implemented for this provider.");
|
||||
}
|
||||
}
|
||||
|
||||
public static IQuery CreatePagingSelectQuery(TableCollection tables, IDataMapper dataMapper, string where, ISortQueryBuilder orderBy, bool useAltName, int skip, int take)
|
||||
{
|
||||
SelectQuery innerQuery = (SelectQuery)CreateSelectQuery(tables, dataMapper, where, orderBy, useAltName);
|
||||
|
||||
string providerString = dataMapper.ProviderFactory.ToString();
|
||||
switch (providerString)
|
||||
{
|
||||
case DB_SqlClient:
|
||||
return new PagingQueryDecorator(innerQuery, skip, take);
|
||||
|
||||
case DB_SqlCeClient:
|
||||
return new PagingQueryDecorator(innerQuery, skip, take);
|
||||
|
||||
case DB_SQLiteClient:
|
||||
return new SqlitePagingQueryDecorator(innerQuery, skip, take);
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Paging has not yet been implemented for this provider.");
|
||||
}
|
||||
}
|
||||
|
||||
public static Dialect CreateDialect(IDataMapper dataMapper)
|
||||
{
|
||||
string providerString = dataMapper.ProviderFactory.ToString();
|
||||
switch (providerString)
|
||||
{
|
||||
case DB_SqlClient:
|
||||
return new SqlServerDialect();
|
||||
|
||||
case DB_OracleDataAccessClient:
|
||||
return new OracleDialect();
|
||||
|
||||
case DB_SystemDataOracleClient:
|
||||
return new OracleDialect();
|
||||
|
||||
case DB_SqlCeClient:
|
||||
return new SqlServerCeDialect();
|
||||
|
||||
case DB_FireBirdClient:
|
||||
return new FirebirdDialect();
|
||||
|
||||
case DB_SQLiteClient:
|
||||
return new SqliteDialect();
|
||||
|
||||
default:
|
||||
return new Dialect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
src/Marr.Data/QGen/QueryQueueItem.cs
Normal file
19
src/Marr.Data/QGen/QueryQueueItem.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
|
||||
//namespace Marr.Data.QGen
|
||||
//{
|
||||
// public class QueryQueueItem
|
||||
// {
|
||||
// public QueryQueueItem(string queryText, IEnumerable<string> entitiesToLoad)
|
||||
// {
|
||||
// QueryText = queryText;
|
||||
// EntitiesToLoad = entitiesToLoad;
|
||||
// }
|
||||
|
||||
// public string QueryText { get; set; }
|
||||
// public IEnumerable<string> EntitiesToLoad { get; private set; }
|
||||
// }
|
||||
//}
|
121
src/Marr.Data/QGen/RowCountQueryDecorator.cs
Normal file
121
src/Marr.Data/QGen/RowCountQueryDecorator.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
public class RowCountQueryDecorator : IQuery
|
||||
{
|
||||
private SelectQuery _innerQuery;
|
||||
|
||||
public RowCountQueryDecorator(SelectQuery innerQuery)
|
||||
{
|
||||
_innerQuery = innerQuery;
|
||||
}
|
||||
|
||||
public string Generate()
|
||||
{
|
||||
// Decide which type of paging query to create
|
||||
if (_innerQuery.IsView || _innerQuery.IsJoin)
|
||||
{
|
||||
return ComplexRowCount();
|
||||
}
|
||||
return SimpleRowCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a row count query for a multiple table joined query (groups by the parent entity).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string ComplexRowCount()
|
||||
{
|
||||
// Create paged query
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
sql.AppendLine("WITH GroupCTE AS (");
|
||||
sql.Append("SELECT ").AppendLine(BuildBaseTablePKColumns());
|
||||
BuildGroupColumn(sql);
|
||||
_innerQuery.BuildFromClause(sql);
|
||||
_innerQuery.BuildJoinClauses(sql);
|
||||
_innerQuery.BuildWhereClause(sql);
|
||||
sql.AppendLine(")");
|
||||
BuildSelectCountClause(sql);
|
||||
sql.AppendLine("FROM GroupCTE");
|
||||
sql.AppendLine("WHERE GroupRow = 1");
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a row count query for a single table query (no joins).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string SimpleRowCount()
|
||||
{
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
BuildSelectCountClause(sql);
|
||||
_innerQuery.BuildFromClause(sql);
|
||||
_innerQuery.BuildJoinClauses(sql);
|
||||
_innerQuery.BuildWhereClause(sql);
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
|
||||
private void BuildGroupColumn(StringBuilder sql)
|
||||
{
|
||||
string baseTablePKColumns = BuildBaseTablePKColumns();
|
||||
sql.AppendFormat(", ROW_NUMBER() OVER (PARTITION BY {0} ORDER BY {1}) As GroupRow ", baseTablePKColumns, baseTablePKColumns);
|
||||
}
|
||||
|
||||
private string BuildBaseTablePKColumns()
|
||||
{
|
||||
Table baseTable = GetBaseTable();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var col in baseTable.Columns.PrimaryKeys)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.AppendLine(", ");
|
||||
|
||||
string colName = _innerQuery.IsView ?
|
||||
_innerQuery.NameOrAltName(col.ColumnInfo) :
|
||||
col.ColumnInfo.Name;
|
||||
|
||||
sb.AppendFormat(_innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", colName)));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void BuildSelectCountClause(StringBuilder sql)
|
||||
{
|
||||
sql.AppendLine("SELECT COUNT(*)");
|
||||
}
|
||||
|
||||
private Table GetBaseTable()
|
||||
{
|
||||
Table baseTable = null;
|
||||
if (_innerQuery.Tables[0] is View)
|
||||
{
|
||||
baseTable = (_innerQuery.Tables[0] as View).Tables[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
baseTable = _innerQuery.Tables[0];
|
||||
}
|
||||
return baseTable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
WITH GroupCTE AS
|
||||
(
|
||||
SELECT [t0].[ID],[t0].[OrderName],[t1].[ID] AS OrderItemID,[t1].[OrderID],[t1].[ItemDescription],[t1].[Price],
|
||||
ROW_NUMBER() OVER (PARTITION BY [t0].[ID] ORDER BY [t0].[OrderName]) As GroupRow
|
||||
FROM [Order] [t0]
|
||||
LEFT JOIN [OrderItem] [t1] ON (([t0].[ID] = [t1].[OrderID]))
|
||||
--WHERE (([t0].[OrderName] = @P0))
|
||||
)
|
||||
SELECT * FROM GroupCTE
|
||||
WHERE GroupRow = 1
|
||||
*/
|
149
src/Marr.Data/QGen/SelectQuery.cs
Normal file
149
src/Marr.Data/QGen/SelectQuery.cs
Normal file
|
@ -0,0 +1,149 @@
|
|||
using System.Text;
|
||||
using Marr.Data.Mapping;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is responsible for creating a select query.
|
||||
/// </summary>
|
||||
public class SelectQuery : IQuery
|
||||
{
|
||||
public Dialect Dialect { get; set; }
|
||||
public string WhereClause { get; set; }
|
||||
public ISortQueryBuilder OrderBy { get; set; }
|
||||
public TableCollection Tables { get; set; }
|
||||
public bool UseAltName;
|
||||
|
||||
public SelectQuery(Dialect dialect, TableCollection tables, string whereClause, ISortQueryBuilder orderBy, bool useAltName)
|
||||
{
|
||||
Dialect = dialect;
|
||||
Tables = tables;
|
||||
WhereClause = whereClause;
|
||||
OrderBy = orderBy;
|
||||
UseAltName = useAltName;
|
||||
}
|
||||
|
||||
public bool IsView
|
||||
{
|
||||
get
|
||||
{
|
||||
return Tables[0] is View;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsJoin
|
||||
{
|
||||
get
|
||||
{
|
||||
return Tables.Count > 1;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string Generate()
|
||||
{
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
BuildSelectClause(sql);
|
||||
BuildFromClause(sql);
|
||||
BuildJoinClauses(sql);
|
||||
BuildWhereClause(sql);
|
||||
BuildOrderClause(sql);
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
|
||||
public void BuildSelectClause(StringBuilder sql)
|
||||
{
|
||||
sql.Append("SELECT ");
|
||||
|
||||
int startIndex = sql.Length;
|
||||
|
||||
// COLUMNS
|
||||
foreach (Table join in Tables)
|
||||
{
|
||||
for (int i = 0; i < join.Columns.Count; i++)
|
||||
{
|
||||
var c = join.Columns[i];
|
||||
|
||||
if (sql.Length > startIndex)
|
||||
sql.Append(",");
|
||||
|
||||
if (join is View)
|
||||
{
|
||||
string token = string.Concat(join.Alias, ".", NameOrAltName(c.ColumnInfo));
|
||||
sql.Append(Dialect.CreateToken(token));
|
||||
}
|
||||
else
|
||||
{
|
||||
string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
|
||||
sql.Append(Dialect.CreateToken(token));
|
||||
|
||||
if (UseAltName && c.ColumnInfo.AltName != null && c.ColumnInfo.AltName != c.ColumnInfo.Name)
|
||||
{
|
||||
string altName = c.ColumnInfo.AltName;
|
||||
sql.AppendFormat(" AS {0}", altName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string NameOrAltName(IColumnInfo columnInfo)
|
||||
{
|
||||
if (UseAltName && columnInfo.AltName != null && columnInfo.AltName != columnInfo.Name)
|
||||
{
|
||||
return columnInfo.AltName;
|
||||
}
|
||||
return columnInfo.Name;
|
||||
}
|
||||
|
||||
public void BuildFromClause(StringBuilder sql)
|
||||
{
|
||||
// BASE TABLE
|
||||
Table baseTable = Tables[0];
|
||||
sql.AppendFormat(" FROM {0} {1} ", Dialect.CreateToken(baseTable.Name), Dialect.CreateToken(baseTable.Alias));
|
||||
}
|
||||
|
||||
public void BuildJoinClauses(StringBuilder sql)
|
||||
{
|
||||
// JOINS
|
||||
for (int i = 1; i < Tables.Count; i++)
|
||||
{
|
||||
if (Tables[i].JoinType != JoinType.None)
|
||||
{
|
||||
sql.AppendFormat("{0} {1} {2} {3} ",
|
||||
TranslateJoin(Tables[i].JoinType),
|
||||
Dialect.CreateToken(Tables[i].Name),
|
||||
Dialect.CreateToken(Tables[i].Alias),
|
||||
Tables[i].JoinClause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void BuildWhereClause(StringBuilder sql)
|
||||
{
|
||||
sql.Append(WhereClause);
|
||||
}
|
||||
|
||||
public void BuildOrderClause(StringBuilder sql)
|
||||
{
|
||||
sql.Append(OrderBy.ToString());
|
||||
}
|
||||
|
||||
private string TranslateJoin(JoinType join)
|
||||
{
|
||||
switch (join)
|
||||
{
|
||||
case JoinType.Inner:
|
||||
return "INNER JOIN";
|
||||
case JoinType.Left:
|
||||
return "LEFT JOIN";
|
||||
case JoinType.Right:
|
||||
return "RIGHT JOIN";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
262
src/Marr.Data/QGen/SortBuilder.cs
Normal file
262
src/Marr.Data/QGen/SortBuilder.cs
Normal file
|
@ -0,0 +1,262 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq.Expressions;
|
||||
using Marr.Data.QGen.Dialects;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is responsible for creating an "ORDER BY" clause.
|
||||
/// It uses chaining methods to provide a fluent interface.
|
||||
/// It also has some methods that coincide with Linq methods, to provide Linq compatibility.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class SortBuilder<T> : IEnumerable<T>, ISortQueryBuilder
|
||||
{
|
||||
private string _constantOrderByClause;
|
||||
private QueryBuilder<T> _baseBuilder;
|
||||
private Dialect _dialect;
|
||||
private List<SortColumn<T>> _sortExpressions;
|
||||
private bool _useAltName;
|
||||
private TableCollection _tables;
|
||||
private IDataMapper _db;
|
||||
private WhereBuilder<T> _whereBuilder;
|
||||
|
||||
public SortBuilder()
|
||||
{
|
||||
// Used only for unit testing with mock frameworks
|
||||
}
|
||||
|
||||
public SortBuilder(QueryBuilder<T> baseBuilder, IDataMapper db, WhereBuilder<T> whereBuilder, Dialect dialect, TableCollection tables, bool useAltName)
|
||||
{
|
||||
_baseBuilder = baseBuilder;
|
||||
_db = db;
|
||||
_whereBuilder = whereBuilder;
|
||||
_dialect = dialect;
|
||||
_sortExpressions = new List<SortColumn<T>>();
|
||||
_useAltName = useAltName;
|
||||
_tables = tables;
|
||||
}
|
||||
|
||||
#region - AndWhere / OrWhere -
|
||||
|
||||
public virtual SortBuilder<T> OrWhere(Expression<Func<T, bool>> filterExpression)
|
||||
{
|
||||
var orWhere = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, false, true);
|
||||
_whereBuilder.Append(orWhere, WhereAppendType.OR);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrWhere(string whereClause)
|
||||
{
|
||||
var orWhere = new WhereBuilder<T>(whereClause, false);
|
||||
_whereBuilder.Append(orWhere, WhereAppendType.OR);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> AndWhere(Expression<Func<T, bool>> filterExpression)
|
||||
{
|
||||
var andWhere = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, false, true);
|
||||
_whereBuilder.Append(andWhere, WhereAppendType.AND);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> AndWhere(string whereClause)
|
||||
{
|
||||
var andWhere = new WhereBuilder<T>(whereClause, false);
|
||||
_whereBuilder.Append(andWhere, WhereAppendType.AND);
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Order -
|
||||
|
||||
internal SortBuilder<T> Order(Type declaringType, string propertyName)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(declaringType, propertyName, SortDirection.Asc));
|
||||
return this;
|
||||
}
|
||||
|
||||
internal SortBuilder<T> OrderByDescending(Type declaringType, string propertyName)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(declaringType, propertyName, SortDirection.Desc));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderBy(string orderByClause)
|
||||
{
|
||||
if (string.IsNullOrEmpty(orderByClause))
|
||||
throw new ArgumentNullException("orderByClause");
|
||||
|
||||
if (!orderByClause.ToUpper().Contains("ORDER BY "))
|
||||
{
|
||||
orderByClause = orderByClause.Insert(0, " ORDER BY ");
|
||||
}
|
||||
|
||||
_constantOrderByClause = orderByClause;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderBy(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Asc));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderBy(Expression<Func<T, object>> sortExpression, SortDirection sortDirection)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(sortExpression, sortDirection));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> OrderByDescending(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Desc));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> ThenBy(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Asc));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> ThenBy(Expression<Func<T, object>> sortExpression, SortDirection sortDirection)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(sortExpression, sortDirection));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> ThenByDescending(Expression<Func<T, object>> sortExpression)
|
||||
{
|
||||
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Desc));
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Paging -
|
||||
|
||||
public virtual SortBuilder<T> Take(int count)
|
||||
{
|
||||
_baseBuilder.Take(count);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> Skip(int count)
|
||||
{
|
||||
_baseBuilder.Skip(count);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual SortBuilder<T> Page(int pageNumber, int pageSize)
|
||||
{
|
||||
_baseBuilder.Page(pageNumber, pageSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - GetRowCount -
|
||||
|
||||
public virtual int GetRowCount()
|
||||
{
|
||||
return _baseBuilder.GetRowCount();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - ToList / ToString / BuildQuery -
|
||||
|
||||
public virtual List<T> ToList()
|
||||
{
|
||||
return _baseBuilder.ToList();
|
||||
}
|
||||
|
||||
public virtual string BuildQuery()
|
||||
{
|
||||
return _baseBuilder.BuildQuery();
|
||||
}
|
||||
|
||||
public virtual string BuildQuery(bool useAltName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_constantOrderByClause))
|
||||
{
|
||||
return _constantOrderByClause;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
foreach (var sort in _sortExpressions)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append(",");
|
||||
|
||||
Table table = _tables.FindTable(sort.DeclaringType);
|
||||
|
||||
if (table == null)
|
||||
{
|
||||
string msg = string.Format("The property '{0} -> {1}' you are trying to reference in the 'ORDER BY' statement belongs to an entity that has not been joined in your query. To reference this property, you must join the '{0}' entity using the Join method.",
|
||||
sort.DeclaringType.Name,
|
||||
sort.PropertyName);
|
||||
|
||||
throw new DataMappingException(msg);
|
||||
}
|
||||
|
||||
string columnName = DataHelper.GetColumnName(sort.DeclaringType, sort.PropertyName, useAltName);
|
||||
|
||||
if (!useAltName)
|
||||
sb.Append(_dialect.CreateToken(string.Format("{0}.{1}", table.Alias, columnName)));
|
||||
|
||||
else
|
||||
sb.Append(_dialect.CreateToken(string.Format("{0}", columnName)));
|
||||
|
||||
if (sort.Direction == SortDirection.Desc)
|
||||
sb.Append(" DESC");
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
sb.Insert(0, " ORDER BY ");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return BuildQuery(_useAltName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Implicit List<T> Operator -
|
||||
|
||||
public static implicit operator List<T>(SortBuilder<T> builder)
|
||||
{
|
||||
return builder.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable<T> Members
|
||||
|
||||
public virtual IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
var list = ToList();
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
46
src/Marr.Data/QGen/SortColumn.cs
Normal file
46
src/Marr.Data/QGen/SortColumn.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Marr.Data.QGen
|
||||
{
|
||||
public class SortColumn<T>
|
||||
{
|
||||
public SortColumn(Expression<Func<T, object>> sortExpression, SortDirection direction)
|
||||
{
|
||||
MemberExpression me = GetMemberExpression(sortExpression.Body);
|
||||
DeclaringType = me.Expression.Type;
|
||||
PropertyName = me.Member.Name;
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
public SortColumn(Type declaringType, string propertyName, SortDirection direction)
|
||||
{
|
||||
DeclaringType = declaringType;
|
||||
PropertyName = propertyName;
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
public SortDirection Direction { get; private set; }
|
||||
public Type DeclaringType { get; private set; }
|
||||
public string PropertyName { get; private set; }
|
||||
|
||||
private MemberExpression GetMemberExpression(Expression exp)
|
||||
{
|
||||
MemberExpression me = exp as MemberExpression;
|
||||
|
||||
if (me == null)
|
||||
{
|
||||
var ue = exp as UnaryExpression;
|
||||
me = ue.Operand as MemberExpression;
|
||||
}
|
||||
|
||||
return me;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SortDirection
|
||||
{
|
||||
Asc,
|
||||
Desc
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue