Revert "Remove Marr.Data"

This reverts commit 7b17c3e36c.
This commit is contained in:
Qstick 2019-12-17 21:23:16 -05:00
parent e937d74b11
commit d2d2020573
88 changed files with 9203 additions and 0 deletions

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<Words>
<Recognized>
<Word>Ack</Word>
<Word>Minifier</Word>
<Word>Jsonp</Word>
<Word>Linktionary</Word>
<Word>Scaleout</Word>
<Word>Redis</Word>
<Word>Owin</Word>
<Word>Stringify</Word>
<Word>Unminify</Word>
<Word>Unminified</Word>
<Word>Stateful</Word>
<Word>SignalR</Word>
<Word>Hubservable</Word>
<Word>Sse</Word>
<Word>GitHub</Word>
</Recognized>
</Words>
</Dictionary>

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Reflection;
using System.Resources;
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: AssemblyCompany("Microsoft Open Technologies, Inc.")]
[assembly: AssemblyCopyright("© Microsoft Open Technologies, Inc. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyConfiguration("")]
[assembly: ComVisible(false)]
[assembly: CLSCompliant(false)]
[assembly: NeutralResourcesLanguage("en-US")]

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Reflection;
[assembly: AssemblyVersion("10.0.0.*")]

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Code Analysis results, point to "Suppress Message", and click
// "In Suppression File".
// You do not need to add suppressions to this file manually.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Strong naming is done on the CI.")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "param", Scope = "resource", Target = "Microsoft.AspNet.SignalR.Resources.resources")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", Justification = "We use semver")]
[assembly: SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Scope = "member", Target = "Microsoft.AspNet.SignalR.Messaging.ScaleoutTaskQueue.#.cctor()", Justification = "The task is cached")]

View File

@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="New Rule Set" Description=" " ToolsVersion="11.0">
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1000" Action="Error" />
<Rule Id="CA1001" Action="Error" />
<Rule Id="CA1002" Action="Error" />
<Rule Id="CA1003" Action="Error" />
<Rule Id="CA1004" Action="Error" />
<Rule Id="CA1005" Action="Error" />
<Rule Id="CA1007" Action="Error" />
<Rule Id="CA1008" Action="Error" />
<Rule Id="CA1010" Action="Error" />
<Rule Id="CA1011" Action="Error" />
<Rule Id="CA1012" Action="Error" />
<Rule Id="CA1013" Action="Error" />
<Rule Id="CA1014" Action="Error" />
<Rule Id="CA1016" Action="Error" />
<Rule Id="CA1017" Action="Error" />
<Rule Id="CA1018" Action="Error" />
<Rule Id="CA1019" Action="Error" />
<Rule Id="CA1021" Action="Error" />
<Rule Id="CA1023" Action="Error" />
<Rule Id="CA1024" Action="Error" />
<Rule Id="CA1025" Action="Error" />
<Rule Id="CA1026" Action="Error" />
<Rule Id="CA1027" Action="Error" />
<Rule Id="CA1028" Action="Error" />
<Rule Id="CA1030" Action="Error" />
<Rule Id="CA1031" Action="Error" />
<Rule Id="CA1032" Action="Error" />
<Rule Id="CA1034" Action="Error" />
<Rule Id="CA1035" Action="Error" />
<Rule Id="CA1036" Action="Error" />
<Rule Id="CA1038" Action="Error" />
<Rule Id="CA1039" Action="Error" />
<Rule Id="CA1040" Action="Error" />
<Rule Id="CA1041" Action="Error" />
<Rule Id="CA1043" Action="Error" />
<Rule Id="CA1044" Action="Error" />
<Rule Id="CA1045" Action="Error" />
<Rule Id="CA1046" Action="Error" />
<Rule Id="CA1047" Action="Error" />
<Rule Id="CA1048" Action="Error" />
<Rule Id="CA1049" Action="Error" />
<Rule Id="CA1050" Action="Error" />
<Rule Id="CA1051" Action="Error" />
<Rule Id="CA1052" Action="Error" />
<Rule Id="CA1053" Action="Error" />
<Rule Id="CA1057" Action="Error" />
<Rule Id="CA1058" Action="Error" />
<Rule Id="CA1059" Action="Error" />
<Rule Id="CA1060" Action="Error" />
<Rule Id="CA1061" Action="Error" />
<Rule Id="CA1062" Action="Error" />
<Rule Id="CA1063" Action="Error" />
<Rule Id="CA1064" Action="Error" />
<Rule Id="CA1065" Action="Error" />
<Rule Id="CA1300" Action="Error" />
<Rule Id="CA1301" Action="Error" />
<Rule Id="CA1302" Action="Error" />
<Rule Id="CA1303" Action="Error" />
<Rule Id="CA1304" Action="Error" />
<Rule Id="CA1305" Action="Error" />
<Rule Id="CA1306" Action="Error" />
<Rule Id="CA1307" Action="Error" />
<Rule Id="CA1308" Action="Error" />
<Rule Id="CA1309" Action="Error" />
<Rule Id="CA1400" Action="Error" />
<Rule Id="CA1401" Action="Error" />
<Rule Id="CA1402" Action="Error" />
<Rule Id="CA1403" Action="Error" />
<Rule Id="CA1404" Action="Error" />
<Rule Id="CA1405" Action="Error" />
<Rule Id="CA1406" Action="Error" />
<Rule Id="CA1407" Action="Error" />
<Rule Id="CA1408" Action="Error" />
<Rule Id="CA1409" Action="Error" />
<Rule Id="CA1410" Action="Error" />
<Rule Id="CA1411" Action="Error" />
<Rule Id="CA1412" Action="Error" />
<Rule Id="CA1413" Action="Error" />
<Rule Id="CA1414" Action="Error" />
<Rule Id="CA1415" Action="Error" />
<Rule Id="CA1500" Action="Error" />
<Rule Id="CA1501" Action="Error" />
<Rule Id="CA1502" Action="Error" />
<Rule Id="CA1504" Action="Error" />
<Rule Id="CA1505" Action="Error" />
<Rule Id="CA1506" Action="Error" />
<Rule Id="CA1600" Action="Error" />
<Rule Id="CA1601" Action="Error" />
<Rule Id="CA1700" Action="Error" />
<Rule Id="CA1701" Action="Error" />
<Rule Id="CA1702" Action="Error" />
<Rule Id="CA1703" Action="Error" />
<Rule Id="CA1704" Action="Error" />
<Rule Id="CA1707" Action="Error" />
<Rule Id="CA1708" Action="Error" />
<Rule Id="CA1709" Action="Error" />
<Rule Id="CA1712" Action="Error" />
<Rule Id="CA1713" Action="Error" />
<Rule Id="CA1714" Action="Error" />
<Rule Id="CA1715" Action="Error" />
<Rule Id="CA1716" Action="Error" />
<Rule Id="CA1717" Action="Error" />
<Rule Id="CA1719" Action="Error" />
<Rule Id="CA1720" Action="Error" />
<Rule Id="CA1721" Action="Error" />
<Rule Id="CA1722" Action="Error" />
<Rule Id="CA1724" Action="Error" />
<Rule Id="CA1725" Action="Error" />
<Rule Id="CA1726" Action="Error" />
<Rule Id="CA1800" Action="Error" />
<Rule Id="CA1801" Action="Error" />
<Rule Id="CA1802" Action="Error" />
<Rule Id="CA1804" Action="Error" />
<Rule Id="CA1806" Action="Error" />
<Rule Id="CA1809" Action="Error" />
<Rule Id="CA1810" Action="Error" />
<Rule Id="CA1811" Action="Error" />
<Rule Id="CA1812" Action="Error" />
<Rule Id="CA1813" Action="Error" />
<Rule Id="CA1814" Action="Error" />
<Rule Id="CA1815" Action="Error" />
<Rule Id="CA1819" Action="Error" />
<Rule Id="CA1820" Action="Error" />
<Rule Id="CA1821" Action="Error" />
<Rule Id="CA1822" Action="Error" />
<Rule Id="CA1823" Action="Error" />
<Rule Id="CA1824" Action="Error" />
<Rule Id="CA1900" Action="Error" />
<Rule Id="CA1901" Action="Error" />
<Rule Id="CA1903" Action="Error" />
<Rule Id="CA2000" Action="Error" />
<Rule Id="CA2001" Action="Error" />
<Rule Id="CA2002" Action="Error" />
<Rule Id="CA2003" Action="Error" />
<Rule Id="CA2004" Action="Error" />
<Rule Id="CA2006" Action="Error" />
<Rule Id="CA2100" Action="Error" />
<Rule Id="CA2101" Action="Error" />
<Rule Id="CA2102" Action="Error" />
<Rule Id="CA2103" Action="Error" />
<Rule Id="CA2104" Action="Error" />
<Rule Id="CA2105" Action="Error" />
<Rule Id="CA2106" Action="Error" />
<Rule Id="CA2107" Action="Error" />
<Rule Id="CA2108" Action="Error" />
<Rule Id="CA2109" Action="Error" />
<Rule Id="CA2111" Action="Error" />
<Rule Id="CA2112" Action="Error" />
<Rule Id="CA2114" Action="Error" />
<Rule Id="CA2115" Action="Error" />
<Rule Id="CA2116" Action="Error" />
<Rule Id="CA2117" Action="Error" />
<Rule Id="CA2118" Action="Error" />
<Rule Id="CA2119" Action="Error" />
<Rule Id="CA2120" Action="Error" />
<Rule Id="CA2121" Action="Error" />
<Rule Id="CA2122" Action="Error" />
<Rule Id="CA2123" Action="Error" />
<Rule Id="CA2124" Action="Error" />
<Rule Id="CA2126" Action="Error" />
<Rule Id="CA2130" Action="Error" />
<Rule Id="CA2131" Action="Error" />
<Rule Id="CA2132" Action="Error" />
<Rule Id="CA2133" Action="Error" />
<Rule Id="CA2134" Action="Error" />
<Rule Id="CA2135" Action="Error" />
<Rule Id="CA2136" Action="Error" />
<Rule Id="CA2137" Action="Error" />
<Rule Id="CA2138" Action="Error" />
<Rule Id="CA2139" Action="Error" />
<Rule Id="CA2140" Action="Error" />
<Rule Id="CA2141" Action="Error" />
<Rule Id="CA2142" Action="Error" />
<Rule Id="CA2143" Action="Error" />
<Rule Id="CA2144" Action="Error" />
<Rule Id="CA2145" Action="Error" />
<Rule Id="CA2146" Action="Error" />
<Rule Id="CA2147" Action="Error" />
<Rule Id="CA2149" Action="Error" />
<Rule Id="CA2151" Action="Error" />
<Rule Id="CA2200" Action="Error" />
<Rule Id="CA2201" Action="Error" />
<Rule Id="CA2202" Action="Error" />
<Rule Id="CA2204" Action="Error" />
<Rule Id="CA2205" Action="Error" />
<Rule Id="CA2207" Action="Error" />
<Rule Id="CA2208" Action="Error" />
<Rule Id="CA2210" Action="Error" />
<Rule Id="CA2211" Action="Error" />
<Rule Id="CA2212" Action="Error" />
<Rule Id="CA2213" Action="Error" />
<Rule Id="CA2214" Action="Error" />
<Rule Id="CA2215" Action="Error" />
<Rule Id="CA2216" Action="Error" />
<Rule Id="CA2217" Action="Error" />
<Rule Id="CA2218" Action="Error" />
<Rule Id="CA2219" Action="Error" />
<Rule Id="CA2220" Action="Error" />
<Rule Id="CA2221" Action="Error" />
<Rule Id="CA2222" Action="Error" />
<Rule Id="CA2223" Action="Error" />
<Rule Id="CA2224" Action="Error" />
<Rule Id="CA2225" Action="Error" />
<Rule Id="CA2226" Action="Error" />
<Rule Id="CA2227" Action="Error" />
<Rule Id="CA2228" Action="Error" />
<Rule Id="CA2229" Action="Error" />
<Rule Id="CA2230" Action="Error" />
<Rule Id="CA2231" Action="Error" />
<Rule Id="CA2232" Action="Error" />
<Rule Id="CA2233" Action="Error" />
<Rule Id="CA2234" Action="Error" />
<Rule Id="CA2235" Action="Error" />
<Rule Id="CA2236" Action="Error" />
<Rule Id="CA2237" Action="Error" />
<Rule Id="CA2238" Action="Error" />
<Rule Id="CA2239" Action="Error" />
<Rule Id="CA2240" Action="Error" />
<Rule Id="CA2241" Action="Error" />
<Rule Id="CA2242" Action="Error" />
<Rule Id="CA2243" Action="Error" />
<Rule Id="CA5122" Action="Error" />
</Rules>
</RuleSet>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(ArtifactsDir)' != ''">
<OutputPath Condition="$(UseBinPath) == ''">$(ArtifactsDir)\$(MSBuildProjectName)</OutputPath>
<OutputPath Condition="$(UseBinPath) == 'true'">$(ArtifactsDir)\$(MSBuildProjectName)\bin</OutputPath>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Microsoft.AspNet.SignalR.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis Condition="'$(RunCodeAnalysis)' == ''">false</RunCodeAnalysis>
<NoWarn>1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(RunCodeAnalysis)' == 'true'">
<DefineConstants>$(DefineConstants);CODE_ANALYSIS</DefineConstants>
<VisualStudioVersion>11.0</VisualStudioVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
<DefineConstants>$(DefineConstants);MONO</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(KeyFile)' != '' And '$(DisableSigning)' != 'true'">
<DefineConstants>$(DefineConstants);SIGNED</DefineConstants>
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>$(KeyFile)</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup Condition="Exists('$(MSBuildThisFileDirectory)GlobalSuppressions.cs')">
<Compile Include="$(MSBuildThisFileDirectory)GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="$(MSBuildThisFileDirectory)CodeAnalysisDictionary.xml" />
</ItemGroup>
</Project>

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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
}
}

View 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)
{ }
}
}

View 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; }
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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
View 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<>)));
}
}
}

958
src/Marr.Data/DataMapper.cs Normal file
View File

@ -0,0 +1,958 @@
/* 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>
/// 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(IsolationLevel isolationLevel)
{
OpenConnection();
DbTransaction trans = Command.Connection.BeginTransaction(isolationLevel);
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 != null)
{
if (_command.Transaction != null)
{
_command.Transaction.Dispose();
_command.Transaction = null;
}
if (_command.Connection != null)
{
_command.Connection.Dispose();
_command.Connection = null;
}
_command.Dispose();
_command = null;
}
}
}
#endregion
}
}

View 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)
{
}
}
}

View File

@ -0,0 +1,419 @@
/* 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 Child in the graph for LazyLoaded property.
/// </summary>
public void AddLazyRelationship(Relationship childRelationship)
{
_children.Add(new EntityGraph(childRelationship.RelationshipInfo.EntityType.GetGenericArguments()[0], this, childRelationship));
}
/// <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
{
if (_relationship.IsLazyLoaded)
_relationship.Setter(_parent._entity, Activator.CreateInstance(_relationship.MemberType, entityInstance));
else
_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; }
}
}

View 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);
}
}
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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
}
}

View 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(IsolationLevel isolationLevel);
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
View 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;
}
}
}

View 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
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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; }
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View File

@ -0,0 +1,234 @@
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) &&
!MapRepository.Instance.TypeConverters.ContainsKey((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);
}
}
}
}

View 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();
}
}

View 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
}
}

View 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
}
}

View File

@ -0,0 +1,194 @@
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)
{
object dbValue = null;
try
{
string colName = dataMap.ColumnInfo.GetColumName(useAltName);
int ordinal = reader.GetOrdinal(colName);
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}' value: '{1}'. {2}",
dataMap.ColumnInfo.Name, dbValue, 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))
{
var provider = _db.ProviderFactory;
var connectionString = _db.ConnectionString;
Func<IDataMapper> dbCreate = () =>
{
var db = new DataMapper(provider, 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));
}
}
}
}

View 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; }
}
}

View 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
}
}

View 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
}
}

View 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);
}
}
}
}

View 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; }
}
}

View 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);
}
}
}
}

View 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));
}
}
}
}

View 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);
}
}

View 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);
}
}
}
}
}
}

View 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);
}
}

View 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; }
}
}
}

View 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
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AssemblyVersion>3.17.0.0</AssemblyVersion>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
</PropertyGroup>
</Project>

View 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;
}
}
}

View 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);
}
}

View File

@ -0,0 +1,133 @@
/* 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.Data.Common;
using System.Runtime.InteropServices.ComTypes;
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
//If we have a list of ints, we ignore the converter since we want to do an in statement!
var list = value as List<int>;
if (list != null)
{
Parameter.Value = $"{string.Join(",", list)}";
}
else
{
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;
}
}
}

View 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);
}
}
}

View File

@ -0,0 +1,80 @@
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} + '%')"; }
}
public virtual string InFormat
{
get
{
return "({0} in ({1}))";
}
}
}
}

View 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());
}
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View File

@ -0,0 +1,13 @@
namespace Marr.Data.QGen.Dialects
{
public class SqlServerDialect : Dialect
{
public override string IdentityQuery
{
get
{
return "SELECT SCOPE_IDENTITY();";
}
}
}
}

View 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} || '%')"; }
}
}
}

View 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;
}
}
}

View 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();
}
}

View File

@ -0,0 +1,12 @@
namespace Marr.Data.QGen
{
public interface IQueryBuilder
{
string BuildQuery();
}
public interface ISortQueryBuilder : IQueryBuilder
{
string BuildQuery(bool useAltNames);
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}
}
}
}

View File

@ -0,0 +1,635 @@
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, Expression<Func<TLeft, LazyLoaded<TRight>>> rightEntity, Expression<Func<TLeft, TRight, bool>> filterExpression)
{
_isJoin = true;
MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member;
foreach (var item in EntGraph)
{
if (item.EntityType == typeof(TLeft))
{
var relationship = item.Relationships.Single(v => v.Member == rightMember);
item.AddLazyRelationship(relationship);
}
}
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
}
}

View 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();
}
}
}
}

View 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; }
// }
//}

View 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
*/

View File

@ -0,0 +1,159 @@
using System.Linq;
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());
}
public void BuildGroupBy(StringBuilder sql)
{
var baseTable = this.Tables.First();
var primaryKeyColumn = baseTable.Columns.Single(c => c.ColumnInfo.IsPrimaryKey);
string token = this.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", primaryKeyColumn.ColumnInfo.Name));
sql.AppendFormat(" GROUP BY {0}", token);
}
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;
}
}
}
}

View 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
}
}

View 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
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen
{
/// <summary>
/// Decorates the SelectQuery by wrapping it in a paging query.
/// </summary>
public class SqlitePagingQueryDecorator : IQuery
{
private SelectQuery _innerQuery;
private int _skip;
private int _take;
public SqlitePagingQueryDecorator(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;
_skip = skip;
_take = take;
}
public string Generate()
{
if (_innerQuery.IsView || _innerQuery.IsJoin)
{
return ComplexPaging();
}
return SimplePaging();
}
private string SimplePaging()
{
// Create paged query
StringBuilder sql = new StringBuilder();
_innerQuery.BuildSelectClause(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
_innerQuery.BuildOrderClause(sql);
sql.AppendLine(String.Format(" LIMIT {0},{1}", _skip, _take));
return sql.ToString();
}
private string ComplexPaging()
{
var baseTable = _innerQuery.Tables.First();
StringBuilder sql = new StringBuilder();
_innerQuery.BuildSelectClause(sql);
sql.Append(" FROM (");
BuildSimpleInnerSelect(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
BuildGroupBy(sql);
BuildOrderBy(sql);
sql.AppendFormat(" LIMIT {0},{1}", _skip, _take);
sql.AppendFormat(") AS {0} ", _innerQuery.Dialect.CreateToken(baseTable.Alias));
_innerQuery.BuildJoinClauses(sql);
return sql.ToString();
}
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);
}
}
}
}
}
private void BuildSimpleInnerSelect(StringBuilder sql)
{
sql.Append("SELECT ");
int startIndex = sql.Length;
// COLUMNS
var join = _innerQuery.Tables.First();
for (int i = 0; i < join.Columns.Count; i++)
{
var c = join.Columns[i];
if (sql.Length > startIndex)
sql.Append(",");
string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
sql.Append(_innerQuery.Dialect.CreateToken(token));
}
}
private void BuildOrderBy(StringBuilder sql)
{
sql.Append(_innerQuery.OrderBy.BuildQuery(false));
}
private void BuildGroupBy(StringBuilder sql)
{
var baseTable = _innerQuery.Tables.First();
var primaryKeyColumn = baseTable.Columns.Single(c => c.ColumnInfo.IsPrimaryKey);
string token = _innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", primaryKeyColumn.ColumnInfo.Name));
sql.AppendFormat(" GROUP BY {0}", token);
}
}
}

View File

@ -0,0 +1,45 @@
using System.Text;
namespace Marr.Data.QGen
{
public class SqliteRowCountQueryDecorator : IQuery
{
private SelectQuery _innerQuery;
public SqliteRowCountQueryDecorator(SelectQuery innerQuery)
{
_innerQuery = innerQuery;
}
public string Generate()
{
StringBuilder sql = new StringBuilder();
BuildSelectCountClause(sql);
if (_innerQuery.IsJoin)
{
sql.Append(" FROM (");
_innerQuery.BuildSelectClause(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
_innerQuery.BuildGroupBy(sql);
sql.Append(") ");
return sql.ToString();
}
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
return sql.ToString();
}
private void BuildSelectCountClause(StringBuilder sql)
{
sql.AppendLine("SELECT COUNT(*)");
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using Marr.Data.Mapping;
namespace Marr.Data.QGen
{
/// <summary>
/// This class represents a table in a query.
/// A table contains corresponding columns.
/// </summary>
public class Table
{
public Table(Type memberType)
: this(memberType, JoinType.None)
{ }
public Table(Type memberType, JoinType joinType)
{
EntityType = memberType;
Name = memberType.GetTableName();
JoinType = joinType;
Columns = MapRepository.Instance.GetColumns(memberType);
}
public bool IsBaseTable
{
get
{
return Alias == "t0";
}
}
public Type EntityType { get; private set; }
public virtual string Name { get; set; }
public JoinType JoinType { get; private set; }
public virtual ColumnMapCollection Columns { get; private set; }
public virtual string Alias { get; set; }
public string JoinClause { get; set; }
}
public enum JoinType
{
None,
Inner,
Left,
Right
}
}

View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
namespace Marr.Data.QGen
{
/// <summary>
/// This class holds a collection of Table objects.
/// </summary>
public class TableCollection : IEnumerable<Table>
{
private List<Table> _tables;
public TableCollection()
{
_tables = new List<Table>();
}
public void Add(Table table)
{
if (this.Any(t => t.EntityType == table.EntityType))
{
// Already exists -- don't add
//return; This prevents joining on the same table!
}
// Create an alias (ex: "t0", "t1", "t2", etc...)
table.Alias = string.Format("t{0}", _tables.Count);
_tables.Add(table);
}
public void ReplaceBaseTable(View view)
{
_tables.RemoveAt(0);
Add(view);
}
/// <summary>
/// Tries to find a table for a given member.
/// </summary>
public Table FindTable(Type declaringType)
{
return EnumerateViewsAndTables().Where(t => t.EntityType == declaringType).FirstOrDefault();
}
public Table this[int index]
{
get
{
return _tables[index];
}
}
public int Count
{
get
{
return _tables.Count;
}
}
/// <summary>
/// Recursively enumerates through all tables, including tables embedded in views.
/// </summary>
/// <returns></returns>
public IEnumerable<Table> EnumerateViewsAndTables()
{
foreach (Table table in _tables)
{
if (table is View)
{
foreach (Table viewTable in (table as View))
{
yield return viewTable;
}
}
else
{
yield return table;
}
}
}
public IEnumerator<Table> GetEnumerator()
{
return _tables.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _tables.GetEnumerator();
}
}
}

View File

@ -0,0 +1,56 @@
using System.Text;
using System.Data.Common;
using Marr.Data.Mapping;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
public class UpdateQuery : IQuery
{
protected Dialect Dialect { get; set; }
protected string Target { get; set; }
protected ColumnMapCollection Columns { get; set; }
protected DbCommand Command { get; set; }
protected string WhereClause { get; set; }
public UpdateQuery(Dialect dialect, ColumnMapCollection columns, DbCommand command, string target, string whereClause)
{
Dialect = dialect;
Target = target;
Columns = columns;
Command = command;
WhereClause = whereClause;
}
public string Generate()
{
StringBuilder sql = new StringBuilder();
sql.AppendFormat("UPDATE {0} SET ", Dialect.CreateToken(Target));
int startIndex = sql.Length;
foreach (DbParameter p in Command.Parameters)
{
var c = Columns.GetByColumnName(p.ParameterName);
if (c == null)
break; // All SET columns have been added
if (sql.Length > startIndex)
sql.Append(",");
if (!c.ColumnInfo.IsAutoIncrement)
{
sql.AppendFormat("{0}={1}{2}", Dialect.CreateToken(c.ColumnInfo.Name), Command.ParameterPrefix(), p.ParameterName);
}
}
sql.AppendFormat(" {0}", WhereClause);
return sql.ToString();
}
}
}

View File

@ -0,0 +1,175 @@
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 UpdateQueryBuilder<T>
{
private DataMapper _db;
private string _tableName;
private T _entity;
private MappingHelper _mappingHelper;
private ColumnMapCollection _mappings;
private SqlModes _previousSqlMode;
private bool _generateQuery = true;
private TableCollection _tables;
private Expression<Func<T, bool>> _filterExpression;
private Dialect _dialect;
private ColumnMapCollection _columnsToUpdate;
public UpdateQueryBuilder()
{
// Used only for unit testing with mock frameworks
}
public UpdateQueryBuilder(DataMapper db)
{
_db = db;
_tableName = MapRepository.Instance.GetTableName(typeof(T));
_tables = new TableCollection();
_tables.Add(new Table(typeof(T)));
_previousSqlMode = _db.SqlMode;
_mappingHelper = new MappingHelper(_db);
_mappings = MapRepository.Instance.GetColumns(typeof(T));
_dialect = QueryFactory.CreateDialect(_db);
}
public virtual UpdateQueryBuilder<T> TableName(string tableName)
{
_tableName = tableName;
return this;
}
public virtual UpdateQueryBuilder<T> QueryText(string queryText)
{
_generateQuery = false;
_db.Command.CommandText = queryText;
return this;
}
public virtual UpdateQueryBuilder<T> Entity(T entity)
{
_entity = entity;
return this;
}
public virtual UpdateQueryBuilder<T> Where(Expression<Func<T, bool>> filterExpression)
{
_filterExpression = filterExpression;
return this;
}
public virtual UpdateQueryBuilder<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 UpdateQueryBuilder<T> ColumnsIncluding(params string[] properties)
{
_columnsToUpdate = new ColumnMapCollection();
foreach (string propertyName in properties)
{
_columnsToUpdate.Add(_mappings.GetByFieldName(propertyName));
}
return this;
}
public virtual UpdateQueryBuilder<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 UpdateQueryBuilder<T> ColumnsExcluding(params string[] properties)
{
_columnsToUpdate = new ColumnMapCollection();
_columnsToUpdate.AddRange(_mappings);
foreach (string propertyName in properties)
{
_columnsToUpdate.RemoveAll(c => c.FieldName == propertyName);
}
return this;
}
public virtual string BuildQuery()
{
if (_entity == null)
throw new ArgumentNullException("You must specify an entity to update.");
// Override SqlMode since we know this will be a text query
_db.SqlMode = SqlModes.Text;
var columnsToUpdate = _columnsToUpdate ?? _mappings;
_mappingHelper.CreateParameters<T>(_entity, columnsToUpdate, _generateQuery);
string where = string.Empty;
if (_filterExpression != null)
{
var whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, _filterExpression, _tables, false, false);
where = whereBuilder.ToString();
}
IQuery query = QueryFactory.CreateUpdateQuery(columnsToUpdate, _db, _tableName, where);
_db.Command.CommandText = query.Generate();
return _db.Command.CommandText;
}
public virtual int Execute()
{
if (_generateQuery)
{
BuildQuery();
}
else
{
_mappingHelper.CreateParameters<T>(_entity, _mappings, _generateQuery);
}
int rowsAffected = 0;
try
{
_db.OpenConnection();
rowsAffected = _db.Command.ExecuteNonQuery();
_mappingHelper.SetOutputValues<T>(_entity, _mappings.OutputFields);
}
finally
{
_db.CloseConnection();
}
if (_generateQuery)
{
// Return to previous sql mode
_db.SqlMode = _previousSqlMode;
}
return rowsAffected;
}
}
}

View File

@ -0,0 +1,90 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Marr.Data.Mapping;
namespace Marr.Data.QGen
{
/// <summary>
/// This class represents a View. A view can hold multiple tables (and their columns).
/// </summary>
public class View : Table, IEnumerable<Table>
{
private string _viewName;
private Table[] _tables;
private ColumnMapCollection _columns;
public View(string viewName, Table[] tables)
: base(tables[0].EntityType, JoinType.None)
{
_viewName = viewName;
_tables = tables;
}
public Table[] Tables
{
get { return _tables; }
}
public override string Name
{
get
{
return _viewName;
}
set
{
_viewName = value;
}
}
public override string Alias
{
get
{
return base.Alias;
}
set
{
base.Alias = value;
// Sync view tables
foreach (Table table in _tables)
{
table.Alias = value;
}
}
}
/// <summary>
/// Gets all the columns from all the tables included in the view.
/// </summary>
public override ColumnMapCollection Columns
{
get
{
if (_columns == null)
{
var allColumns = _tables.SelectMany(t => t.Columns);
_columns = new ColumnMapCollection();
_columns.AddRange(allColumns);
}
return _columns;
}
}
public IEnumerator<Table> GetEnumerator()
{
foreach (Table table in _tables)
{
yield return table;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;
using System.Data.Common;
using Marr.Data.Parameters;
using System.Reflection;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// This class utilizes the ExpressionVisitor base class, and it is responsible for creating the "WHERE" clause.
/// It builds a protected StringBuilder class whose output is created when the ToString method is called.
/// It also has some methods that coincide with Linq methods, to provide Linq compatibility.
/// </summary>
/// <typeparam name="T"></typeparam>
public class WhereBuilder<T> : ExpressionVisitor
{
private string _constantWhereClause;
private MapRepository _repos;
private DbCommand _command;
private string _paramPrefix;
private bool _isLeftSide = true;
protected bool _useAltName;
protected Dialect _dialect;
protected StringBuilder _sb;
protected TableCollection _tables;
protected bool _tablePrefix;
public WhereBuilder(string whereClause, bool useAltName)
{
_constantWhereClause = whereClause;
_useAltName = useAltName;
}
public WhereBuilder(DbCommand command, Dialect dialect, Expression filter, TableCollection tables, bool useAltName, bool tablePrefix)
{
_repos = MapRepository.Instance;
_command = command;
_dialect = dialect;
_paramPrefix = command.ParameterPrefix();
_sb = new StringBuilder();
_useAltName = useAltName;
_tables = tables;
_tablePrefix = tablePrefix;
if (filter != null)
{
_sb.AppendFormat("{0} ", PrefixText);
base.Visit(filter);
}
}
protected virtual string PrefixText
{
get
{
return "WHERE";
}
}
protected override Expression VisitBinary(BinaryExpression expression)
{
_sb.Append("(");
_isLeftSide = true;
Visit(expression.Left);
_sb.AppendFormat(" {0} ", Decode(expression));
_isLeftSide = false;
Visit(expression.Right);
_sb.Append(")");
return expression;
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
string method = (expression as MethodCallExpression).Method.Name;
switch (method)
{
case "Contains":
Write_Contains(expression);
break;
case "StartsWith":
Write_StartsWith(expression);
break;
case "EndsWith":
Write_EndsWith(expression);
break;
case "In":
Write_In(expression);
break;
default:
string msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
throw new NotImplementedException(msg);
}
return expression;
}
protected override Expression VisitMemberAccess(MemberExpression expression)
{
if (_isLeftSide)
{
string fqColumn = GetFullyQualifiedColumnName(expression.Member, expression.Expression.Type);
_sb.Append(fqColumn);
}
else
{
// Add parameter to Command.Parameters
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
_sb.Append(paramName);
object value = GetRightValue(expression);
new ParameterChainMethods(_command, paramName, value);
}
return expression;
}
protected override Expression VisitConstant(ConstantExpression expression)
{
if (expression.Value != null)
{
// Add parameter to Command.Parameters
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
_sb.Append(paramName);
var parameter = new ParameterChainMethods(_command, paramName, expression.Value).Parameter;
}
else
{
_sb.Append("NULL");
}
return expression;
}
private object GetRightValue(Expression expression)
{
object rightValue = null;
var simpleConstExp = expression as ConstantExpression;
if (simpleConstExp == null) // Value is not directly passed in as a constant
{
MemberExpression memberExp = expression as MemberExpression;
ConstantExpression constExp = null;
// Value may be nested in multiple levels of objects/properties, so traverse the MemberExpressions
// until a ConstantExpression property value is found, and then unwind the stack to get the value.
var memberNames = new Stack<string>();
while (memberExp != null)
{
memberNames.Push(memberExp.Member.Name);
// Function calls are not supported - user needs to simplify their Where expression.
var methodExp = memberExp.Expression as MethodCallExpression;
if (methodExp != null)
{
var errMsg = string.Format("Function calls are not supported by the Where clause expression parser. Please evaluate your function call, '{0}', manually and then use the resulting paremeter value in your Where expression.", methodExp.Method.Name);
throw new NotSupportedException(errMsg);
}
constExp = memberExp.Expression as ConstantExpression;
memberExp = memberExp.Expression as MemberExpression;
}
object entity = constExp.Value;
while (memberNames.Count > 0)
{
string entityName = memberNames.Pop();
entity = _repos.ReflectionStrategy.GetFieldValue(entity, entityName);
}
rightValue = entity;
}
else // Value is passed in directly as a constant
{
rightValue = simpleConstExp.Value;
}
return rightValue;
}
protected string GetFullyQualifiedColumnName(MemberInfo member, Type declaringType)
{
if (_tablePrefix)
{
Table table = _tables.FindTable(declaringType);
if (table == null)
{
string msg = string.Format("The property '{0} -> {1}' you are trying to reference in the 'WHERE' 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.",
declaringType,
member.Name);
throw new DataMappingException(msg);
}
string columnName = DataHelper.GetColumnName(declaringType, member.Name, _useAltName);
return _dialect.CreateToken(string.Format("{0}.{1}", table.Alias, columnName));
}
else
{
string columnName = DataHelper.GetColumnName(declaringType, member.Name, _useAltName);
return _dialect.CreateToken(columnName);
}
}
private string Decode(BinaryExpression expression)
{
bool isRightSideNullConstant = expression.Right.NodeType ==
ExpressionType.Constant &&
((ConstantExpression)expression.Right).Value == null;
if (isRightSideNullConstant)
{
switch (expression.NodeType)
{
case ExpressionType.Equal: return "IS";
case ExpressionType.NotEqual: return "IS NOT";
}
}
switch (expression.NodeType)
{
case ExpressionType.AndAlso: return "AND";
case ExpressionType.And: return "AND";
case ExpressionType.Equal: return "=";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
case ExpressionType.NotEqual: return "<>";
case ExpressionType.OrElse: return "OR";
case ExpressionType.Or: return "OR";
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
}
}
private void Write_Contains(MethodCallExpression body)
{
// Add parameter to Command.Parameters
object value = GetRightValue(body.Arguments[0]);
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Object as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat(_dialect.ContainsFormat, fqColumn, paramName);
}
private void Write_In(MethodCallExpression body)
{
object value = GetRightValue(body.Arguments[1]);
//string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
//var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Arguments[0] as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat(_dialect.InFormat, fqColumn, string.Join(",", value as List<int>));
}
private void Write_StartsWith(MethodCallExpression body)
{
// Add parameter to Command.Parameters
object value = GetRightValue(body.Arguments[0]);
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Object as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat(_dialect.StartsWithFormat, fqColumn, paramName);
}
private void Write_EndsWith(MethodCallExpression body)
{
// Add parameter to Command.Parameters
object value = GetRightValue(body.Arguments[0]);
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Object as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat(_dialect.EndsWithFormat, fqColumn, paramName);
}
/// <summary>
/// Appends the current where clause with another where clause.
/// </summary>
/// <param name="where">The second where clause that is being appended.</param>
/// <param name="appendType">AND / OR</param>
internal void Append(WhereBuilder<T> where, WhereAppendType appendType)
{
_constantWhereClause = string.Format("{0} {1} {2}",
ToString(),
appendType.ToString(),
where.ToString().Replace("WHERE ", string.Empty));
}
public override string ToString()
{
if (string.IsNullOrEmpty(_constantWhereClause))
{
return _sb.ToString();
}
return _constantWhereClause;
}
}
internal enum WhereAppendType
{
AND,
OR
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace Marr.Data.Reflection
{
public interface IReflectionStrategy
{
object GetFieldValue(object entity, string fieldName);
GetterDelegate BuildGetter(Type type, string memberName);
SetterDelegate BuildSetter(Type type, string memberName);
object CreateInstance(Type type);
}
public delegate void SetterDelegate(object instance, object value);
public delegate object GetterDelegate(object instance);
}

View File

@ -0,0 +1,67 @@
/* 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.Reflection
{
public class ReflectionHelper
{
/// <summary>
/// Converts a DBNull.Value to a null for a reference field,
/// or the default value of a value field.
/// </summary>
/// <param name="fieldType"></param>
/// <returns></returns>
public static object GetDefaultValue(Type fieldType)
{
if (fieldType.IsGenericType)
{
return null;
}
if (fieldType.IsValueType)
{
return Activator.CreateInstance(fieldType);
}
return null;
}
/// <summary>
/// Gets the CLR data type of a MemberInfo.
/// If the type is nullable, returns the underlying type.
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
public static Type GetMemberType(MemberInfo member)
{
Type memberType = null;
if (member.MemberType == MemberTypes.Property)
memberType = (member as PropertyInfo).PropertyType;
else if (member.MemberType == MemberTypes.Field)
memberType = (member as FieldInfo).FieldType;
else
memberType = typeof(object);
// Handle nullable types - get underlying type
if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
memberType = memberType.GetGenericArguments()[0];
}
return memberType;
}
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Marr.Data.Reflection
{
public class SimpleReflectionStrategy : IReflectionStrategy
{
private static readonly Dictionary<string, MemberInfo> MemberCache = new Dictionary<string, MemberInfo>();
private static readonly Dictionary<string, GetterDelegate> GetterCache = new Dictionary<string, GetterDelegate>();
private static readonly Dictionary<string, SetterDelegate> SetterCache = new Dictionary<string, SetterDelegate>();
private static MemberInfo GetMember(Type entityType, string name)
{
MemberInfo member;
var key = entityType.FullName + name;
if (!MemberCache.TryGetValue(key, out member))
{
member = entityType.GetMember(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0];
MemberCache[key] = member;
}
return member;
}
/// <summary>
/// Gets an entity field value by name.
/// </summary>
public object GetFieldValue(object entity, string fieldName)
{
var member = GetMember(entity.GetType(), fieldName);
if (member.MemberType == MemberTypes.Field)
{
return (member as FieldInfo).GetValue(entity);
}
if (member.MemberType == MemberTypes.Property)
{
return BuildGetter(entity.GetType(), fieldName)(entity);
}
throw new DataMappingException(string.Format("The DataMapper could not get the value for {0}.{1}.", entity.GetType().Name, fieldName));
}
/// <summary>
/// Instantiates a type using the FastReflector library for increased speed.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public object CreateInstance(Type type)
{
return Activator.CreateInstance(type);
}
public GetterDelegate BuildGetter(Type type, string memberName)
{
GetterDelegate getter;
var key = type.FullName + memberName;
if (!GetterCache.TryGetValue(key, out getter))
{
getter = GetPropertyGetter((PropertyInfo)GetMember(type, memberName));
}
return getter;
}
public SetterDelegate BuildSetter(Type type, string memberName)
{
SetterDelegate setter;
var key = type.FullName + memberName;
if (!SetterCache.TryGetValue(key, out setter))
{
setter = GetPropertySetter((PropertyInfo)GetMember(type, memberName));
}
return setter;
}
private static SetterDelegate GetPropertySetter(PropertyInfo propertyInfo)
{
var propertySetMethod = propertyInfo.GetSetMethod();
if (propertySetMethod == null) return null;
#if NO_EXPRESSIONS
return (o, convertedValue) =>
{
propertySetMethod.Invoke(o, new[] { convertedValue });
return;
};
#else
var instance = Expression.Parameter(typeof(object), "i");
var argument = Expression.Parameter(typeof(object), "a");
var instanceParam = Expression.Convert(instance, propertyInfo.DeclaringType);
var valueParam = Expression.Convert(argument, propertyInfo.PropertyType);
var setterCall = Expression.Call(instanceParam, propertyInfo.GetSetMethod(), valueParam);
return Expression.Lambda<SetterDelegate>(setterCall, instance, argument).Compile();
#endif
}
private static GetterDelegate GetPropertyGetter(PropertyInfo propertyInfo)
{
var getMethodInfo = propertyInfo.GetGetMethod();
if (getMethodInfo == null) return null;
#if NO_EXPRESSIONS
return o => propertyInfo.GetGetMethod().Invoke(o, new object[] { });
#else
try
{
var oInstanceParam = Expression.Parameter(typeof(object), "oInstanceParam");
var instanceParam = Expression.Convert(oInstanceParam, propertyInfo.DeclaringType);
var exprCallPropertyGetFn = Expression.Call(instanceParam, getMethodInfo);
var oExprCallPropertyGetFn = Expression.Convert(exprCallPropertyGetFn, typeof(object));
var propertyGetFn = Expression.Lambda<GetterDelegate>
(
oExprCallPropertyGetFn,
oInstanceParam
).Compile();
return propertyGetFn;
}
catch (Exception ex)
{
Console.Write(ex.Message);
throw;
}
#endif
}
}
}

View File

@ -0,0 +1,23 @@
/* 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
{
public enum SqlModes
{
StoredProcedure,
Text
}
}

127
src/Marr.Data/UnitOfWork.cs Normal file
View File

@ -0,0 +1,127 @@
using System;
using System.Data;
using System.Runtime.Serialization;
namespace Marr.Data
{
/// <summary>
/// The UnitOfWork class can be used to manage the lifetime of an IDataMapper, from creation to disposal.
/// When used in a "using" statement, the UnitOfWork will create and dispose an IDataMapper.
/// When the SharedContext property is used in a "using" statement,
/// it will create a parent unit of work that will share a single IDataMapper with other units of work,
/// and the IDataMapper will not be disposed until the shared context is disposed.
/// If more than one shared context is created, the IDataMapper will be disposed when the outer most
/// shared context is disposed.
/// </summary>
/// <remarks>
/// It should be noted that the Dispose method on the UnitOfWork class only affects the managed IDataMapper.
/// The UnitOfWork instance itself is not affected by the Dispose method.
/// </remarks>
public class UnitOfWork : IDisposable
{
private Func<IDataMapper> _dbConstructor;
private IDataMapper _lazyLoadedDB;
private short _transactionCount;
public UnitOfWork(Func<IDataMapper> dbConstructor)
{
_dbConstructor = dbConstructor;
}
/// <summary>
/// Gets an IDataMapper object whose lifetime is managed by the UnitOfWork class.
/// </summary>
public IDataMapper DB
{
get
{
if (_lazyLoadedDB == null)
{
_lazyLoadedDB = _dbConstructor.Invoke();
}
return _lazyLoadedDB;
}
}
/// <summary>
/// Instructs the UnitOfWork to share a single IDataMapper instance.
/// </summary>
public UnitOfWorkSharedContext SharedContext
{
get
{
return new UnitOfWorkSharedContext(this);
}
}
public void BeginTransaction(IsolationLevel isolationLevel)
{
// Only allow one transaction to begin
if (_transactionCount < 1)
{
DB.BeginTransaction(isolationLevel);
}
_transactionCount++;
}
public void Commit()
{
// Only allow the outermost transaction to commit (all nested transactions must succeed)
if (_transactionCount == 1)
{
DB.Commit();
}
_transactionCount--;
}
public void RollBack()
{
// Any level transaction should be allowed to rollback
DB.RollBack();
// Throw an exception if a nested ShareContext transaction rolls back
if (_transactionCount > 1)
{
throw new NestedSharedContextRollBackException();
}
_transactionCount--;
}
public void Dispose()
{
if (!IsShared)
{
ForceDispose();
}
}
internal bool IsShared { get; set; }
private void ForceDispose()
{
_transactionCount = 0;
if (_lazyLoadedDB != null)
{
_lazyLoadedDB.Dispose();
_lazyLoadedDB = null;
}
}
}
[Serializable]
public class NestedSharedContextRollBackException : Exception
{
public NestedSharedContextRollBackException() { }
public NestedSharedContextRollBackException(string message) : base(message) { }
public NestedSharedContextRollBackException(string message, Exception inner) : base(message, inner) { }
protected NestedSharedContextRollBackException(
SerializationInfo info,
StreamingContext context)
: base(info, context) { }
}
}

View File

@ -0,0 +1,38 @@
using System;
namespace Marr.Data
{
/// <summary>
/// Works in conjunction with the UnitOfWork to create a new
/// shared context that will preserve a single IDataMapper.
/// </summary>
public class UnitOfWorkSharedContext : IDisposable
{
private UnitOfWork _mgr;
private bool _isParentContext;
public UnitOfWorkSharedContext(UnitOfWork mgr)
{
_mgr = mgr;
if (_mgr.IsShared)
{
_isParentContext = false;
}
else
{
_isParentContext = true;
_mgr.IsShared = true;
}
}
public void Dispose()
{
if (_isParentContext)
{
_mgr.IsShared = false;
_mgr.Dispose();
}
}
}
}

View File

@ -9,6 +9,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test.Common", "Test.Common"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowsServiceHelpers", "WindowsServiceHelpers", "{F9E67978-5CD6-4A5F-827B-4249711C0B02}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceHelpers", "ServiceHelpers", "{CF5BF374-71E4-485E-A74C-39B581323D9A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{486ADF86-DD89-4E19-B805-9D94F19800D9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}"
@ -23,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Api.V3", "Radarr.Api
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Http", "Radarr.Http\Radarr.Http.csproj", "{F8A02FD4-A7A4-40D0-BB81-6319105A3302}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marr.Data", "Marr.Data\Marr.Data.csproj", "{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoTorrent", "MonoTorrent\MonoTorrent.csproj", "{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Api.Test", "NzbDrone.Api.Test\Radarr.Api.Test.csproj", "{E2EA47B1-6996-417D-A6EC-28C4F202715C}"
@ -103,6 +107,14 @@ Global
{F8A02FD4-A7A4-40D0-BB81-6319105A3302}.Release|Posix.Build.0 = Release|Any CPU
{F8A02FD4-A7A4-40D0-BB81-6319105A3302}.Release|Windows.ActiveCfg = Release|Any CPU
{F8A02FD4-A7A4-40D0-BB81-6319105A3302}.Release|Windows.Build.0 = Release|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Debug|Posix.ActiveCfg = Debug|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Debug|Posix.Build.0 = Debug|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Debug|Windows.ActiveCfg = Debug|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Debug|Windows.Build.0 = Debug|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Release|Posix.ActiveCfg = Release|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Release|Posix.Build.0 = Release|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Release|Windows.ActiveCfg = Release|Any CPU
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A}.Release|Windows.Build.0 = Release|Any CPU
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Posix.ActiveCfg = Debug|Any CPU
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Posix.Build.0 = Debug|Any CPU
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Windows.ActiveCfg = Debug|Any CPU
@ -300,6 +312,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{47697CDB-27B6-4B05-B4F8-0CBE6F6EDF97} = {57A04B72-8088-4F75-A582-1158CF8291F7}
{4EACDBBC-BCD7-4765-A57B-3E08331E4749} = {57A04B72-8088-4F75-A582-1158CF8291F7}
{8D7D5F17-96BB-4EDD-A6E6-3BCA2D6B401A} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
{E2EA47B1-6996-417D-A6EC-28C4F202715C} = {57A04B72-8088-4F75-A582-1158CF8291F7}
{2356C987-F992-4084-9DA2-5DAD1DA35E85} = {57A04B72-8088-4F75-A582-1158CF8291F7}