From 3cdff3bb71252f29d16d1603e1a0b3595ada7330 Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 30 Mar 2013 14:56:34 -0700 Subject: [PATCH] added marr.datamapper source code for easy debugging. --- Libraries/FastReflection/FastReflection.dll | Bin 0 -> 22528 bytes Marr.Data/Converters/BooleanIntConverter.cs | 78 ++ Marr.Data/Converters/BooleanYNConverter.cs | 78 ++ Marr.Data/Converters/CastConverter.cs | 49 + Marr.Data/Converters/ConversionException.cs | 29 + Marr.Data/Converters/EnumIntConverter.cs | 50 + Marr.Data/Converters/EnumStringConverter.cs | 50 + Marr.Data/Converters/IConverter.cs | 30 + Marr.Data/DataHelper.cs | 165 +++ Marr.Data/DataMapper.cs | 996 ++++++++++++++++++ Marr.Data/DataMappingException.cs | 25 + Marr.Data/EntityGraph.cs | 376 +++++++ Marr.Data/EntityMerger.cs | 37 + Marr.Data/EntityReference.cs | 31 + Marr.Data/ExtensionMethods.cs | 23 + Marr.Data/GroupingKeyCollection.cs | 85 ++ Marr.Data/IDataMapper.cs | 220 ++++ Marr.Data/LazyLoaded.cs | 129 +++ Marr.Data/MapRepository.cs | 265 +++++ Marr.Data/Mapping/ColumnAttribute.cs | 122 +++ Marr.Data/Mapping/ColumnInfo.cs | 38 + Marr.Data/Mapping/ColumnMap.cs | 69 ++ Marr.Data/Mapping/ColumnMapBuilder.cs | 240 +++++ Marr.Data/Mapping/ColumnMapCollection.cs | 176 ++++ Marr.Data/Mapping/EnumConversionType.cs | 29 + Marr.Data/Mapping/FluentMappings.cs | 236 +++++ Marr.Data/Mapping/IColumnInfo.cs | 37 + Marr.Data/Mapping/IRelationshipInfo.cs | 34 + Marr.Data/Mapping/MapBuilder.cs | 208 ++++ Marr.Data/Mapping/MappingHelper.cs | 185 ++++ Marr.Data/Mapping/Relationship.cs | 120 +++ Marr.Data/Mapping/RelationshipAttribute.cs | 77 ++ Marr.Data/Mapping/RelationshipBuilder.cs | 176 ++++ Marr.Data/Mapping/RelationshipCollection.cs | 37 + Marr.Data/Mapping/RelationshipInfo.cs | 14 + .../Strategies/AttributeMapStrategy.cs | 75 ++ .../Strategies/ConventionMapStrategy.cs | 51 + Marr.Data/Mapping/Strategies/IMapStrategy.cs | 48 + .../Mapping/Strategies/PropertyMapStrategy.cs | 85 ++ .../Strategies/ReflectionMapStrategyBase.cs | 151 +++ Marr.Data/Mapping/TableAttribute.cs | 43 + Marr.Data/Mapping/TableBuilder.cs | 61 ++ Marr.Data/Marr.Data.csproj | 164 +++ Marr.Data/NuGet/NuGet.exe | Bin 0 -> 292864 bytes Marr.Data/NuGet/marrdatamapper.nuspec | 20 + Marr.Data/Parameters/DbTypeBuilder.cs | 74 ++ Marr.Data/Parameters/IDbTypeBuilder.cs | 34 + Marr.Data/Parameters/OleDbTypeBuilder.cs | 72 ++ Marr.Data/Parameters/ParameterChainMethods.cs | 124 +++ Marr.Data/Parameters/SqlDbTypeBuilder.cs | 76 ++ Marr.Data/Properties/AssemblyInfo.cs | 41 + Marr.Data/QGen/DeleteQuery.cs | 33 + Marr.Data/QGen/Dialects/Dialect.cs | 59 ++ Marr.Data/QGen/Dialects/FirebirdDialect.cs | 20 + Marr.Data/QGen/Dialects/OracleDialect.cs | 36 + Marr.Data/QGen/Dialects/SqlServerCeDialect.cs | 26 + Marr.Data/QGen/Dialects/SqlServerDialect.cs | 18 + Marr.Data/QGen/Dialects/SqliteDialect.cs | 18 + Marr.Data/QGen/ExpressionVisitor.cs | 149 +++ Marr.Data/QGen/IQuery.cs | 16 + Marr.Data/QGen/IQueryBuilder.cs | 17 + Marr.Data/QGen/InsertQuery.cs | 70 ++ Marr.Data/QGen/InsertQueryBuilder.cs | 204 ++++ Marr.Data/QGen/JoinBuilder.cs | 39 + Marr.Data/QGen/PagingQueryDecorator.cs | 239 +++++ Marr.Data/QGen/QueryBuilder.cs | 532 ++++++++++ Marr.Data/QGen/QueryFactory.cs | 110 ++ Marr.Data/QGen/QueryQueueItem.cs | 19 + Marr.Data/QGen/RowCountQueryDecorator.cs | 127 +++ Marr.Data/QGen/SelectQuery.cs | 157 +++ Marr.Data/QGen/SortBuilder.cs | 246 +++++ Marr.Data/QGen/SortColumn.cs | 50 + Marr.Data/QGen/Table.cs | 50 + Marr.Data/QGen/TableCollection.cs | 97 ++ Marr.Data/QGen/UpdateQuery.cs | 59 ++ Marr.Data/QGen/UpdateQueryBuilder.cs | 176 ++++ Marr.Data/QGen/View.cs | 91 ++ Marr.Data/QGen/WhereBuilder.cs | 279 +++++ .../Reflection/CachedReflectionStrategy.cs | 90 ++ Marr.Data/Reflection/IReflectionStrategy.cs | 14 + Marr.Data/Reflection/ReflectionHelper.cs | 80 ++ .../Reflection/SimpleReflectionStrategy.cs | 85 ++ Marr.Data/SqlModesEnum.cs | 27 + Marr.Data/UnitOfWork.cs | 128 +++ Marr.Data/UnitOfWorkSharedContext.cs | 41 + NzbDrone.Api.Test/NzbDrone.Api.Test.csproj | 3 + NzbDrone.Api/Series/SeriesModule.cs | 1 - .../JobTests/ImportNewSeriesJobTest.cs | 218 ---- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 13 +- NzbDrone.Core.Test/packages.config | 1 - .../Implementations/ImportNewSeriesJob.cs | 118 --- .../Jobs/Implementations/UpdateInfoJob.cs | 17 +- NzbDrone.Core/NzbDrone.Core.csproj | 11 +- NzbDrone.Core/Tv/SeriesService.cs | 7 +- NzbDrone.Core/packages.config | 1 - NzbDrone.sln | 136 +++ 96 files changed, 9198 insertions(+), 363 deletions(-) create mode 100644 Libraries/FastReflection/FastReflection.dll create mode 100644 Marr.Data/Converters/BooleanIntConverter.cs create mode 100644 Marr.Data/Converters/BooleanYNConverter.cs create mode 100644 Marr.Data/Converters/CastConverter.cs create mode 100644 Marr.Data/Converters/ConversionException.cs create mode 100644 Marr.Data/Converters/EnumIntConverter.cs create mode 100644 Marr.Data/Converters/EnumStringConverter.cs create mode 100644 Marr.Data/Converters/IConverter.cs create mode 100644 Marr.Data/DataHelper.cs create mode 100644 Marr.Data/DataMapper.cs create mode 100644 Marr.Data/DataMappingException.cs create mode 100644 Marr.Data/EntityGraph.cs create mode 100644 Marr.Data/EntityMerger.cs create mode 100644 Marr.Data/EntityReference.cs create mode 100644 Marr.Data/ExtensionMethods.cs create mode 100644 Marr.Data/GroupingKeyCollection.cs create mode 100644 Marr.Data/IDataMapper.cs create mode 100644 Marr.Data/LazyLoaded.cs create mode 100644 Marr.Data/MapRepository.cs create mode 100644 Marr.Data/Mapping/ColumnAttribute.cs create mode 100644 Marr.Data/Mapping/ColumnInfo.cs create mode 100644 Marr.Data/Mapping/ColumnMap.cs create mode 100644 Marr.Data/Mapping/ColumnMapBuilder.cs create mode 100644 Marr.Data/Mapping/ColumnMapCollection.cs create mode 100644 Marr.Data/Mapping/EnumConversionType.cs create mode 100644 Marr.Data/Mapping/FluentMappings.cs create mode 100644 Marr.Data/Mapping/IColumnInfo.cs create mode 100644 Marr.Data/Mapping/IRelationshipInfo.cs create mode 100644 Marr.Data/Mapping/MapBuilder.cs create mode 100644 Marr.Data/Mapping/MappingHelper.cs create mode 100644 Marr.Data/Mapping/Relationship.cs create mode 100644 Marr.Data/Mapping/RelationshipAttribute.cs create mode 100644 Marr.Data/Mapping/RelationshipBuilder.cs create mode 100644 Marr.Data/Mapping/RelationshipCollection.cs create mode 100644 Marr.Data/Mapping/RelationshipInfo.cs create mode 100644 Marr.Data/Mapping/Strategies/AttributeMapStrategy.cs create mode 100644 Marr.Data/Mapping/Strategies/ConventionMapStrategy.cs create mode 100644 Marr.Data/Mapping/Strategies/IMapStrategy.cs create mode 100644 Marr.Data/Mapping/Strategies/PropertyMapStrategy.cs create mode 100644 Marr.Data/Mapping/Strategies/ReflectionMapStrategyBase.cs create mode 100644 Marr.Data/Mapping/TableAttribute.cs create mode 100644 Marr.Data/Mapping/TableBuilder.cs create mode 100644 Marr.Data/Marr.Data.csproj create mode 100644 Marr.Data/NuGet/NuGet.exe create mode 100644 Marr.Data/NuGet/marrdatamapper.nuspec create mode 100644 Marr.Data/Parameters/DbTypeBuilder.cs create mode 100644 Marr.Data/Parameters/IDbTypeBuilder.cs create mode 100644 Marr.Data/Parameters/OleDbTypeBuilder.cs create mode 100644 Marr.Data/Parameters/ParameterChainMethods.cs create mode 100644 Marr.Data/Parameters/SqlDbTypeBuilder.cs create mode 100644 Marr.Data/Properties/AssemblyInfo.cs create mode 100644 Marr.Data/QGen/DeleteQuery.cs create mode 100644 Marr.Data/QGen/Dialects/Dialect.cs create mode 100644 Marr.Data/QGen/Dialects/FirebirdDialect.cs create mode 100644 Marr.Data/QGen/Dialects/OracleDialect.cs create mode 100644 Marr.Data/QGen/Dialects/SqlServerCeDialect.cs create mode 100644 Marr.Data/QGen/Dialects/SqlServerDialect.cs create mode 100644 Marr.Data/QGen/Dialects/SqliteDialect.cs create mode 100644 Marr.Data/QGen/ExpressionVisitor.cs create mode 100644 Marr.Data/QGen/IQuery.cs create mode 100644 Marr.Data/QGen/IQueryBuilder.cs create mode 100644 Marr.Data/QGen/InsertQuery.cs create mode 100644 Marr.Data/QGen/InsertQueryBuilder.cs create mode 100644 Marr.Data/QGen/JoinBuilder.cs create mode 100644 Marr.Data/QGen/PagingQueryDecorator.cs create mode 100644 Marr.Data/QGen/QueryBuilder.cs create mode 100644 Marr.Data/QGen/QueryFactory.cs create mode 100644 Marr.Data/QGen/QueryQueueItem.cs create mode 100644 Marr.Data/QGen/RowCountQueryDecorator.cs create mode 100644 Marr.Data/QGen/SelectQuery.cs create mode 100644 Marr.Data/QGen/SortBuilder.cs create mode 100644 Marr.Data/QGen/SortColumn.cs create mode 100644 Marr.Data/QGen/Table.cs create mode 100644 Marr.Data/QGen/TableCollection.cs create mode 100644 Marr.Data/QGen/UpdateQuery.cs create mode 100644 Marr.Data/QGen/UpdateQueryBuilder.cs create mode 100644 Marr.Data/QGen/View.cs create mode 100644 Marr.Data/QGen/WhereBuilder.cs create mode 100644 Marr.Data/Reflection/CachedReflectionStrategy.cs create mode 100644 Marr.Data/Reflection/IReflectionStrategy.cs create mode 100644 Marr.Data/Reflection/ReflectionHelper.cs create mode 100644 Marr.Data/Reflection/SimpleReflectionStrategy.cs create mode 100644 Marr.Data/SqlModesEnum.cs create mode 100644 Marr.Data/UnitOfWork.cs create mode 100644 Marr.Data/UnitOfWorkSharedContext.cs delete mode 100644 NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs delete mode 100644 NzbDrone.Core/Jobs/Implementations/ImportNewSeriesJob.cs diff --git a/Libraries/FastReflection/FastReflection.dll b/Libraries/FastReflection/FastReflection.dll new file mode 100644 index 0000000000000000000000000000000000000000..947231aab3c1a3198634cde53235cf0e60ef4fb9 GIT binary patch literal 22528 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. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.Converters +{ + public class BooleanIntConverter : IConverter + { + public object FromDB(ColumnMap map, object dbValue) + { + if (dbValue == DBNull.Value) + { + return DBNull.Value; + } + + int val = (int)dbValue; + + if (val == 1) + { + return true; + } + else if (val == 0) + { + return false; + } + else + { + throw new ConversionException( + string.Format( + "The BooleanCharConverter could not convert the value '{0}' to a boolean.", + dbValue)); + } + } + + public object ToDB(object clrValue) + { + bool? val = (bool?)clrValue; + + if (val == true) + { + return 1; + } + else if (val == false) + { + return 0; + } + else + { + return DBNull.Value; + } + } + + public Type DbType + { + get + { + return typeof(int); + } + } + } +} diff --git a/Marr.Data/Converters/BooleanYNConverter.cs b/Marr.Data/Converters/BooleanYNConverter.cs new file mode 100644 index 000000000..3f3acf0c1 --- /dev/null +++ b/Marr.Data/Converters/BooleanYNConverter.cs @@ -0,0 +1,78 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.Converters +{ + public class BooleanYNConverter : IConverter + { + public object FromDB(ColumnMap map, object dbValue) + { + if (dbValue == DBNull.Value) + { + return DBNull.Value; + } + + string val = dbValue.ToString(); + + if (val == "Y") + { + return true; + } + else if (val == "N") + { + return false; + } + else + { + throw new ConversionException( + string.Format( + "The BooleanYNConverter could not convert the value '{0}' to a boolean.", + dbValue)); + } + } + + public object ToDB(object clrValue) + { + bool? val = (bool?)clrValue; + + if (val == true) + { + return "Y"; + } + else if (val == false) + { + return "N"; + } + else + { + return DBNull.Value; + } + } + + public Type DbType + { + get + { + return typeof(string); + } + } + } +} diff --git a/Marr.Data/Converters/CastConverter.cs b/Marr.Data/Converters/CastConverter.cs new file mode 100644 index 000000000..2c1913966 --- /dev/null +++ b/Marr.Data/Converters/CastConverter.cs @@ -0,0 +1,49 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Converters +{ + public class CastConverter : Marr.Data.Converters.IConverter + where TClr : IConvertible + where TDb : IConvertible + { + #region IConversion Members + + public Type DbType + { + get { return typeof(TDb); } + } + + public object FromDB(Marr.Data.Mapping.ColumnMap map, object dbValue) + { + TDb val = (TDb)dbValue; + return val.ToType(typeof(TClr), System.Globalization.CultureInfo.InvariantCulture); + } + + public object ToDB(object clrValue) + { + TClr val = (TClr)clrValue; + return val.ToType(typeof(TDb), System.Globalization.CultureInfo.InvariantCulture); + } + + #endregion + } +} + diff --git a/Marr.Data/Converters/ConversionException.cs b/Marr.Data/Converters/ConversionException.cs new file mode 100644 index 000000000..074b5604f --- /dev/null +++ b/Marr.Data/Converters/ConversionException.cs @@ -0,0 +1,29 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Converters +{ + public class ConversionException : Exception + { + public ConversionException(string message) + : base(message) + { } + } +} diff --git a/Marr.Data/Converters/EnumIntConverter.cs b/Marr.Data/Converters/EnumIntConverter.cs new file mode 100644 index 000000000..41515737e --- /dev/null +++ b/Marr.Data/Converters/EnumIntConverter.cs @@ -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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.Converters +{ + public class EnumIntConverter : IConverter + { + public object FromDB(ColumnMap map, object dbValue) + { + if (dbValue == null || dbValue == DBNull.Value) + return null; + else + return Enum.ToObject(map.FieldType, (int)dbValue); + } + + public object ToDB(object clrValue) + { + if (clrValue == null) + return DBNull.Value; + else + return (int)clrValue; + } + + public Type DbType + { + get + { + return typeof(int); + } + } + } +} diff --git a/Marr.Data/Converters/EnumStringConverter.cs b/Marr.Data/Converters/EnumStringConverter.cs new file mode 100644 index 000000000..964e5824c --- /dev/null +++ b/Marr.Data/Converters/EnumStringConverter.cs @@ -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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.Converters +{ + public class EnumStringConverter : IConverter + { + public object FromDB(ColumnMap map, object dbValue) + { + if (dbValue == null || dbValue == DBNull.Value) + return null; + else + return Enum.Parse(map.FieldType, (string)dbValue); + } + + public object ToDB(object clrValue) + { + if (clrValue == null) + return DBNull.Value; + else + return clrValue.ToString(); + } + + public Type DbType + { + get + { + return typeof(string); + } + } + } +} diff --git a/Marr.Data/Converters/IConverter.cs b/Marr.Data/Converters/IConverter.cs new file mode 100644 index 000000000..00b1fa47b --- /dev/null +++ b/Marr.Data/Converters/IConverter.cs @@ -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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.Converters +{ + public interface IConverter + { + object FromDB(ColumnMap map, object dbValue); + object ToDB(object clrValue); + Type DbType { get; } + } +} diff --git a/Marr.Data/DataHelper.cs b/Marr.Data/DataHelper.cs new file mode 100644 index 000000000..721511a2a --- /dev/null +++ b/Marr.Data/DataHelper.cs @@ -0,0 +1,165 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data; +using System.Reflection; +using Marr.Data.Mapping; +using System.Linq.Expressions; + +namespace Marr.Data +{ + /// + /// This class contains misc. extension methods that are used throughout the project. + /// + 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") ? ":" : "@"; + } + + /// + /// Returns the mapped name, or the member name. + /// + /// + /// + 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(); + } + else + { + return col.Name; + } + } + + /// + /// Returns the mapped column name, or the member name. + /// + /// + /// + 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 (useAltName) + { + columnName = columnMap.ColumnInfo.TryGetAltName(); + } + else + { + columnName = columnMap.ColumnInfo.Name; + } + + return columnName; + } + + /// + /// Determines a property name from a passed in expression. + /// Ex: p => p.FirstName -> "FirstName + /// + /// + /// + /// + public static string GetMemberName(this Expression> 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 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; + } + + /// + /// Determines if a type is not a complex object. + /// + 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<>))); + } + + } +} diff --git a/Marr.Data/DataMapper.cs b/Marr.Data/DataMapper.cs new file mode 100644 index 000000000..8ad948eae --- /dev/null +++ b/Marr.Data/DataMapper.cs @@ -0,0 +1,996 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Reflection; +using System.Collections; +using System.Linq; +using Marr.Data.Mapping; +using Marr.Data.Converters; +using Marr.Data.Parameters; +using Marr.Data.QGen; +using System.Linq.Expressions; +using System.Diagnostics; + +namespace Marr.Data +{ + /// + /// This class is the main access point for making database related calls. + /// + public class DataMapper : IDataMapper + { + + #region - Contructor, Members - + + private DbProviderFactory _dbProviderFactory; + private string _connectionString; + private DbCommand _command; + + /// + /// Initializes a DataMapper for the given provider type and connection string. + /// + /// Ex: + /// The database connection string. + public DataMapper(string providerName, string connectionString) + : this(DbProviderFactories.GetFactory(providerName), connectionString) + { } + + /// + /// A database provider agnostic initialization. + /// + /// The database connection string. + public DataMapper(DbProviderFactory dbProviderFactory, string connectionString) + { + if (dbProviderFactory == null) + throw new ArgumentNullException("dbProviderFactory instance cannot be null."); + + if (string.IsNullOrEmpty(connectionString)) + throw new ArgumentNullException("connectionString cannot be null or empty."); + + _dbProviderFactory = dbProviderFactory; + + _connectionString = connectionString; + } + + public string ConnectionString + { + get + { + return _connectionString; + } + } + + public DbProviderFactory ProviderFactory + { + get + { + return _dbProviderFactory; + } + } + + /// + /// Creates a new command utilizing the connection string. + /// + private DbCommand CreateNewCommand() + { + DbConnection conn = _dbProviderFactory.CreateConnection(); + conn.ConnectionString = _connectionString; + DbCommand cmd = conn.CreateCommand(); + SetSqlMode(cmd); + return cmd; + } + + /// + /// Creates a new command utilizing the connection string with a given SQL command. + /// + private DbCommand CreateNewCommand(string sql) + { + DbCommand cmd = CreateNewCommand(); + cmd.CommandText = sql; + return cmd; + } + + /// + /// Gets or creates a DbCommand object. + /// + 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; + + this.Parameters.Add(parameter); + return parameter; + } + + #endregion + + #region - SP / SQL Mode - + + private SqlModes _sqlMode = SqlModes.StoredProcedure; // Defaults to SP. + /// + /// 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. + /// + public SqlModes SqlMode + { + get + { + return _sqlMode; + } + set + { + _sqlMode = value; + } + } + + /// + /// Sets the DbCommand objects CommandType to the current SqlMode. + /// + /// The DbCommand object we are modifying. + /// Returns the same DbCommand that was passed in. + 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 - + + /// + /// Executes a stored procedure that returns a scalar value. + /// + /// The SQL command to execute. + /// A scalar value + public object ExecuteScalar(string sql) + { + if (string.IsNullOrEmpty(sql)) + throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required"); + else + Command.CommandText = sql; + + try + { + OpenConnection(); + return Command.ExecuteScalar(); + } + finally + { + CloseConnection(); + } + } + + /// + /// Executes a non query that returns an integer. + /// + /// The SQL command to execute. + /// An integer value + public int ExecuteNonQuery(string sql) + { + if (string.IsNullOrEmpty(sql)) + throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required"); + else + Command.CommandText = sql; + + try + { + OpenConnection(); + return Command.ExecuteNonQuery(); + } + finally + { + CloseConnection(); + } + } + + /// + /// Executes a DataReader that can be controlled using a Func delegate. + /// (Note that reader.Read() will be called automatically). + /// + /// The type that will be return in the result set. + /// The sql statement that will be executed. + /// The function that will build the the TResult set. + /// An IEnumerable of TResult. + public IEnumerable ExecuteReader(string sql, Func func) + { + if (string.IsNullOrEmpty(sql)) + throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required"); + else + Command.CommandText = sql; + + try + { + OpenConnection(); + + List list = new List(); + DbDataReader reader = null; + try + { + reader = Command.ExecuteReader(); + + while (reader.Read()) + { + list.Add(func(reader)); + } + + return list; + } + finally + { + if (reader != null) reader.Close(); + } + } + finally + { + CloseConnection(); + } + } + + /// + /// Executes a DataReader that can be controlled using an Action delegate. + /// + /// The sql statement that will be executed. + /// The delegate that will work with the result set. + public void ExecuteReader(string sql, Action action) + { + if (string.IsNullOrEmpty(sql)) + throw new ArgumentNullException("sql", "A SQL query or stored procedure name is required"); + else + 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 = _dbProviderFactory.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 = _dbProviderFactory.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 = _dbProviderFactory.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 this.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 = _dbProviderFactory.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 = _dbProviderFactory.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(string sql) + { + return this.Find(sql, default(T)); + } + + /// + /// Returns an entity of type T. + /// + /// The type of entity that is to be instantiated and loaded with values. + /// The SQL command to execute. + /// An instantiated and loaded entity of type T. + public T Find(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(reader); + } + else + { + if (ent == null) + ent = (T)mappingHelper.CreateAndLoadEntity(mappings, reader, false); + else + mappingHelper.LoadExistingEntity(mappings, reader, ent, false); + } + } + } + } + finally + { + CloseConnection(); + } + + return ent; + } + + #endregion + + #region - Query - + + /// + /// Creates a QueryBuilder that allows you to build a query. + /// + /// The type of object that will be queried. + /// Returns a QueryBuilder of T. + public QueryBuilder Query() + { + var dialect = QGen.QueryFactory.CreateDialect(this); + return new QueryBuilder(this, dialect); + } + + /// + /// Returns the results of a query. + /// Uses a List of type T to return the data. + /// + /// Returns a list of the specified type. + public List Query(string sql) + { + return (List)Query(sql, new List()); + } + + /// + /// Returns the results of a SP query. + /// + /// Returns a list of the specified type. + public ICollection Query(string sql, ICollection entityList) + { + return Query(sql, entityList, false); + } + + internal ICollection Query(string sql, ICollection 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(reader)); + } + else + { + entityList.Add((T)mappingHelper.CreateAndLoadEntity(mappings, reader, useAltName)); + } + } + } + } + finally + { + CloseConnection(); + } + + return entityList; + } + + #endregion + + #region - Query to Graph - + + public List QueryToGraph(string sql) + { + return (List)QueryToGraph(sql, new List()); + } + + public ICollection QueryToGraph(string sql, ICollection entityList) + { + EntityGraph graph = new EntityGraph(typeof(T), (IList)entityList); + return QueryToGraph(sql, graph, new List()); + } + + /// + /// Queries a view that joins multiple tables and returns an object graph. + /// + /// + /// + /// + /// Coordinates loading all objects in the graph.. + /// + internal ICollection QueryToGraph(string sql, EntityGraph graph, List 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 is child relationship entity, and childrenToLoad are specified, and entity is not listed, + // then skip this entity. + if (childrenToLoad.Count > 0 && !lvl.IsRoot && !childrenToLoad.ContainsMember(lvl.Member)) // lvl.Member.Name + { + continue; + } + + if (lvl.IsNewGroup(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)graph.RootList; + } + + #endregion + + #region - Update - + + public UpdateQueryBuilder Update() + { + return new UpdateQueryBuilder(this); + } + + public int Update(T entity, Expression> filter) + { + return Update() + .Entity(entity) + .Where(filter) + .Execute(); + } + + public int Update(string tableName, T entity, Expression> filter) + { + return Update() + .TableName(tableName) + .Entity(entity) + .Where(filter) + .Execute(); + } + + public int Update(T entity, string sql) + { + return Update() + .Entity(entity) + .QueryText(sql) + .Execute(); + } + + #endregion + + #region - Insert - + + /// + /// 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. + /// + public InsertQueryBuilder Insert() + { + return new InsertQueryBuilder(this); + } + + /// + /// 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. + /// + public object Insert(T entity) + { + var columns = MapRepository.Instance.GetColumns(typeof(T)); + var dialect = QueryFactory.CreateDialect(this); + var builder = Insert().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(); + } + + /// + /// 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. + /// + public object Insert(string tableName, T entity) + { + var columns = MapRepository.Instance.GetColumns(typeof(T)); + var dialect = QueryFactory.CreateDialect(this); + var builder = Insert().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(); + } + + /// + /// 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. + /// + public object Insert(T entity, string sql) + { + var columns = MapRepository.Instance.GetColumns(typeof(T)); + var dialect = QueryFactory.CreateDialect(this); + var builder = Insert().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(Expression> filter) + { + return Delete(null, filter); + } + + public int Delete(string tableName, Expression> filter) + { + // Remember sql mode + var previousSqlMode = this.SqlMode; + SqlMode = SqlModes.Text; + + var mappingHelper = new MappingHelper(this); + if (tableName == null) + { + tableName = MapRepository.Instance.GetTableName(typeof(T)); + } + var dialect = QGen.QueryFactory.CreateDialect(this); + TableCollection tables = new TableCollection(); + tables.Add(new Table(typeof(T))); + var where = new WhereBuilder(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) + { + StringBuilder 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() + { + if (OpeningConnection != null) + OpeningConnection = null; + + if (ClosingConnection != null) + ClosingConnection = null; + } + + public void BeginTransaction() + { + OpenConnection(); + DbTransaction trans = Command.Connection.BeginTransaction(); + Command.Transaction = trans; + } + + public void RollBack() + { + try + { + if (Command.Transaction != null) + Command.Transaction.Rollback(); + } + finally + { + Command.Connection.Close(); + } + } + + public void Commit() + { + try + { + if (Command.Transaction != null) + Command.Transaction.Commit(); + } + finally + { + Command.Connection.Close(); + } + } + + #endregion + + #region - IDisposable Members - + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); // In case a derived class implements a finalizer + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (Command.Transaction != null) + { + Command.Transaction.Dispose(); + Command.Transaction = null; + } + + if (Command.Connection != null) + { + Command.Connection.Dispose(); + Command.Connection = null; + } + + if (Command != null) + { + Command.Dispose(); + _command = null; + } + } + } + + #endregion + + } +} diff --git a/Marr.Data/DataMappingException.cs b/Marr.Data/DataMappingException.cs new file mode 100644 index 000000000..2ab5619be --- /dev/null +++ b/Marr.Data/DataMappingException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +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) + { + } + } +} diff --git a/Marr.Data/EntityGraph.cs b/Marr.Data/EntityGraph.cs new file mode 100644 index 000000000..b28fbc69c --- /dev/null +++ b/Marr.Data/EntityGraph.cs @@ -0,0 +1,376 @@ +/* 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 . */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Data.Common; +using System.Linq; +using Marr.Data.Mapping; +using System.Reflection; + +namespace Marr.Data +{ + /// + /// 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. + /// + internal class EntityGraph : IEnumerable + { + private MapRepository _repos; + private EntityGraph _parent; + private Type _entityType; + private Relationship _relationship; + private ColumnMapCollection _columns; + private RelationshipCollection _relationships; + private List _children; + private object _entity; + private GroupingKeyCollection _groupingKeyColumns; + private Dictionary _entityReferences; + + public IList RootList { get; private set; } + + /// + /// Recursively builds an entity graph of the given parent type. + /// + /// + public EntityGraph(Type entityType, IList rootList) + : this(entityType, null, null) // Recursively constructs hierarchy + { + RootList = rootList; + } + + /// + /// Recursively builds entity graph hierarchy. + /// + /// + /// + /// + private EntityGraph(Type entityType, EntityGraph parent, Relationship relationship) + { + _repos = MapRepository.Instance; + + _entityType = entityType; + _parent = parent; + _relationship = relationship; + _columns = _repos.GetColumns(entityType); + _relationships = _repos.GetRelationships(entityType); + _children = new List(); + Member = relationship != null ? relationship.Member : null; + _entityReferences = new Dictionary(); + + // Create a new EntityGraph for each child relationship that is not lazy loaded + foreach (Relationship childRelationship in this.Relationships) + { + if (!childRelationship.IsLazyLoaded) + { + _children.Add(new EntityGraph(childRelationship.RelationshipInfo.EntityType, this, childRelationship)); + } + } + } + + public MemberInfo Member { get; private set; } + + /// + /// Gets the parent of this EntityGraph. + /// + public EntityGraph Parent + { + get + { + return _parent; + } + } + + /// + /// Gets the Type of this EntityGraph. + /// + public Type EntityType + { + get { return _entityType; } + } + + /// + /// Gets a boolean than indicates whether this entity is the root node in the graph. + /// + public bool IsRoot + { + get + { + return _parent == null; + } + } + + /// + /// Gets a boolean that indicates whether this entity is a child. + /// + public bool IsChild + { + get + { + return _parent != null; + } + } + + /// + /// Gets the columns mapped to this entity. + /// + public ColumnMapCollection Columns + { + get { return _columns; } + } + + /// + /// Gets the relationships mapped to this entity. + /// + public RelationshipCollection Relationships + { + get { return _relationships; } + } + + /// + /// A list of EntityGraph objects that hold metadata about the child entities that will be loaded. + /// + public List Children + { + get { return _children; } + } + + /// + /// Adds an entity to the appropriate place in the object graph. + /// + /// + public void AddEntity(object entityInstance) + { + _entity = entityInstance; + + // Add newly created entityInstance to list (Many) or set it to field (One) + if (this.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 + { + _repos.ReflectionStrategy.SetFieldValue(_parent._entity, _relationship.Member.Name, entityInstance); + } + + EntityReference entityRef = new EntityReference(entityInstance); + _entityReferences.Add(GroupingKeyColumns.GroupingKey, entityRef); + + InitOneToManyChildLists(entityRef); + } + + /// + /// Initializes the owning lists on many-to-many Children. + /// + /// + 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); + _repos.ReflectionStrategy.SetFieldValue(entityRef.Entity, relationship.Member.Name, 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); + } + } + } + } + + /// + /// Recursively adds primary key columns from contiguous child graphs with a one-to-one relationship type to the pKeys collection.. + /// + /// + /// + private void AddOneToOneChildKeys(ColumnMapCollection pKeys, EntityGraph entity) + { + var oneToOneChildren = entity.Children + .Where(c => c._relationship.RelationshipInfo.RelationType == RelationshipTypes.One); + + foreach (var child in oneToOneChildren) + { + pKeys.AddRange(child.Columns.PrimaryKeys); + AddOneToOneChildKeys(pKeys, child); + } + } + + /// + /// 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. + /// + /// + /// + public bool IsNewGroup(DbDataReader reader) + { + bool isNewGroup = false; + + // Get primary keys from parent entity and any one-to-one child entites + GroupingKeyCollection groupingKeyColumns = this.GroupingKeyColumns; + + // Concatenate column values + KeyGroupInfo keyGroupInfo = groupingKeyColumns.CreateGroupingKey(reader); + + if (!keyGroupInfo.HasNullKey && !_entityReferences.ContainsKey(keyGroupInfo.GroupingKey)) + { + isNewGroup = true; + } + + return isNewGroup; + } + + /// + /// Gets the GroupingKeys for this entity. + /// GroupingKeys determine when to create and add a new entity to the graph. + /// + /// + /// 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. + /// + public GroupingKeyCollection GroupingKeyColumns + { + get + { + if (_groupingKeyColumns == null) + _groupingKeyColumns = GetGroupingKeyColumns(); + + return _groupingKeyColumns; + } + } + + /// + /// Gets a list of keys to group by. + /// + /// + /// 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. + /// + /// + 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}'.", this.EntityType.Name)); + + // Add parent's keys + if (IsChild) + groupingKeyColumns.ParentPrimaryKeys.AddRange(Parent.GroupingKeyColumns); + + return groupingKeyColumns; + } + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return TraverseGraph(this); + } + + /// + /// Recursively traverses through every entity in the EntityGraph. + /// + /// + /// + private static IEnumerator TraverseGraph(EntityGraph entityGraph) + { + Stack stack = new Stack(); + 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 this.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; } + } +} \ No newline at end of file diff --git a/Marr.Data/EntityMerger.cs b/Marr.Data/EntityMerger.cs new file mode 100644 index 000000000..717b2e46c --- /dev/null +++ b/Marr.Data/EntityMerger.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; + +namespace Marr.Data +{ + /// + /// This utility class allows you to join two existing entity collections. + /// + public class EntityMerger + { + /// + /// Joines to existing entity collections. + /// + /// The parent entity type. + /// The child entity type. + /// The parent entities. + /// The child entities + /// A predicate that defines the relationship between the parent and child entities. Returns true if they are related. + /// An action that adds a related child to the parent. + public static void Merge(IEnumerable parentList, IEnumerable childList, Func relationship, Action mergeAction) + { + foreach (TParent parent in parentList) + { + foreach (TChild child in childList) + { + if (relationship(parent, child)) + { + mergeAction(parent, child); + } + } + } + } + } +} diff --git a/Marr.Data/EntityReference.cs b/Marr.Data/EntityReference.cs new file mode 100644 index 000000000..84a68b98b --- /dev/null +++ b/Marr.Data/EntityReference.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; + +namespace Marr.Data +{ + /// + /// Stores an entity along with all of its 1-M IList references. + /// + public class EntityReference + { + public EntityReference(object entity) + { + Entity = entity; + ChildLists = new Dictionary(); + } + + public object Entity { get; private set; } + public Dictionary ChildLists { get; private set; } + + public void AddChildList(string memberName, IList list) + { + if (ChildLists.ContainsKey(memberName)) + ChildLists[memberName] = list; + else + ChildLists.Add(memberName, list); + } + } +} diff --git a/Marr.Data/ExtensionMethods.cs b/Marr.Data/ExtensionMethods.cs new file mode 100644 index 000000000..b33093c33 --- /dev/null +++ b/Marr.Data/ExtensionMethods.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.Common; + +namespace Marr.Data +{ + /// + /// This class contains public extension methods. + /// + public static class ExtensionMethods + { + /// + /// Gets a value from a DbDataReader by using the column name; + /// + public static T GetValue(this DbDataReader reader, string columnName) + { + int ordinal = reader.GetOrdinal(columnName); + return (T)reader.GetValue(ordinal); + } + } +} diff --git a/Marr.Data/GroupingKeyCollection.cs b/Marr.Data/GroupingKeyCollection.cs new file mode 100644 index 000000000..8f81a7482 --- /dev/null +++ b/Marr.Data/GroupingKeyCollection.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; +using System.Data.Common; + +namespace Marr.Data +{ + public class GroupingKeyCollection : IEnumerable + { + 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; + } + } + + /// + /// Gets the PK values that define this entity in the graph. + /// + internal string GroupingKey { get; private set; } + + /// + /// Returns a concatented string containing the primary key values of the current record. + /// + /// The open data reader. + /// Returns the primary key value(s) as a string. + 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 Members + + public IEnumerator GetEnumerator() + { + foreach (ColumnMap map in ParentPrimaryKeys) + { + yield return map; + } + + foreach (ColumnMap map in PrimaryKeys) + { + yield return map; + } + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + #endregion + } +} diff --git a/Marr.Data/IDataMapper.cs b/Marr.Data/IDataMapper.cs new file mode 100644 index 000000000..4f0f9014a --- /dev/null +++ b/Marr.Data/IDataMapper.cs @@ -0,0 +1,220 @@ +/* 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 . */ + +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; +using System.Reflection; + +namespace Marr.Data +{ + public interface IDataMapper : IDisposable + { + #region - Contructor, Members - + + string ConnectionString { get; } + DbProviderFactory ProviderFactory { get; } + DbCommand Command { get; } + + /// + /// 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. + /// + SqlModes SqlMode { get; set; } + + #endregion + + #region - Update - + + UpdateQueryBuilder Update(); + int Update(T entity, Expression> filter); + int Update(string tableName, T entity, Expression> filter); + int Update(T entity, string sql); + + #endregion + + #region - Insert - + + /// + /// 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. + /// + InsertQueryBuilder Insert(); + + /// + /// 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. + /// + object Insert(T entity); + + /// + /// 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. + /// + object Insert(string tableName, T entity); + + /// + /// 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. + /// + object Insert(T entity, string sql); + + #endregion + + #region - Delete - + + int Delete(Expression> filter); + int Delete(string tableName, Expression> filter); + + #endregion + + #region - Connections / Transactions - + + void BeginTransaction(); + void RollBack(); + void Commit(); + event EventHandler OpeningConnection; + + #endregion + + #region - ExecuteScalar, ExecuteNonQuery, ExecuteReader - + + /// + /// Executes a non query that returns an integer. + /// + /// The SQL command to execute. + /// An integer value + int ExecuteNonQuery(string sql); + + /// + /// Executes a stored procedure that returns a scalar value. + /// + /// The SQL command to execute. + /// A scalar value + object ExecuteScalar(string sql); + + /// + /// Executes a DataReader that can be controlled using a Func delegate. + /// (Note that reader.Read() will be called automatically). + /// + /// The type that will be return in the result set. + /// The sql statement that will be executed. + /// The function that will build the the TResult set. + /// An IEnumerable of TResult. + IEnumerable ExecuteReader(string sql, Func func); + + /// + /// Executes a DataReader that can be controlled using an Action delegate. + /// + /// The sql statement that will be executed. + /// The delegate that will work with the result set. + void ExecuteReader(string sql, Action 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 - + + /// + /// Returns an entity of type T. + /// + /// The type of entity that is to be instantiated and loaded with values. + /// The SQL command to execute. + /// An instantiated and loaded entity of type T. + T Find(string sql); + + /// + /// Returns an entity of type T. + /// + /// The type of entity that is to be instantiated and loaded with values. + /// The SQL command to execute. + /// A previously instantiated entity that will be loaded with values. + /// An instantiated and loaded entity of type T. + T Find(string sql, T ent); + + #endregion + + #region - Query - + + /// + /// Creates a QueryBuilder that allows you to build a query. + /// + /// The type of object that will be queried. + /// Returns a QueryBuilder of T. + QueryBuilder Query(); + + /// + /// Returns the results of a query. + /// Uses a List of type T to return the data. + /// + /// The type of object that will be queried. + /// Returns a list of the specified type. + List Query(string sql); + + /// + /// Returns the results of a query or a stored procedure. + /// + /// The type of object that will be queried. + /// The sql query or stored procedure name to run. + /// A previously instantiated list to populate. + /// Returns a list of the specified type. + ICollection Query(string sql, ICollection entityList); + + #endregion + + #region - Query to Graph - + + /// + /// Runs a query and then tries to instantiate the entire object graph with entites. + /// + List QueryToGraph(string sql); + + /// + /// Runs a query and then tries to instantiate the entire object graph with entites. + /// + ICollection QueryToGraph(string sql, ICollection entityList); + + #endregion + } +} diff --git a/Marr.Data/LazyLoaded.cs b/Marr.Data/LazyLoaded.cs new file mode 100644 index 000000000..d492b18ba --- /dev/null +++ b/Marr.Data/LazyLoaded.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.Common; + +namespace Marr.Data +{ + public interface ILazyLoaded : ICloneable + { + void Prepare(Func dbCreator, object parent); + void LazyLoad(); + } + + /// + /// Allows a field to be lazy loaded. + /// + /// + public class LazyLoaded : ILazyLoaded + { + protected TChild _child; + protected bool _isLoaded; + + public LazyLoaded() + { + } + + public LazyLoaded(TChild val) + { + _child = val; + _isLoaded = true; + } + + public TChild Value + { + get + { + LazyLoad(); + return _child; + } + } + + public virtual void Prepare(Func dbCreator, object parent) + { } + + public virtual void LazyLoad() + { } + + public static implicit operator LazyLoaded(TChild val) + { + return new LazyLoaded(val); + } + + public static implicit operator TChild(LazyLoaded lazy) + { + return lazy.Value; + } + + public object Clone() + { + return this.MemberwiseClone(); + } + } + + /// + /// This is the lazy loading proxy. + /// + /// The parent entity that contains the lazy loaded entity. + /// The child entity that is being lazy loaded. + internal class LazyLoaded : LazyLoaded + { + private TParent _parent; + private Func _query; + + private Func _dbCreator; + + internal LazyLoaded(Func query) + : base() + { + _query = query; + } + + public LazyLoaded(TChild val) + : base(val) + { + _child = val; + _isLoaded = true; + } + + /// + /// The second part of the initialization happens when the entity is being built. + /// + /// Knows how to instantiate a new IDataMapper. + /// The parent entity. + public override void Prepare(Func dbCreator, object parent) + { + _dbCreator = dbCreator; + _parent = (TParent)parent; + } + + public bool IsLoaded + { + get { return _isLoaded; } + } + + public override void LazyLoad() + { + if (!_isLoaded) + { + using (IDataMapper db = _dbCreator()) + { + _child = _query(db, _parent); + _isLoaded = true; + } + } + } + + public static implicit operator LazyLoaded(TChild val) + { + return new LazyLoaded(val); + } + + public static implicit operator TChild(LazyLoaded lazy) + { + return lazy.Value; + } + } + +} \ No newline at end of file diff --git a/Marr.Data/MapRepository.cs b/Marr.Data/MapRepository.cs new file mode 100644 index 000000000..25489d15f --- /dev/null +++ b/Marr.Data/MapRepository.cs @@ -0,0 +1,265 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Data.Common; +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 _columnMapStrategies; + + internal Dictionary Tables { get; set; } + internal Dictionary Columns { get; set; } + internal Dictionary Relationships { get; set; } + internal Dictionary TypeConverters { get; set; } + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static MapRepository() + { } + + private MapRepository() + { + Tables = new Dictionary(); + Columns = new Dictionary(); + Relationships = new Dictionary(); + TypeConverters = new Dictionary(); + + // Register a default IReflectionStrategy + ReflectionStrategy = new CachedReflectionStrategy(); + + // Register a default type converter for Enums + TypeConverters.Add(typeof(Enum), new Converters.EnumStringConverter()); + + // Register a default IDbTypeBuilder + _dbTypeBuilder = new Parameters.DbTypeBuilder(); + + _columnMapStrategies = new Dictionary(); + RegisterDefaultMapStrategy(new AttributeMapStrategy()); + + EnableTraceLogging = false; + } + + private readonly static MapRepository _instance = new MapRepository(); + + /// + /// Gets a reference to the singleton MapRepository. + /// + public static MapRepository Instance + { + get + { + return _instance; + } + } + + /// + /// Gets or sets a boolean that determines whether debug information should be written to the trace log. + /// The default is false. + /// + 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]; + } + else + { + // 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 - + + /// + /// 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. + /// + public IReflectionStrategy ReflectionStrategy { get; set; } + + #endregion + + #region - Type Converters - + + /// + /// Registers a converter for a given type. + /// + /// The CLR data type that will be converted. + /// An IConverter object that will handle the data conversion. + public void RegisterTypeConverter(Type type, IConverter converter) + { + if (TypeConverters.ContainsKey(type)) + { + TypeConverters[type] = converter; + } + else + { + TypeConverters.Add(type, converter); + } + } + + /// + /// 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). + /// + /// The current data map. + /// Returns an IConverter object or null if one does not exist. + internal IConverter GetConverter(Type dataType) + { + if (TypeConverters.ContainsKey(dataType)) + { + // User registered type converter + return TypeConverters[dataType]; + } + else if (TypeConverters.ContainsKey(typeof(Enum)) && dataType.IsEnum) + { + // A converter is registered to handled enums + return TypeConverters[typeof(Enum)]; + } + else if (TypeConverters.ContainsKey(typeof(object))) + { + // User registered default converter + return TypeConverters[typeof(object)]; + } + else + { + // No conversion + return null; + } + } + + #endregion + + #region - DbTypeBuilder - + + /// + /// 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. + /// + public IDbTypeBuilder DbTypeBuilder + { + get { return _dbTypeBuilder; } + set { _dbTypeBuilder = value; } + } + + #endregion + } +} \ No newline at end of file diff --git a/Marr.Data/Mapping/ColumnAttribute.cs b/Marr.Data/Mapping/ColumnAttribute.cs new file mode 100644 index 000000000..9d63e5b7c --- /dev/null +++ b/Marr.Data/Mapping/ColumnAttribute.cs @@ -0,0 +1,122 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Data.OleDb; +using System.Data.Common; + +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; + } + + /// + /// Gets or sets the column name. + /// + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// 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. + /// + public string AltName + { + get { return _altName; } + set { _altName = value; } + } + + /// + /// Gets or sets the column size. + /// + public int Size + { + get { return _size; } + set { _size = value; } + } + + /// + /// Gets or sets a value that determines whether the column is the Primary Key. + /// + public bool IsPrimaryKey + { + get { return _isPrimaryKey; } + set { _isPrimaryKey = value; } + } + + /// + /// Gets or sets a value that determines whether the column is an auto-incrementing seed column. + /// + public bool IsAutoIncrement + { + get { return _isAutoIncrement; } + set { _isAutoIncrement = value; } + } + + /// + /// Gets or sets a value that determines whether the column has a return value. + /// + public bool ReturnValue + { + get { return _returnValue; } + set { _returnValue = value; } + } + + /// + /// Gets or sets the ParameterDirection. + /// + public ParameterDirection ParamDirection + { + get { return _paramDirection; } + set { _paramDirection = value; } + } + + public string TryGetAltName() + { + if (!string.IsNullOrEmpty(AltName) && AltName != Name) + { + return AltName; + } + else + { + return Name; + } + } + } +} diff --git a/Marr.Data/Mapping/ColumnInfo.cs b/Marr.Data/Mapping/ColumnInfo.cs new file mode 100644 index 000000000..de1a66da4 --- /dev/null +++ b/Marr.Data/Mapping/ColumnInfo.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Mapping +{ + public class ColumnInfo : IColumnInfo + { + public ColumnInfo() + { + IsPrimaryKey = false; + IsAutoIncrement = false; + ReturnValue = false; + ParamDirection = System.Data.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 System.Data.ParameterDirection ParamDirection { get; set; } + + public string TryGetAltName() + { + if (!string.IsNullOrEmpty(AltName) && AltName != Name) + { + return AltName; + } + else + { + return Name; + } + } + } +} diff --git a/Marr.Data/Mapping/ColumnMap.cs b/Marr.Data/Mapping/ColumnMap.cs new file mode 100644 index 000000000..3ae479b4c --- /dev/null +++ b/Marr.Data/Mapping/ColumnMap.cs @@ -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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Reflection; +using Marr.Data.Converters; + +namespace Marr.Data.Mapping +{ + /// + /// Contains information about the class fields and their associated stored proc parameters + /// + public class ColumnMap + { + /// + /// Creates a column map with an empty ColumnInfo object. + /// + /// The .net member that is being mapped. + public ColumnMap(MemberInfo member) + : this(member, new ColumnInfo()) + { } + + public ColumnMap(MemberInfo member, IColumnInfo columnInfo) + { + FieldName = member.Name; + + // 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; + MapRepository repository = MapRepository.Instance; + + IConverter converter = repository.GetConverter(FieldType); + if (converter != null) + { + // Handle conversions + paramNetType = converter.DbType; + } + + // Get database specific DbType and store with column map in cache + DBType = repository.DbTypeBuilder.GetDbType(paramNetType); + + ColumnInfo = columnInfo; + } + + public string FieldName { get; set; } + public Type FieldType { get; set; } + public Enum DBType { get; set; } + public IColumnInfo ColumnInfo { get; set; } + } +} diff --git a/Marr.Data/Mapping/ColumnMapBuilder.cs b/Marr.Data/Mapping/ColumnMapBuilder.cs new file mode 100644 index 000000000..ad8b470ee --- /dev/null +++ b/Marr.Data/Mapping/ColumnMapBuilder.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; +using System.Data; +using Marr.Data.Mapping.Strategies; + +namespace Marr.Data.Mapping +{ + /// + /// This class has fluent methods that are used to easily configure column mappings. + /// + /// + public class ColumnMapBuilder + { + private FluentMappings.MappingsFluentEntity _fluentEntity; + private string _currentPropertyName; + + public ColumnMapBuilder(FluentMappings.MappingsFluentEntity fluentEntity, ColumnMapCollection mappedColumns) + { + _fluentEntity = fluentEntity; + MappedColumns = mappedColumns; + } + + /// + /// Gets the list of column mappings that are being configured. + /// + public ColumnMapCollection MappedColumns { get; private set; } + + #region - Fluent Methods - + + /// + /// Initializes the configurator to configure the given property. + /// + /// + /// + public ColumnMapBuilder For(Expression> property) + { + For(property.GetMemberName()); + return this; + } + + /// + /// Initializes the configurator to configure the given property or field. + /// + /// + /// + public ColumnMapBuilder 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 SetPrimaryKey() + { + AssertCurrentPropertyIsSet(); + return SetPrimaryKey(_currentPropertyName); + } + + public ColumnMapBuilder SetPrimaryKey(string propertyName) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsPrimaryKey = true; + return this; + } + + public ColumnMapBuilder SetAutoIncrement() + { + AssertCurrentPropertyIsSet(); + return SetAutoIncrement(_currentPropertyName); + } + + public ColumnMapBuilder SetAutoIncrement(string propertyName) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsAutoIncrement = true; + return this; + } + + public ColumnMapBuilder SetColumnName(string columnName) + { + AssertCurrentPropertyIsSet(); + return SetColumnName(_currentPropertyName, columnName); + } + + public ColumnMapBuilder SetColumnName(string propertyName, string columnName) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.Name = columnName; + return this; + } + + public ColumnMapBuilder SetReturnValue() + { + AssertCurrentPropertyIsSet(); + return SetReturnValue(_currentPropertyName); + } + + public ColumnMapBuilder SetReturnValue(string propertyName) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.ReturnValue = true; + return this; + } + + public ColumnMapBuilder SetSize(int size) + { + AssertCurrentPropertyIsSet(); + return SetSize(_currentPropertyName, size); + } + + public ColumnMapBuilder SetSize(string propertyName, int size) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.Size = size; + return this; + } + + public ColumnMapBuilder SetAltName(string altName) + { + AssertCurrentPropertyIsSet(); + return SetAltName(_currentPropertyName, altName); + } + + public ColumnMapBuilder SetAltName(string propertyName, string altName) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.AltName = altName; + return this; + } + + public ColumnMapBuilder SetParamDirection(ParameterDirection direction) + { + AssertCurrentPropertyIsSet(); + return SetParamDirection(_currentPropertyName, direction); + } + + public ColumnMapBuilder SetParamDirection(string propertyName, ParameterDirection direction) + { + MappedColumns.GetByFieldName(propertyName).ColumnInfo.ParamDirection = direction; + return this; + } + + public ColumnMapBuilder Ignore(Expression> property) + { + string propertyName = property.GetMemberName(); + return Ignore(propertyName); + } + + public ColumnMapBuilder Ignore(string propertyName) + { + var columnMap = MappedColumns.GetByFieldName(propertyName); + MappedColumns.Remove(columnMap); + return this; + } + + public ColumnMapBuilder PrefixAltNames(string prefix) + { + MappedColumns.PrefixAltNames(prefix); + return this; + } + + public ColumnMapBuilder SuffixAltNames(string suffix) + { + MappedColumns.SuffixAltNames(suffix); + return this; + } + + public FluentMappings.MappingsFluentTables Tables + { + get + { + if (_fluentEntity == null) + { + throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class."); + } + + return _fluentEntity.Table; + } + } + + public FluentMappings.MappingsFluentRelationships Relationships + { + get + { + if (_fluentEntity == null) + { + throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class."); + } + + return _fluentEntity.Relationships; + } + } + + public FluentMappings.MappingsFluentEntity Entity() + { + return new FluentMappings.MappingsFluentEntity(true); + } + + /// + /// Tries to add a ColumnMap for the given field name. + /// Throws and exception if field cannot be found. + /// + 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)); + } + else + { + MappedColumns.Add(columnMap); + } + } + + /// + /// Throws an exception if the "current" property has not been set. + /// + private void AssertCurrentPropertyIsSet() + { + if (string.IsNullOrEmpty(_currentPropertyName)) + { + throw new DataMappingException("A property must first be specified using the 'For' method."); + } + } + + #endregion + } +} diff --git a/Marr.Data/Mapping/ColumnMapCollection.cs b/Marr.Data/Mapping/ColumnMapCollection.cs new file mode 100644 index 000000000..0a035fbae --- /dev/null +++ b/Marr.Data/Mapping/ColumnMapCollection.cs @@ -0,0 +1,176 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Text.RegularExpressions; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; + +namespace Marr.Data.Mapping +{ + /// + /// This class contains a list of column mappings. + /// It also provides various methods to filter the collection. + /// + public class ColumnMapCollection : List + { + #region - Filters - + + public ColumnMap GetByColumnName(string columnName) + { + return this.Find(m => m.ColumnInfo.Name == columnName); + } + + public ColumnMap GetByFieldName(string fieldName) + { + return this.Find(m => m.FieldName == fieldName); + } + + /// + /// Iterates through all fields marked as return values. + /// + public IEnumerable ReturnValues + { + get + { + foreach (ColumnMap map in this) + if (map.ColumnInfo.ReturnValue) + yield return map; + } + } + + /// + /// Iterates through all fields that are not return values. + /// + public ColumnMapCollection NonReturnValues + { + get + { + ColumnMapCollection collection = new ColumnMapCollection(); + + foreach (ColumnMap map in this) + if (!map.ColumnInfo.ReturnValue) + collection.Add(map); + + return collection; + } + } + + /// + /// Iterates through all fields marked as Output parameters or InputOutput. + /// + public IEnumerable OutputFields + { + get + { + foreach (ColumnMap map in this) + if (map.ColumnInfo.ParamDirection == ParameterDirection.InputOutput || + map.ColumnInfo.ParamDirection == ParameterDirection.Output) + yield return map; + } + } + + /// + /// Iterates through all fields marked as primary keys. + /// + public ColumnMapCollection PrimaryKeys + { + get + { + ColumnMapCollection keys = new ColumnMapCollection(); + foreach (ColumnMap map in this) + if (map.ColumnInfo.IsPrimaryKey) + keys.Add(map); + + return keys; + } + } + + /// + /// 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. + /// + /// The command and parameters that are being parsed. + /// A list of mapped columns that are present in the sql statement as parameters. + public ColumnMapCollection OrderParameters(DbCommand command) + { + if (command.CommandType == CommandType.Text && this.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 = this.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 - + + /// + /// 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" + /// + /// The given prefix. + /// + public ColumnMapCollection PrefixAltNames(string prefix) + { + this.ForEach(c => c.ColumnInfo.AltName = c.ColumnInfo.Name.Insert(0, prefix)); + return this; + } + + /// + /// 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" + /// + /// + /// + public ColumnMapCollection SuffixAltNames(string suffix) + { + this.ForEach(c => c.ColumnInfo.AltName = c.ColumnInfo.Name + suffix); + return this; + } + + #endregion + } +} diff --git a/Marr.Data/Mapping/EnumConversionType.cs b/Marr.Data/Mapping/EnumConversionType.cs new file mode 100644 index 000000000..b865e84e8 --- /dev/null +++ b/Marr.Data/Mapping/EnumConversionType.cs @@ -0,0 +1,29 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Mapping +{ + public enum EnumConversionType + { + NA, + Int, + String + } +} diff --git a/Marr.Data/Mapping/FluentMappings.cs b/Marr.Data/Mapping/FluentMappings.cs new file mode 100644 index 000000000..83002440a --- /dev/null +++ b/Marr.Data/Mapping/FluentMappings.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Marr.Data.Mapping.Strategies; +using System.Collections; + +namespace Marr.Data.Mapping +{ + /// + /// Provides a fluent interface for mapping domain entities and properties to database tables and columns. + /// + public class FluentMappings + { + private bool _publicOnly; + + public FluentMappings() + : this(true) + { } + + public FluentMappings(bool publicOnly) + { + _publicOnly = publicOnly; + + } + + public MappingsFluentEntity Entity() + { + return new MappingsFluentEntity(_publicOnly); + } + + public class MappingsFluentEntity + { + public MappingsFluentEntity(bool publicOnly) + { + Columns = new MappingsFluentColumns(this, publicOnly); + Table = new MappingsFluentTables(this); + Relationships = new MappingsFluentRelationships(this, publicOnly); + } + + /// + /// Contains methods that map entity properties to database table and view column names; + /// + public MappingsFluentColumns Columns { get; private set; } + + /// + /// Contains methods that map entity classes to database table names. + /// + public MappingsFluentTables Table { get; private set; } + + /// + /// Contains methods that map sub-entities with database table and view column names. + /// + public MappingsFluentRelationships Relationships { get; private set; } + } + + public class MappingsFluentColumns + { + private bool _publicOnly; + private FluentMappings.MappingsFluentEntity _fluentEntity; + + public MappingsFluentColumns(FluentMappings.MappingsFluentEntity fluentEntity, bool publicOnly) + { + _fluentEntity = fluentEntity; + _publicOnly = publicOnly; + } + + /// + /// Creates column mappings for the given type. + /// Maps all properties except ICollection properties. + /// + /// The type that is being built. + /// + public ColumnMapBuilder AutoMapAllProperties() + { + return AutoMapPropertiesWhere(m => m.MemberType == MemberTypes.Property && + !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates column mappings for the given type. + /// Maps all properties that are simple types (int, string, DateTime, etc). + /// ICollection properties are not included. + /// + /// The type that is being built. + /// + public ColumnMapBuilder AutoMapSimpleTypeProperties() + { + return AutoMapPropertiesWhere(m => m.MemberType == MemberTypes.Property && + DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) && + !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates column mappings for the given type if they match the predicate. + /// + /// The type that is being built. + /// Determines whether a mapping should be created based on the member info. + /// + public ColumnMapBuilder AutoMapPropertiesWhere(Func 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(_fluentEntity, columns); + } + + /// + /// Creates a ColumnMapBuilder that starts out with no pre-populated columns. + /// All columns must be added manually using the builder. + /// + /// + /// + public ColumnMapBuilder MapProperties() + { + Type entityType = typeof(TEntity); + ColumnMapCollection columns = new ColumnMapCollection(); + MapRepository.Instance.Columns[entityType] = columns; + return new ColumnMapBuilder(_fluentEntity, columns); + } + } + + public class MappingsFluentTables + { + private FluentMappings.MappingsFluentEntity _fluentEntity; + + public MappingsFluentTables(FluentMappings.MappingsFluentEntity fluentEntity) + { + _fluentEntity = fluentEntity; + } + + /// + /// Provides a fluent table mapping interface. + /// + /// + /// + public TableBuilder AutoMapTable() + { + return new TableBuilder(_fluentEntity); + } + + /// + /// Sets the table name for a given type. + /// + /// + /// + public TableBuilder MapTable(string tableName) + { + return new TableBuilder(_fluentEntity).SetTableName(tableName); + } + } + + public class MappingsFluentRelationships + { + private FluentMappings.MappingsFluentEntity _fluentEntity; + private bool _publicOnly; + + public MappingsFluentRelationships(FluentMappings.MappingsFluentEntity fluentEntity, bool publicOnly) + { + _fluentEntity = fluentEntity; + _publicOnly = publicOnly; + } + + /// + /// Creates relationship mappings for the given type. + /// Maps all properties that implement ICollection or are not "simple types". + /// + /// + public RelationshipBuilder AutoMapICollectionOrComplexProperties() + { + return AutoMapPropertiesWhere(m => + m.MemberType == MemberTypes.Property && + ( + typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType) || !DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) + ) + ); + + } + + /// + /// Creates relationship mappings for the given type. + /// Maps all properties that implement ICollection. + /// + /// + public RelationshipBuilder AutoMapICollectionProperties() + { + return AutoMapPropertiesWhere(m => + m.MemberType == MemberTypes.Property && + typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates relationship mappings for the given type. + /// Maps all properties that are not "simple types". + /// + /// + public RelationshipBuilder AutoMapComplexTypeProperties() + { + return AutoMapPropertiesWhere(m => + m.MemberType == MemberTypes.Property && + !DataHelper.IsSimpleType((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates relationship mappings for the given type if they match the predicate. + /// + /// Determines whether a mapping should be created based on the member info. + /// + public RelationshipBuilder AutoMapPropertiesWhere(Func 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(_fluentEntity, relationships); + } + + /// + /// Creates a RelationshipBuilder that starts out with no pre-populated relationships. + /// All relationships must be added manually using the builder. + /// + /// + public RelationshipBuilder MapProperties() + { + Type entityType = typeof(T); + RelationshipCollection relationships = new RelationshipCollection(); + MapRepository.Instance.Relationships[entityType] = relationships; + return new RelationshipBuilder(_fluentEntity, relationships); + } + } + } +} diff --git a/Marr.Data/Mapping/IColumnInfo.cs b/Marr.Data/Mapping/IColumnInfo.cs new file mode 100644 index 000000000..4b54b1d5b --- /dev/null +++ b/Marr.Data/Mapping/IColumnInfo.cs @@ -0,0 +1,37 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Data.Common; +using System.Data.OleDb; + +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(); + } + +} diff --git a/Marr.Data/Mapping/IRelationshipInfo.cs b/Marr.Data/Mapping/IRelationshipInfo.cs new file mode 100644 index 000000000..fc28fe0a6 --- /dev/null +++ b/Marr.Data/Mapping/IRelationshipInfo.cs @@ -0,0 +1,34 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Marr.Data.Mapping +{ + public interface IRelationshipInfo + { + RelationshipTypes RelationType { get; set; } + Type EntityType { get; set; } + } + + public enum RelationshipTypes + { + AutoDetect, + One, + Many + } +} diff --git a/Marr.Data/Mapping/MapBuilder.cs b/Marr.Data/Mapping/MapBuilder.cs new file mode 100644 index 000000000..bb82cc9c3 --- /dev/null +++ b/Marr.Data/Mapping/MapBuilder.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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 - + + /// + /// Creates column mappings for the given type. + /// Maps all properties except ICollection properties. + /// + /// The type that is being built. + /// + public ColumnMapBuilder BuildColumns() + { + return BuildColumns(m => m.MemberType == MemberTypes.Property && + !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates column mappings for the given type. + /// Maps all properties that are simple types (int, string, DateTime, etc). + /// ICollection properties are not included. + /// + /// The type that is being built. + /// + public ColumnMapBuilder BuildColumnsFromSimpleTypes() + { + return BuildColumns(m => m.MemberType == MemberTypes.Property && + DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) && + !typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates column mappings for the given type. + /// Maps properties that are included in the include list. + /// + /// The type that is being built. + /// + /// + public ColumnMapBuilder BuildColumns(params string[] propertiesToInclude) + { + return BuildColumns(m => + m.MemberType == MemberTypes.Property && + propertiesToInclude.Contains(m.Name)); + } + + /// + /// Creates column mappings for the given type. + /// Maps all properties except the ones in the exclusion list. + /// + /// The type that is being built. + /// + /// + public ColumnMapBuilder BuildColumnsExcept(params string[] propertiesToExclude) + { + return BuildColumns(m => + m.MemberType == MemberTypes.Property && + !propertiesToExclude.Contains(m.Name)); + } + + /// + /// Creates column mappings for the given type if they match the predicate. + /// + /// The type that is being built. + /// Determines whether a mapping should be created based on the member info. + /// + public ColumnMapBuilder BuildColumns(Func 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(null, columns); + } + + /// + /// Creates a ColumnMapBuilder that starts out with no pre-populated columns. + /// All columns must be added manually using the builder. + /// + /// + /// + public ColumnMapBuilder Columns() + { + Type entityType = typeof(T); + ColumnMapCollection columns = new ColumnMapCollection(); + MapRepository.Instance.Columns[entityType] = columns; + return new ColumnMapBuilder(null, columns); + } + + #endregion + + #region - Relationships - + + /// + /// Creates relationship mappings for the given type. + /// Maps all properties that implement ICollection. + /// + /// The type that is being built. + /// + public RelationshipBuilder BuildRelationships() + { + return BuildRelationships(m => + m.MemberType == MemberTypes.Property && + typeof(ICollection).IsAssignableFrom((m as PropertyInfo).PropertyType)); + } + + /// + /// Creates relationship mappings for the given type. + /// Maps all properties that are listed in the include list. + /// + /// The type that is being built. + /// + /// + public RelationshipBuilder BuildRelationships(params string[] propertiesToInclude) + { + Func 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(predicate); + } + + /// + /// Creates relationship mappings for the given type if they match the predicate. + /// + /// The type that is being built. + /// Determines whether a mapping should be created based on the member info. + /// + public RelationshipBuilder BuildRelationships(Func 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(null, relationships); + } + + /// + /// Creates a RelationshipBuilder that starts out with no pre-populated relationships. + /// All relationships must be added manually using the builder. + /// + /// + /// + public RelationshipBuilder Relationships() + { + Type entityType = typeof(T); + RelationshipCollection relationships = new RelationshipCollection(); + MapRepository.Instance.Relationships[entityType] = relationships; + return new RelationshipBuilder(null, relationships); + } + + #endregion + + #region - Tables - + + /// + /// Provides a fluent table mapping interface. + /// + /// + /// + public TableBuilder BuildTable() + { + return new TableBuilder(null); + } + + /// + /// Sets the table name for a given type. + /// + /// + /// + public TableBuilder BuildTable(string tableName) + { + return new TableBuilder(null).SetTableName(tableName); + } + + #endregion + } +} diff --git a/Marr.Data/Mapping/MappingHelper.cs b/Marr.Data/Mapping/MappingHelper.cs new file mode 100644 index 000000000..c9b6def31 --- /dev/null +++ b/Marr.Data/Mapping/MappingHelper.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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; + } + + /// + /// Instantiates an entity and loads its mapped fields with the data from the reader. + /// + public object CreateAndLoadEntity(ColumnMapCollection mappings, DbDataReader reader, bool useAltName) + { + return CreateAndLoadEntity(typeof(T), mappings, reader, useAltName); + } + + /// + /// Instantiates an entity and loads its mapped fields with the data from the reader. + /// + /// The entity being created and loaded. + /// The field mappings for the passed in entity. + /// The open data reader. + /// Determines if the column AltName should be used. + /// Returns an entity loaded with data. + public object CreateAndLoadEntity(Type entityType, ColumnMapCollection mappings, DbDataReader reader, bool useAltName) + { + // Create new entity + object ent = _repos.ReflectionStrategy.CreateInstance(entityType); + return LoadExistingEntity(mappings, reader, ent, useAltName); + } + + public object LoadExistingEntity(ColumnMapCollection mappings, DbDataReader reader, object ent, bool useAltName) + { + // Populate entity fields from data reader + foreach (ColumnMap dataMap in mappings) + { + try + { + string colName = dataMap.ColumnInfo.GetColumName(useAltName); + int ordinal = reader.GetOrdinal(colName); + object dbValue = reader.GetValue(ordinal); + + // Handle conversions + IConverter conversion = _repos.GetConverter(dataMap.FieldType); + if (conversion != null) + { + dbValue = conversion.FromDB(dataMap, dbValue); + } + + _repos.ReflectionStrategy.SetFieldValue(ent, dataMap.FieldName, dbValue); + } + catch (Exception ex) + { + string msg = string.Format("The DataMapper was unable to load the following field: '{0}'.", + dataMap.ColumnInfo.Name); + + throw new DataMappingException(msg, ex); + } + } + + PrepareLazyLoadedProperties(ent); + + return ent; + } + + private void PrepareLazyLoadedProperties(object ent) + { + // Handle lazy loaded properties + Type entType = ent.GetType(); + if (_repos.Relationships.ContainsKey(entType)) + { + Func dbCreate = () => + { + var db = new DataMapper(_db.ProviderFactory, _db.ConnectionString); + db.SqlMode = SqlModes.Text; + return db; + }; + + var relationships = _repos.Relationships[entType]; + foreach (var rel in relationships.Where(r => r.IsLazyLoaded)) + { + ILazyLoaded lazyLoaded = (ILazyLoaded)rel.LazyLoaded.Clone(); + lazyLoaded.Prepare(dbCreate, ent); + _repos.ReflectionStrategy.SetFieldValue(ent, rel.Member.Name, lazyLoaded); + } + } + } + + public T LoadSimpleValueFromFirstColumn(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); + } + } + + /// + /// 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. + /// + public void CreateParameters(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 = _repos.ReflectionStrategy.GetFieldValue(entity, columnMap.FieldName); + + param.Value = val == null ? DBNull.Value : val; // Convert nulls to DBNulls + + var repos = MapRepository.Instance; + + IConverter conversion = repos.GetConverter(columnMap.FieldType); + if (conversion != null) + { + param.Value = conversion.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 + repos.DbTypeBuilder.SetDbType(param, columnMap.DBType); + + _db.Command.Parameters.Add(param); + } + } + + /// + /// Assigns the SP result columns to the passed in 'mappings' fields. + /// + public void SetOutputValues(T entity, IEnumerable mappings) + { + foreach (ColumnMap dataMap in mappings) + { + object output = _db.Command.Parameters[dataMap.ColumnInfo.Name].Value; + _repos.ReflectionStrategy.SetFieldValue(entity, dataMap.FieldName, output); + } + } + + /// + /// Assigns the passed in 'value' to the passed in 'mappings' fields. + /// + public void SetOutputValues(T entity, IEnumerable mappings, object value) + { + foreach (ColumnMap dataMap in mappings) + { + _repos.ReflectionStrategy.SetFieldValue(entity, dataMap.FieldName, Convert.ChangeType(value, dataMap.FieldType)); + } + } + + } +} diff --git a/Marr.Data/Mapping/Relationship.cs b/Marr.Data/Mapping/Relationship.cs new file mode 100644 index 000000000..f39715292 --- /dev/null +++ b/Marr.Data/Mapping/Relationship.cs @@ -0,0 +1,120 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +namespace Marr.Data.Mapping +{ + public class Relationship + { + private IRelationshipInfo _relationshipInfo; + private MemberInfo _member; + private ILazyLoaded _lazyLoaded; + + public Relationship(MemberInfo member) + : this(member, new RelationshipInfo()) + { } + + public Relationship(MemberInfo member, IRelationshipInfo relationshipInfo) + { + _member = member; + + Type memberType = ReflectionHelper.GetMemberType(member); + + // Try to determine the RelationshipType + if (relationshipInfo.RelationType == RelationshipTypes.AutoDetect) + { + if (typeof(System.Collections.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 or List 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; + } + + public IRelationshipInfo RelationshipInfo + { + get { return _relationshipInfo; } + } + + public MemberInfo Member + { + get { return _member; } + } + + public Type MemberType + { + get + { + // Assumes that a relationship can only have a member type of Field or Property + if (Member.MemberType == MemberTypes.Field) + return (Member as FieldInfo).FieldType; + else + return (Member as PropertyInfo).PropertyType; + } + } + + public bool IsLazyLoaded + { + get + { + return _lazyLoaded != null; + } + } + + public ILazyLoaded LazyLoaded + { + get + { + return _lazyLoaded; + } + set + { + _lazyLoaded = value; + } + } + } +} diff --git a/Marr.Data/Mapping/RelationshipAttribute.cs b/Marr.Data/Mapping/RelationshipAttribute.cs new file mode 100644 index 000000000..eaa2bb7c3 --- /dev/null +++ b/Marr.Data/Mapping/RelationshipAttribute.cs @@ -0,0 +1,77 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Marr.Data.Mapping +{ + /// + /// Defines a field as a related entity that needs to be created at filled with data. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + public class RelationshipAttribute : Attribute, IRelationshipInfo + { + /// + /// Defines a data relationship. + /// + public RelationshipAttribute() + : this(RelationshipTypes.AutoDetect) + { } + + /// + /// Defines a data relationship. + /// + /// + public RelationshipAttribute(RelationshipTypes relationType) + { + RelationType = relationType; + } + + /// + /// Defines a One-ToMany data relationship for a given type. + /// + /// The type of the child entity. + public RelationshipAttribute(Type entityType) + : this(entityType, RelationshipTypes.AutoDetect) + { } + + /// + /// Defines a data relationship. + /// + /// The type of the child entity. + /// The relationship type can be "One" or "Many". + public RelationshipAttribute(Type entityType, RelationshipTypes relationType) + { + EntityType = entityType; + RelationType = relationType; + } + + #region IRelationshipInfo Members + + /// + /// Gets or sets the relationship type can be "One" or "Many". + /// + public RelationshipTypes RelationType { get; set; } + + /// + /// Gets or sets the type of the child entity. + /// + public Type EntityType { get; set; } + + #endregion + } +} diff --git a/Marr.Data/Mapping/RelationshipBuilder.cs b/Marr.Data/Mapping/RelationshipBuilder.cs new file mode 100644 index 000000000..1b27cdd87 --- /dev/null +++ b/Marr.Data/Mapping/RelationshipBuilder.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; +using Marr.Data.Mapping.Strategies; + +namespace Marr.Data.Mapping +{ + /// + /// This class has fluent methods that are used to easily configure relationship mappings. + /// + /// + public class RelationshipBuilder + { + private FluentMappings.MappingsFluentEntity _fluentEntity; + private string _currentPropertyName; + + public RelationshipBuilder(FluentMappings.MappingsFluentEntity fluentEntity, RelationshipCollection relationships) + { + _fluentEntity = fluentEntity; + Relationships = relationships; + } + + /// + /// Gets the list of relationship mappings that are being configured. + /// + public RelationshipCollection Relationships { get; private set; } + + #region - Fluent Methods - + + /// + /// Initializes the configurator to configure the given property. + /// + /// + /// + public RelationshipBuilder For(Expression> property) + { + return For(property.GetMemberName()); + } + + /// + /// Initializes the configurator to configure the given property or field. + /// + /// + /// + public RelationshipBuilder For(string propertyName) + { + _currentPropertyName = propertyName; + + // Try to add the relationship if it doesn't exist + if (Relationships[_currentPropertyName] == null) + { + TryAddRelationshipForField(_currentPropertyName); + } + + return this; + } + + /// + /// Sets a property to be lazy loaded, with a given query. + /// + /// + /// + /// + public RelationshipBuilder LazyLoad(Func query) + { + AssertCurrentPropertyIsSet(); + + Relationships[_currentPropertyName].LazyLoaded = new LazyLoaded(query); + return this; + } + + public RelationshipBuilder SetOneToOne() + { + AssertCurrentPropertyIsSet(); + SetOneToOne(_currentPropertyName); + return this; + } + + public RelationshipBuilder SetOneToOne(string propertyName) + { + Relationships[propertyName].RelationshipInfo.RelationType = RelationshipTypes.One; + return this; + } + + public RelationshipBuilder SetOneToMany() + { + AssertCurrentPropertyIsSet(); + SetOneToMany(_currentPropertyName); + return this; + } + + public RelationshipBuilder SetOneToMany(string propertyName) + { + Relationships[propertyName].RelationshipInfo.RelationType = RelationshipTypes.Many; + return this; + } + + public RelationshipBuilder Ignore(Expression> property) + { + string propertyName = property.GetMemberName(); + Relationships.RemoveAll(r => r.Member.Name == propertyName); + return this; + } + + public FluentMappings.MappingsFluentTables Tables + { + get + { + if (_fluentEntity == null) + { + throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class."); + } + + return _fluentEntity.Table; + } + } + + public FluentMappings.MappingsFluentColumns Columns + { + get + { + if (_fluentEntity == null) + { + throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class."); + } + + return _fluentEntity.Columns; + } + } + + public FluentMappings.MappingsFluentEntity Entity() + { + return new FluentMappings.MappingsFluentEntity(true); + } + + /// + /// Tries to add a Relationship for the given field name. + /// Throws and exception if field cannot be found. + /// + 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)); + } + else + { + Relationships.Add(relationship); + } + } + + /// + /// Throws an exception if the "current" property has not been set. + /// + private void AssertCurrentPropertyIsSet() + { + if (string.IsNullOrEmpty(_currentPropertyName)) + { + throw new DataMappingException("A property must first be specified using the 'For' method."); + } + } + + #endregion + } +} diff --git a/Marr.Data/Mapping/RelationshipCollection.cs b/Marr.Data/Mapping/RelationshipCollection.cs new file mode 100644 index 000000000..a5b7bba88 --- /dev/null +++ b/Marr.Data/Mapping/RelationshipCollection.cs @@ -0,0 +1,37 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Marr.Data.Mapping +{ + public class RelationshipCollection : List + { + /// + /// Gets a ColumnMap by its field name. + /// + /// + /// + public Relationship this[string fieldName] + { + get + { + return this.Find(m => m.Member.Name == fieldName); + } + } + } +} diff --git a/Marr.Data/Mapping/RelationshipInfo.cs b/Marr.Data/Mapping/RelationshipInfo.cs new file mode 100644 index 000000000..aa4df3b14 --- /dev/null +++ b/Marr.Data/Mapping/RelationshipInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Mapping +{ + public class RelationshipInfo : IRelationshipInfo + { + public RelationshipTypes RelationType { get; set; } + + public Type EntityType { get; set; } + } +} diff --git a/Marr.Data/Mapping/Strategies/AttributeMapStrategy.cs b/Marr.Data/Mapping/Strategies/AttributeMapStrategy.cs new file mode 100644 index 000000000..7867412fc --- /dev/null +++ b/Marr.Data/Mapping/Strategies/AttributeMapStrategy.cs @@ -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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Marr.Data.Converters; +using Marr.Data.Parameters; + +namespace Marr.Data.Mapping.Strategies +{ + /// + /// Maps fields or properties that are marked with the ColumnAttribute. + /// + public class AttributeMapStrategy : ReflectionMapStrategyBase + { + public AttributeMapStrategy() + : base() + { } + + public AttributeMapStrategy(bool publicOnly) + : base(publicOnly) + { } + + public AttributeMapStrategy(BindingFlags flags) + : base(flags) + { } + + /// + /// Registers any member with a ColumnAttribute as a ColumnMap. + /// The entity that is being mapped. + /// The current member that is being inspected. + /// A ColumnAttribute (is null of one does not exist). + /// A list of ColumnMaps. + /// + protected override void CreateColumnMap(Type entityType, MemberInfo member, ColumnAttribute columnAtt, ColumnMapCollection columnMaps) + { + if (columnAtt != null) + { + ColumnMap columnMap = new ColumnMap(member, columnAtt); + columnMaps.Add(columnMap); + } + } + + /// + /// Registers any member with a RelationshipAttribute as a relationship. + /// + /// The entity that is being mapped. + /// The current member that is being inspected. + /// A RelationshipAttribute (is null if one does not exist). + /// A list of Relationships. + protected override void CreateRelationship(Type entityType, MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships) + { + if (relationshipAtt != null) + { + Relationship relationship = new Relationship(member, relationshipAtt); + relationships.Add(relationship); + } + } + } +} diff --git a/Marr.Data/Mapping/Strategies/ConventionMapStrategy.cs b/Marr.Data/Mapping/Strategies/ConventionMapStrategy.cs new file mode 100644 index 000000000..e140de16d --- /dev/null +++ b/Marr.Data/Mapping/Strategies/ConventionMapStrategy.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Collections; + +namespace Marr.Data.Mapping.Strategies +{ + /// + /// Allows you to specify a member based filter by defining predicates that filter the members that are mapped. + /// + 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 ColumnPredicate; + public Func RelationshipPredicate; + + + + protected override void CreateColumnMap(Type entityType, System.Reflection.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, System.Reflection.MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships) + { + if (RelationshipPredicate(member)) + { + relationships.Add(new Relationship(member)); + } + } + + } +} diff --git a/Marr.Data/Mapping/Strategies/IMapStrategy.cs b/Marr.Data/Mapping/Strategies/IMapStrategy.cs new file mode 100644 index 000000000..39f3801ee --- /dev/null +++ b/Marr.Data/Mapping/Strategies/IMapStrategy.cs @@ -0,0 +1,48 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Mapping.Strategies +{ + /// + /// A strategy for creating mappings for a given entity. + /// + public interface IMapStrategy + { + /// + /// Creates a table map for a given entity type. + /// + /// + /// + string MapTable(Type entityType); + + /// + /// Creates a ColumnMapCollection for a given entity type. + /// + /// The entity that is being mapped. + ColumnMapCollection MapColumns(Type entityType); + + /// + /// Creates a RelationshpCollection for a given entity type. + /// + /// The entity that is being mapped. + /// + RelationshipCollection MapRelationships(Type entityType); + } +} diff --git a/Marr.Data/Mapping/Strategies/PropertyMapStrategy.cs b/Marr.Data/Mapping/Strategies/PropertyMapStrategy.cs new file mode 100644 index 000000000..19af41dd9 --- /dev/null +++ b/Marr.Data/Mapping/Strategies/PropertyMapStrategy.cs @@ -0,0 +1,85 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace Marr.Data.Mapping.Strategies +{ + /// + /// Maps all public properties to DB columns. + /// + public class PropertyMapStrategy : AttributeMapStrategy + { + public PropertyMapStrategy(bool publicOnly) + : base(publicOnly) + { } + + /// + /// Maps properties to DB columns if a ColumnAttribute is not present. + /// The entity that is being mapped. + /// The current member that is being inspected. + /// A ColumnAttribute (is null of one does not exist). + /// A list of ColumnMaps. + /// + 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)); + } + } + } + + /// + /// Maps a relationship if a RelationshipAttribute is present. + /// + /// The entity that is being mapped. + /// The current member that is being inspected. + /// A RelationshipAttribute (is null if one does not exist). + /// A list of Relationships. + 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(System.Collections.ICollection).IsAssignableFrom(propertyInfo.PropertyType)) + { + Relationship relationship = new Relationship(member); + relationships.Add(relationship); + } + } + } + } + } +} diff --git a/Marr.Data/Mapping/Strategies/ReflectionMapStrategyBase.cs b/Marr.Data/Mapping/Strategies/ReflectionMapStrategyBase.cs new file mode 100644 index 000000000..d52217e25 --- /dev/null +++ b/Marr.Data/Mapping/Strategies/ReflectionMapStrategyBase.cs @@ -0,0 +1,151 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace Marr.Data.Mapping.Strategies +{ + /// + /// Iterates through the members of an entity based on the BindingFlags, and provides an abstract method for adding ColumnMaps for each member. + /// + public abstract class ReflectionMapStrategyBase : IMapStrategy + { + private BindingFlags _bindingFlags; + + /// + /// Loops through members with the following BindingFlags: + /// Instance | NonPublic | Public | FlattenHierarchy + /// + public ReflectionMapStrategyBase() + { + _bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy; + } + + /// + /// Loops through members with the following BindingFlags: + /// Instance | Public | FlattenHierarchy | NonPublic (optional) + /// + /// + public ReflectionMapStrategyBase(bool publicOnly) + { + if (publicOnly) + { + _bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy; + } + else + { + _bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy; + } + } + + /// + /// Loops through members based on the passed in BindingFlags. + /// + /// + 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; + } + else + { + return entityType.Name; + } + } + + /// + /// Implements IMapStrategy. + /// Loops through filtered members and calls the virtual "CreateColumnMap" void for each member. + /// Subclasses can override CreateColumnMap to customize adding ColumnMaps. + /// + /// + /// + 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; + } + + /// + /// Inspect a member and optionally add a ColumnMap. + /// + /// The entity type that is being mapped. + /// The member that is being mapped. + /// The ColumnMapCollection that is being created. + protected abstract void CreateColumnMap(Type entityType, MemberInfo member, ColumnAttribute columnAtt, ColumnMapCollection columnMaps); + + /// + /// Inspect a member and optionally add a Relationship. + /// + /// The entity that is being mapped. + /// The current member that is being inspected. + /// A RelationshipAttribute (is null if one does not exist). + /// A list of Relationships. + protected abstract void CreateRelationship(Type entityType, MemberInfo member, RelationshipAttribute relationshipAtt, RelationshipCollection relationships); + } +} diff --git a/Marr.Data/Mapping/TableAttribute.cs b/Marr.Data/Mapping/TableAttribute.cs new file mode 100644 index 000000000..8042bea5c --- /dev/null +++ b/Marr.Data/Mapping/TableAttribute.cs @@ -0,0 +1,43 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +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; } + } + } +} diff --git a/Marr.Data/Mapping/TableBuilder.cs b/Marr.Data/Mapping/TableBuilder.cs new file mode 100644 index 000000000..a8f52995c --- /dev/null +++ b/Marr.Data/Mapping/TableBuilder.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Mapping +{ + /// + /// This class has fluent methods that are used to easily configure the table mapping. + /// + public class TableBuilder + { + private FluentMappings.MappingsFluentEntity _fluentEntity; + + public TableBuilder(FluentMappings.MappingsFluentEntity fluentEntity) + { + _fluentEntity = fluentEntity; + } + + #region - Fluent Methods - + + public TableBuilder SetTableName(string tableName) + { + MapRepository.Instance.Tables[typeof(TEntity)] = tableName; + return this; + } + + public FluentMappings.MappingsFluentColumns Columns + { + get + { + if (_fluentEntity == null) + { + throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class."); + } + + return _fluentEntity.Columns; + } + } + + public FluentMappings.MappingsFluentRelationships Relationships + { + get + { + if (_fluentEntity == null) + { + throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class."); + } + + return _fluentEntity.Relationships; + } + } + + public FluentMappings.MappingsFluentEntity Entity() + { + return new FluentMappings.MappingsFluentEntity(true); + } + + #endregion + } +} diff --git a/Marr.Data/Marr.Data.csproj b/Marr.Data/Marr.Data.csproj new file mode 100644 index 000000000..ab68c06c3 --- /dev/null +++ b/Marr.Data/Marr.Data.csproj @@ -0,0 +1,164 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {F6FC6BE7-0847-4817-A1ED-223DC647C3D7} + Library + Properties + Marr.Data + Marr.Data + v3.5 + 512 + SAK + SAK + SAK + SAK + + + 3.5 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\Libraries\FastReflection\FastReflection.dll + + + + 3.5 + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Marr.Data/NuGet/NuGet.exe b/Marr.Data/NuGet/NuGet.exe new file mode 100644 index 0000000000000000000000000000000000000000..037267229f0f6e04b739571b661e986f651ad9b7 GIT binary patch literal 292864 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�yhN#z3*$pw98YJSwad`D)lIQ&P_!n{wn`c{y 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 + /// Converts from a .NET datatype to the appropriate DB type enum, + /// and then adds the value to the appropriate property on the parameter. + /// + public interface IDbTypeBuilder + { + Enum GetDbType(Type type); + void SetDbType(IDbDataParameter param, Enum dbType); + } +} diff --git a/Marr.Data/Parameters/OleDbTypeBuilder.cs b/Marr.Data/Parameters/OleDbTypeBuilder.cs new file mode 100644 index 000000000..44ba6ddf0 --- /dev/null +++ b/Marr.Data/Parameters/OleDbTypeBuilder.cs @@ -0,0 +1,72 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.OleDb; +using Marr.Data.Mapping; + +namespace Marr.Data.Parameters +{ + public class OleDbTypeBuilder : IDbTypeBuilder + { + public Enum GetDbType(Type type) + { + if (type == typeof(String)) + return OleDbType.VarChar; + + else if (type == typeof(Int32)) + return OleDbType.Integer; + + else if (type == typeof(Decimal)) + return OleDbType.Decimal; + + else if (type == typeof(DateTime)) + return OleDbType.DBTimeStamp; + + else if (type == typeof(Boolean)) + return OleDbType.Boolean; + + else if (type == typeof(Int16)) + return OleDbType.SmallInt; + + else if (type == typeof(Int64)) + return OleDbType.BigInt; + + else if (type == typeof(Double)) + return OleDbType.Double; + + else if (type == typeof(Byte)) + return OleDbType.Binary; + + else if (type == typeof(Byte[])) + return OleDbType.VarBinary; + + else if (type == typeof(Guid)) + return OleDbType.Guid; + + else + return OleDbType.Variant; + } + + public void SetDbType(System.Data.IDbDataParameter param, Enum dbType) + { + var oleDbParam = (OleDbParameter)param; + oleDbParam.OleDbType = (OleDbType)dbType; + } + } +} diff --git a/Marr.Data/Parameters/ParameterChainMethods.cs b/Marr.Data/Parameters/ParameterChainMethods.cs new file mode 100644 index 000000000..9f857cbfd --- /dev/null +++ b/Marr.Data/Parameters/ParameterChainMethods.cs @@ -0,0 +1,124 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data; +using System.Data.Common; +using Marr.Data.Converters; + +namespace Marr.Data.Parameters +{ + /// + /// This class allows chaining methods to be called for convenience when adding a parameter. + /// + public class ParameterChainMethods + { + /// + /// Creates a new parameter and adds it to the command's Parameters collection. + /// + /// The command that the parameter will be added to. + /// The parameter name. + public ParameterChainMethods(DbCommand command, string parameterName, object value) + { + Parameter = command.CreateParameter(); + Parameter.ParameterName = parameterName; + + // Convert null to DBNull.Value + if (value == null) + value = DBNull.Value; + + Type valueType = value.GetType(); + + // Check for a registered IConverter + IConverter converter = MapRepository.Instance.GetConverter(valueType); + if (converter != null) + { + Parameter.Value = converter.ToDB(value); + } + else + { + Parameter.Value = value; + } + + //// Determine the correct DbType based on the passed in value type + //IDbTypeBuilder typeBuilder = MapRepository.Instance.DbTypeBuilder; + //Enum dbType = typeBuilder.GetDbType(valueType); + + //// Set the appropriate DbType property depending on the parameter type + //typeBuilder.SetDbType(Parameter, dbType); + + command.Parameters.Add(Parameter); + } + + /// + /// Gets a reference to the parameter. + /// + public IDbDataParameter Parameter { get; private set; } + + /// + /// Sets the direction of a parameter. + /// + /// Determines the direction of a parameter. + /// Return a ParameterChainMethods object. + public ParameterChainMethods Direction(ParameterDirection direction) + { + Parameter.Direction = direction; + return this; + } + + /// + /// Sets the direction of a parameter to 'Output'. + /// + /// + 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; + } + } +} diff --git a/Marr.Data/Parameters/SqlDbTypeBuilder.cs b/Marr.Data/Parameters/SqlDbTypeBuilder.cs new file mode 100644 index 000000000..df5197a3a --- /dev/null +++ b/Marr.Data/Parameters/SqlDbTypeBuilder.cs @@ -0,0 +1,76 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data; +using System.Data.SqlClient; +using Marr.Data.Mapping; + +namespace Marr.Data.Parameters +{ + public class SqlDbTypeBuilder : IDbTypeBuilder + { + public Enum GetDbType(Type type) + { + if (type == typeof(String)) + return SqlDbType.VarChar; + + else if (type == typeof(Int32)) + return SqlDbType.Int; + + else if (type == typeof(Decimal)) + return SqlDbType.Decimal; + + else if (type == typeof(DateTime)) + return SqlDbType.DateTime; + + else if (type == typeof(Boolean)) + return SqlDbType.Bit; + + else if (type == typeof(Int16)) + return SqlDbType.SmallInt; + + else if (type == typeof(Int64)) + return SqlDbType.BigInt; + + else if (type == typeof(Double)) + return SqlDbType.Float; + + else if (type == typeof(Char)) + return SqlDbType.Char; + + else if (type == typeof(Byte)) + return SqlDbType.Binary; + + else if (type == typeof(Byte[])) + return SqlDbType.VarBinary; + + else if (type == typeof(Guid)) + return SqlDbType.UniqueIdentifier; + + else + return SqlDbType.Variant; + } + + public void SetDbType(System.Data.IDbDataParameter param, Enum dbType) + { + var sqlDbParam = (SqlParameter)param; + sqlDbParam.SqlDbType = (SqlDbType)dbType; + } + } +} diff --git a/Marr.Data/Properties/AssemblyInfo.cs b/Marr.Data/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8b5025d1a --- /dev/null +++ b/Marr.Data/Properties/AssemblyInfo.cs @@ -0,0 +1,41 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Marr.Data")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Marr.Data")] +[assembly: AssemblyCopyright("Copyright © 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Share internals +[assembly: InternalsVisibleTo("Marr.Data.Relationships")] +[assembly: InternalsVisibleTo("Marr.Data.Tests")] + + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6864f4d2-cd0f-4720-9c15-3085f1aa8293")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("3.17.*")] +[assembly: AssemblyInformationalVersion("3.17")] diff --git a/Marr.Data/QGen/DeleteQuery.cs b/Marr.Data/QGen/DeleteQuery.cs new file mode 100644 index 000000000..2f0826725 --- /dev/null +++ b/Marr.Data/QGen/DeleteQuery.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; +using System.Data.Common; + +namespace Marr.Data.QGen +{ + /// + /// This class creates a SQL delete query. + /// + public class DeleteQuery : IQuery + { + protected Table TargetTable { get; set; } + protected string WhereClause { get; set; } + protected Dialects.Dialect Dialect { get; set; } + + public DeleteQuery(Dialects.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); + } + } +} diff --git a/Marr.Data/QGen/Dialects/Dialect.cs b/Marr.Data/QGen/Dialects/Dialect.cs new file mode 100644 index 000000000..18d20afda --- /dev/null +++ b/Marr.Data/QGen/Dialects/Dialect.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.QGen.Dialects +{ + public class Dialect + { + /// + /// The default token is surrounded by brackets. + /// + /// + 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; + } + } + } +} diff --git a/Marr.Data/QGen/Dialects/FirebirdDialect.cs b/Marr.Data/QGen/Dialects/FirebirdDialect.cs new file mode 100644 index 000000000..3e1202151 --- /dev/null +++ b/Marr.Data/QGen/Dialects/FirebirdDialect.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +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()); + } + } +} diff --git a/Marr.Data/QGen/Dialects/OracleDialect.cs b/Marr.Data/QGen/Dialects/OracleDialect.cs new file mode 100644 index 000000000..627197a01 --- /dev/null +++ b/Marr.Data/QGen/Dialects/OracleDialect.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +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(); + } + } +} diff --git a/Marr.Data/QGen/Dialects/SqlServerCeDialect.cs b/Marr.Data/QGen/Dialects/SqlServerCeDialect.cs new file mode 100644 index 000000000..e012020cf --- /dev/null +++ b/Marr.Data/QGen/Dialects/SqlServerCeDialect.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.QGen.Dialects +{ + public class SqlServerCeDialect : Dialect + { + public override string IdentityQuery + { + get + { + return "SELECT @@IDENTITY;"; + } + } + + public override bool SupportsBatchQueries + { + get + { + return false; + } + } + } +} diff --git a/Marr.Data/QGen/Dialects/SqlServerDialect.cs b/Marr.Data/QGen/Dialects/SqlServerDialect.cs new file mode 100644 index 000000000..5353bdfff --- /dev/null +++ b/Marr.Data/QGen/Dialects/SqlServerDialect.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.QGen.Dialects +{ + public class SqlServerDialect : Dialect + { + public override string IdentityQuery + { + get + { + return "SELECT SCOPE_IDENTITY();"; + } + } + } +} diff --git a/Marr.Data/QGen/Dialects/SqliteDialect.cs b/Marr.Data/QGen/Dialects/SqliteDialect.cs new file mode 100644 index 000000000..226675da7 --- /dev/null +++ b/Marr.Data/QGen/Dialects/SqliteDialect.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.QGen.Dialects +{ + public class SqliteDialect : Dialect + { + public override string IdentityQuery + { + get + { + return "SELECT last_insert_rowid();"; + } + } + } +} diff --git a/Marr.Data/QGen/ExpressionVisitor.cs b/Marr.Data/QGen/ExpressionVisitor.cs new file mode 100644 index 000000000..66aca57b1 --- /dev/null +++ b/Marr.Data/QGen/ExpressionVisitor.cs @@ -0,0 +1,149 @@ +/* This class was copied from Mehfuz's LinqExtender project, which is available from github. + * http://mehfuzh.github.com/LinqExtender/ + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; + +namespace Marr.Data.QGen +{ + /// + /// Expression visitor + /// + public class ExpressionVisitor + { + /// + /// Visits expression and delegates call to different to branch. + /// + /// + /// + 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 this.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 this.VisitBinary((BinaryExpression)expression); + case ExpressionType.Call: + return this.VisitMethodCall((MethodCallExpression)expression); + case ExpressionType.Constant: + return this.VisitConstant((ConstantExpression)expression); + case ExpressionType.MemberAccess: + return this.VisitMemberAccess((MemberExpression)expression); + case ExpressionType.Parameter: + return this.VisitParameter((ParameterExpression)expression); + + } + throw new ArgumentOutOfRangeException("expression", expression.NodeType.ToString()); + } + + /// + /// Visits the constance expression. To be implemented by user. + /// + /// + /// + protected virtual Expression VisitConstant(ConstantExpression expression) + { + return expression; + } + + /// + /// Visits the memeber access expression. To be implemented by user. + /// + /// + /// + protected virtual Expression VisitMemberAccess(MemberExpression expression) + { + return expression; + } + + /// + /// Visits the method call expression. To be implemented by user. + /// + /// + /// + protected virtual Expression VisitMethodCall(MethodCallExpression expression) + { + throw new NotImplementedException(); + } + + /// + /// Visits the binary expression. + /// + /// + /// + protected virtual Expression VisitBinary(BinaryExpression expression) + { + this.Visit(expression.Left); + this.Visit(expression.Right); + return expression; + } + + /// + /// Visits the unary expression. + /// + /// + /// + protected virtual Expression VisitUnary(UnaryExpression expression) + { + this.Visit(expression.Operand); + return expression; + } + + /// + /// Visits the lamda expression. + /// + /// + /// + protected virtual Expression VisitLamda(LambdaExpression lambdaExpression) + { + this.Visit(lambdaExpression.Body); + return lambdaExpression; + } + + private Expression VisitParameter(ParameterExpression expression) + { + return expression; + } + } +} diff --git a/Marr.Data/QGen/IQuery.cs b/Marr.Data/QGen/IQuery.cs new file mode 100644 index 000000000..aacdf0f73 --- /dev/null +++ b/Marr.Data/QGen/IQuery.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.QGen +{ + internal interface IQuery + { + /// + /// Generates a SQL query for a given entity. + /// + /// + string Generate(); + } +} diff --git a/Marr.Data/QGen/IQueryBuilder.cs b/Marr.Data/QGen/IQueryBuilder.cs new file mode 100644 index 000000000..618a9e6c9 --- /dev/null +++ b/Marr.Data/QGen/IQueryBuilder.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.QGen +{ + public interface IQueryBuilder + { + string BuildQuery(); + } + + public interface ISortQueryBuilder : IQueryBuilder + { + string BuildQuery(bool useAltNames); + } +} diff --git a/Marr.Data/QGen/InsertQuery.cs b/Marr.Data/QGen/InsertQuery.cs new file mode 100644 index 000000000..cfa351ef0 --- /dev/null +++ b/Marr.Data/QGen/InsertQuery.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; +using System.Data.Common; +using Marr.Data.QGen.Dialects; + +namespace Marr.Data.QGen +{ + /// + /// This class creates an insert query. + /// + 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(); + } + } +} diff --git a/Marr.Data/QGen/InsertQueryBuilder.cs b/Marr.Data/QGen/InsertQueryBuilder.cs new file mode 100644 index 000000000..6b5d40e5a --- /dev/null +++ b/Marr.Data/QGen/InsertQueryBuilder.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; +using System.Linq.Expressions; + +namespace Marr.Data.QGen +{ + public class InsertQueryBuilder : 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 Dialects.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 TableName(string tableName) + { + _tableName = tableName; + return this; + } + + public virtual InsertQueryBuilder QueryText(string queryText) + { + _generateQuery = false; + _db.Command.CommandText = queryText; + return this; + } + + public virtual InsertQueryBuilder Entity(T entity) + { + _entity = entity; + return this; + } + + /// + /// Runs an identity query to get the value of an autoincrement field. + /// + /// + public virtual InsertQueryBuilder 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 ColumnsIncluding(params Expression>[] properties) + { + List columnList = new List(); + + foreach (var column in properties) + { + columnList.Add(column.GetMemberName()); + } + + return ColumnsIncluding(columnList.ToArray()); + } + + public virtual InsertQueryBuilder ColumnsIncluding(params string[] properties) + { + _columnsToInsert = new ColumnMapCollection(); + + foreach (string propertyName in properties) + { + _columnsToInsert.Add(_mappings.GetByFieldName(propertyName)); + } + + return this; + } + + public virtual InsertQueryBuilder ColumnsExcluding(params Expression>[] properties) + { + List columnList = new List(); + + foreach (var column in properties) + { + columnList.Add(column.GetMemberName()); + } + + return ColumnsExcluding(columnList.ToArray()); + } + + public virtual InsertQueryBuilder 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(_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(_entity, _mappings.OutputFields); + if (scalar != null) + { + _mappingHelper.SetOutputValues(_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(_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; + } + } + } +} diff --git a/Marr.Data/QGen/JoinBuilder.cs b/Marr.Data/QGen/JoinBuilder.cs new file mode 100644 index 000000000..c382c85a2 --- /dev/null +++ b/Marr.Data/QGen/JoinBuilder.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.Common; +using Marr.Data.QGen.Dialects; +using System.Linq.Expressions; + +namespace Marr.Data.QGen +{ + /// + /// 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. + /// + /// The entity that is on the left side of the join. + /// The entity that is on the right side of the join. + public class JoinBuilder : WhereBuilder + { + public JoinBuilder(DbCommand command, Dialect dialect, Expression> 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; + } + } +} diff --git a/Marr.Data/QGen/PagingQueryDecorator.cs b/Marr.Data/QGen/PagingQueryDecorator.cs new file mode 100644 index 000000000..d9cf4a284 --- /dev/null +++ b/Marr.Data/QGen/PagingQueryDecorator.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; +using Marr.Data.QGen.Dialects; + +namespace Marr.Data.QGen +{ + /// + /// Decorates the SelectQuery by wrapping it in a paging query. + /// + 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(); + } + else + { + return SimplePaging(); + } + } + + /// + /// Generates a query that pages a simple inner query. + /// + /// + 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(); + } + + /// + /// Generates a query that pages a view or joined inner query. + /// + /// + 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 appended = new List(); + + 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); + } + } + } + } + } + + } +} diff --git a/Marr.Data/QGen/QueryBuilder.cs b/Marr.Data/QGen/QueryBuilder.cs new file mode 100644 index 000000000..f2eb9006b --- /dev/null +++ b/Marr.Data/QGen/QueryBuilder.cs @@ -0,0 +1,532 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.QGen; +using System.Linq.Expressions; +using System.Reflection; +using Marr.Data.Mapping; +using System.Data.Common; +using System.Collections; + +namespace Marr.Data.QGen +{ + /// + /// This class is responsible for building a select query. + /// It uses chaining methods to provide a fluent interface for creating select queries. + /// + /// + public class QueryBuilder : ExpressionVisitor, IEnumerable, IQueryBuilder + { + #region - Private Members - + + private Dialects.Dialect _dialect; + private DataMapper _db; + private TableCollection _tables; + private WhereBuilder _whereBuilder; + private SortBuilder _sortBuilder; + private bool _useAltName = false; + private bool _isGraph = false; + private string _queryText; + private List _childrenToLoad; + private bool _enablePaging = false; + private int _skip; + private int _take; + private SortBuilder SortBuilder + { + get + { + // Lazy load + if (_sortBuilder == null) + _sortBuilder = new SortBuilder(this, _db, _whereBuilder, _dialect, _tables, _useAltName); + + return _sortBuilder; + } + } + private List _results = new List(); + 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, Dialects.Dialect dialect) + { + _db = db; + _dialect = dialect; + _tables = new TableCollection(); + _tables.Add(new Table(typeof(T))); + _childrenToLoad = new List(); + } + + #endregion + + #region - Fluent Methods - + + /// + /// Overrides the base table name that will be used in the query. + /// + [Obsolete("This method is obsolete. Use either the FromTable or FromView method instead.", true)] + public virtual QueryBuilder From(string tableName) + { + return FromView(tableName); + } + + /// + /// Overrides the base view name that will be used in the query. + /// Will try to use the mapped "AltName" values when loading the columns. + /// + public virtual QueryBuilder FromView(string viewName) + { + if (string.IsNullOrEmpty(viewName)) + throw new ArgumentNullException("view"); + + _useAltName = true; + + // Replace the base table with a view with tables + View view = new View(viewName, _tables.ToArray()); + _tables.ReplaceBaseTable(view); + + //// Override the base table name + //_tables[0].Name = view; + return this; + } + + /// + /// 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. + /// + public virtual QueryBuilder FromTable(string table) + { + if (string.IsNullOrEmpty(table)) + throw new ArgumentNullException("view"); + + _useAltName = false; + + // Override the base table name + _tables[0].Name = table; + return this; + } + + /// + /// Allows you to manually specify the query text. + /// + public virtual QueryBuilder QueryText(string queryText) + { + _queryText = queryText; + return this; + } + + /// + /// 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. + /// + /// A list of related child entites to load (passed in as properties / lambda expressions). + public virtual QueryBuilder Graph(params Expression>[] 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; + _useAltName = true; + return this; + } + + public virtual QueryBuilder Page(int pageNumber, int pageSize) + { + _enablePaging = true; + _skip = (pageNumber - 1) * pageSize; + _take = pageSize; + return this; + } + + private string[] ParseChildrenToLoad(Expression>[] childrenToLoad) + { + List entitiesToLoad = new List(); + + // 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(); + } + + /// + /// Allows you to interact with the DbDataReader to manually load entities. + /// + /// An action that takes a DbDataReader. + public virtual void DataReader(Action 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; + + IQuery query = QueryFactory.CreateRowCountSelectQuery(_tables, _db, where, SortBuilder, _useAltName); + string queryText = query.Generate(); + + _db.SqlMode = SqlModes.Text; + int count = (int)_db.ExecuteScalar(queryText); + + _db.SqlMode = previousSqlMode; + return count; + } + + /// + /// Executes the query and returns a list of results. + /// + /// A list of query results of type T. + public virtual List ToList() + { + SqlModes previousSqlMode = _db.SqlMode; + + BuildQueryOrAppendClauses(); + + if (_isGraph) + { + _results = (List)_db.QueryToGraph(_queryText, EntGraph, _childrenToLoad); + } + else + { + _results = (List)_db.Query(_queryText, _results, _useAltName); + } + + // Return to previous sql mode + _db.SqlMode = previousSqlMode; + + return _results; + } + + 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; + + IQuery query = null; + if (_enablePaging) + { + query = QueryFactory.CreatePagingSelectQuery(_tables, _db, where, SortBuilder, _useAltName, _skip, _take); + } + else + { + query = QueryFactory.CreateSelectQuery(_tables, _db, where, SortBuilder, _useAltName); + } + + _queryText = query.Generate(); + + return _queryText; + } + + #endregion + + #region - Helper Methods - + + private ColumnMapCollection GetColumns(IEnumerable entitiesToLoad) + { + // If QueryToGraph and no child load entities are specified, load all children + bool loadAllChildren = _useAltName && entitiesToLoad == null; + + // If Query + if (!_useAltName) + { + 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(QueryBuilder builder) + { + return builder.ToList(); + } + + #endregion + + #region - Linq Support - + + public virtual SortBuilder Where(Expression> filterExpression) + { + _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, _useAltName, true); + return SortBuilder; + } + + public virtual SortBuilder Where(Expression> filterExpression) + { + _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, false, true); + return SortBuilder; + } + + public virtual SortBuilder Where(string whereClause) + { + if (string.IsNullOrEmpty(whereClause)) + throw new ArgumentNullException("whereClause"); + + if (!whereClause.ToUpper().Contains("WHERE ")) + { + whereClause = whereClause.Insert(0, " WHERE "); + } + + _whereBuilder = new WhereBuilder(whereClause, _useAltName); + return SortBuilder; + } + + public virtual SortBuilder OrderBy(Expression> sortExpression) + { + SortBuilder.OrderBy(sortExpression); + return SortBuilder; + } + + public virtual SortBuilder ThenBy(Expression> sortExpression) + { + SortBuilder.OrderBy(sortExpression); + return SortBuilder; + } + + public virtual SortBuilder OrderByDescending(Expression> sortExpression) + { + SortBuilder.OrderByDescending(sortExpression); + return SortBuilder; + } + + public virtual SortBuilder ThenByDescending(Expression> sortExpression) + { + SortBuilder.OrderByDescending(sortExpression); + return SortBuilder; + } + + public virtual SortBuilder 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 Take(int count) + { + _enablePaging = true; + _take = count; + return this; + } + + public virtual QueryBuilder Skip(int count) + { + _enablePaging = true; + _skip = count; + return this; + } + + /// + /// Handles all. + /// + /// + /// + protected override System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression expression) + { + return base.Visit(expression); + } + + /// + /// Handles Where. + /// + /// + /// + protected override System.Linq.Expressions.Expression VisitLamda(System.Linq.Expressions.LambdaExpression lambdaExpression) + { + _sortBuilder = this.Where(lambdaExpression as Expression>); + return base.VisitLamda(lambdaExpression); + } + + /// + /// Handles OrderBy. + /// + /// + /// + protected override System.Linq.Expressions.Expression VisitMethodCall(MethodCallExpression expression) + { + if (expression.Method.Name == "OrderBy" || expression.Method.Name == "ThenBy") + { + var memberExp = ((expression.Arguments[1] as UnaryExpression).Operand as System.Linq.Expressions.LambdaExpression).Body as System.Linq.Expressions.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 System.Linq.Expressions.LambdaExpression).Body as System.Linq.Expressions.MemberExpression; + _sortBuilder.OrderByDescending(memberExp.Expression.Type, memberExp.Member.Name); + } + + return base.VisitMethodCall(expression); + } + + public virtual QueryBuilder Join(JoinType joinType, Expression>> rightEntity, Expression> filterExpression) + { + MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; + return this.Join(joinType, rightMember, filterExpression); + } + + public virtual QueryBuilder Join(JoinType joinType, Expression> rightEntity, Expression> filterExpression) + { + MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; + return this.Join(joinType, rightMember, filterExpression); + } + + public virtual QueryBuilder Join(JoinType joinType, MemberInfo rightMember, Expression> filterExpression) + { + _useAltName = true; + _isGraph = true; + + if (!_childrenToLoad.ContainsMember(rightMember)) + _childrenToLoad.Add(rightMember); + + Table table = new Table(typeof(TRight), joinType); + _tables.Add(table); + + var builder = new JoinBuilder(_db.Command, _dialect, filterExpression, _tables); + + table.JoinClause = builder.ToString(); + return this; + } + + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + var list = this.ToList(); + return list.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + var list = this.ToList(); + return list.GetEnumerator(); + } + + #endregion + } +} diff --git a/Marr.Data/QGen/QueryFactory.cs b/Marr.Data/QGen/QueryFactory.cs new file mode 100644 index 000000000..b2a15248a --- /dev/null +++ b/Marr.Data/QGen/QueryFactory.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.Common; +using Marr.Data.QGen.Dialects; + +namespace Marr.Data.QGen +{ + /// + /// This class contains the factory logic that determines which type of IQuery object should be created. + /// + 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(Mapping.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(Mapping.ColumnMapCollection columns, IDataMapper dataMapper, string target) + { + Dialect dialect = CreateDialect(dataMapper); + return new InsertQuery(dialect, columns, dataMapper.Command, target); + } + + public static IQuery CreateDeleteQuery(Dialects.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); + + 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); + + default: + throw new NotImplementedException("Paging has not yet been implemented for this provider."); + } + } + + public static Dialects.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(); + } + } + } +} diff --git a/Marr.Data/QGen/QueryQueueItem.cs b/Marr.Data/QGen/QueryQueueItem.cs new file mode 100644 index 000000000..98edb6c79 --- /dev/null +++ b/Marr.Data/QGen/QueryQueueItem.cs @@ -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 entitiesToLoad) +// { +// QueryText = queryText; +// EntitiesToLoad = entitiesToLoad; +// } + +// public string QueryText { get; set; } +// public IEnumerable EntitiesToLoad { get; private set; } +// } +//} diff --git a/Marr.Data/QGen/RowCountQueryDecorator.cs b/Marr.Data/QGen/RowCountQueryDecorator.cs new file mode 100644 index 000000000..f144dc3e7 --- /dev/null +++ b/Marr.Data/QGen/RowCountQueryDecorator.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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(); + } + else + { + return SimpleRowCount(); + } + } + + /// + /// Generates a row count query for a multiple table joined query (groups by the parent entity). + /// + /// + 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(); + } + + /// + /// Generates a row count query for a single table query (no joins). + /// + /// + 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 +*/ \ No newline at end of file diff --git a/Marr.Data/QGen/SelectQuery.cs b/Marr.Data/QGen/SelectQuery.cs new file mode 100644 index 000000000..0e74b008d --- /dev/null +++ b/Marr.Data/QGen/SelectQuery.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data; +using Marr.Data.Mapping; +using System.Data.Common; +using Marr.Data.QGen.Dialects; + +namespace Marr.Data.QGen +{ + /// + /// This class is responsible for creating a select query. + /// + 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; + } + else + { + return columnInfo.Name; + } + } + + public void BuildFromClause(StringBuilder sql) + { + // BASE TABLE + Table baseTable = Tables[0]; + sql.AppendFormat(" FROM {0} {1} ", Dialect.CreateToken(baseTable.Name), Dialect.CreateToken(baseTable.Alias)); + } + + public void BuildJoinClauses(StringBuilder sql) + { + // JOINS + for (int i = 1; i < Tables.Count; i++) + { + if (Tables[i].JoinType != JoinType.None) + { + sql.AppendFormat("{0} {1} {2} {3} ", + TranslateJoin(Tables[i].JoinType), + Dialect.CreateToken(Tables[i].Name), + Dialect.CreateToken(Tables[i].Alias), + Tables[i].JoinClause); + } + } + } + + public void BuildWhereClause(StringBuilder sql) + { + sql.Append(WhereClause); + } + + public void BuildOrderClause(StringBuilder sql) + { + sql.Append(OrderBy.ToString()); + } + + private string TranslateJoin(JoinType join) + { + switch (join) + { + case JoinType.Inner: + return "INNER JOIN"; + case JoinType.Left: + return "LEFT JOIN"; + case JoinType.Right: + return "RIGHT JOIN"; + default: + return string.Empty; + } + } + } +} diff --git a/Marr.Data/QGen/SortBuilder.cs b/Marr.Data/QGen/SortBuilder.cs new file mode 100644 index 000000000..c5b1fc240 --- /dev/null +++ b/Marr.Data/QGen/SortBuilder.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; +using System.Reflection; +using Marr.Data.QGen.Dialects; + +namespace Marr.Data.QGen +{ + /// + /// 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. + /// + /// + public class SortBuilder : IEnumerable, ISortQueryBuilder + { + private string _constantOrderByClause; + private QueryBuilder _baseBuilder; + private Dialect _dialect; + private List> _sortExpressions; + private bool _useAltName; + private TableCollection _tables; + private IDataMapper _db; + private WhereBuilder _whereBuilder; + + public SortBuilder() + { + // Used only for unit testing with mock frameworks + } + + public SortBuilder(QueryBuilder baseBuilder, IDataMapper db, WhereBuilder whereBuilder, Dialect dialect, TableCollection tables, bool useAltName) + { + _baseBuilder = baseBuilder; + _db = db; + _whereBuilder = whereBuilder; + _dialect = dialect; + _sortExpressions = new List>(); + _useAltName = useAltName; + _tables = tables; + } + + #region - AndWhere / OrWhere - + + public virtual SortBuilder OrWhere(Expression> filterExpression) + { + var orWhere = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, _useAltName, true); + _whereBuilder.Append(orWhere, WhereAppendType.OR); + return this; + } + + public virtual SortBuilder OrWhere(string whereClause) + { + var orWhere = new WhereBuilder(whereClause, _useAltName); + _whereBuilder.Append(orWhere, WhereAppendType.OR); + return this; + } + + public virtual SortBuilder AndWhere(Expression> filterExpression) + { + var andWhere = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, _useAltName, true); + _whereBuilder.Append(andWhere, WhereAppendType.AND); + return this; + } + + public virtual SortBuilder AndWhere(string whereClause) + { + var andWhere = new WhereBuilder(whereClause, _useAltName); + _whereBuilder.Append(andWhere, WhereAppendType.AND); + return this; + } + + #endregion + + #region - Order - + + internal SortBuilder Order(Type declaringType, string propertyName) + { + _sortExpressions.Add(new SortColumn(declaringType, propertyName, SortDirection.Asc)); + return this; + } + + internal SortBuilder OrderByDescending(Type declaringType, string propertyName) + { + _sortExpressions.Add(new SortColumn(declaringType, propertyName, SortDirection.Desc)); + return this; + } + + public virtual SortBuilder 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 OrderBy(Expression> sortExpression) + { + _sortExpressions.Add(new SortColumn(sortExpression, SortDirection.Asc)); + return this; + } + + public virtual SortBuilder OrderByDescending(Expression> sortExpression) + { + _sortExpressions.Add(new SortColumn(sortExpression, SortDirection.Desc)); + return this; + } + + public virtual SortBuilder ThenBy(Expression> sortExpression) + { + _sortExpressions.Add(new SortColumn(sortExpression, SortDirection.Asc)); + return this; + } + + public virtual SortBuilder ThenByDescending(Expression> sortExpression) + { + _sortExpressions.Add(new SortColumn(sortExpression, SortDirection.Desc)); + return this; + } + + #endregion + + #region - Paging - + + public virtual SortBuilder Take(int count) + { + _baseBuilder.Take(count); + return this; + } + + public virtual SortBuilder Skip(int count) + { + _baseBuilder.Skip(count); + return this; + } + + public virtual SortBuilder 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 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); + sb.Append(_dialect.CreateToken(string.Format("{0}.{1}", table.Alias, 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 Operator - + + public static implicit operator List(SortBuilder builder) + { + return builder.ToList(); + } + + #endregion + + #region IEnumerable Members + + public virtual IEnumerator GetEnumerator() + { + var list = this.ToList(); + return list.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Marr.Data/QGen/SortColumn.cs b/Marr.Data/QGen/SortColumn.cs new file mode 100644 index 000000000..1a38c3c82 --- /dev/null +++ b/Marr.Data/QGen/SortColumn.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; +using System.Reflection; + +namespace Marr.Data.QGen +{ + public class SortColumn + { + public SortColumn(Expression> 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 + } +} diff --git a/Marr.Data/QGen/Table.cs b/Marr.Data/QGen/Table.cs new file mode 100644 index 000000000..e778d6f8e --- /dev/null +++ b/Marr.Data/QGen/Table.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.QGen +{ + /// + /// This class represents a table in a query. + /// A table contains corresponding columns. + /// + 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 + } +} diff --git a/Marr.Data/QGen/TableCollection.cs b/Marr.Data/QGen/TableCollection.cs new file mode 100644 index 000000000..15275d344 --- /dev/null +++ b/Marr.Data/QGen/TableCollection.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using System.Reflection; + +namespace Marr.Data.QGen +{ + /// + /// This class holds a collection of Table objects. + /// + public class TableCollection : IEnumerable + { + private List
_tables; + + public TableCollection() + { + _tables = new List
(); + } + + public void Add(Table table) + { + if (this.Any(t => t.EntityType == table.EntityType)) + { + // Already exists -- don't add + return; + } + + // 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); + } + + /// + /// Tries to find a table for a given member. + /// + public Table FindTable(Type declaringType) + { + return this.EnumerateViewsAndTables().Where(t => t.EntityType == declaringType).FirstOrDefault(); + } + + public Table this[int index] + { + get + { + return _tables[index]; + } + } + + public int Count + { + get + { + return _tables.Count; + } + } + + /// + /// Recursively enumerates through all tables, including tables embedded in views. + /// + /// + public IEnumerable
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
GetEnumerator() + { + return _tables.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _tables.GetEnumerator(); + } + } +} diff --git a/Marr.Data/QGen/UpdateQuery.cs b/Marr.Data/QGen/UpdateQuery.cs new file mode 100644 index 000000000..79ca69784 --- /dev/null +++ b/Marr.Data/QGen/UpdateQuery.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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(); + } + + + } +} diff --git a/Marr.Data/QGen/UpdateQueryBuilder.cs b/Marr.Data/QGen/UpdateQueryBuilder.cs new file mode 100644 index 000000000..ddbec2719 --- /dev/null +++ b/Marr.Data/QGen/UpdateQueryBuilder.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; +using System.Linq.Expressions; + +namespace Marr.Data.QGen +{ + public class UpdateQueryBuilder + { + 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> _filterExpression; + private Dialects.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 TableName(string tableName) + { + _tableName = tableName; + return this; + } + + public virtual UpdateQueryBuilder QueryText(string queryText) + { + _generateQuery = false; + _db.Command.CommandText = queryText; + return this; + } + + public virtual UpdateQueryBuilder Entity(T entity) + { + _entity = entity; + return this; + } + + public virtual UpdateQueryBuilder Where(Expression> filterExpression) + { + _filterExpression = filterExpression; + return this; + } + + public virtual UpdateQueryBuilder ColumnsIncluding(params Expression>[] properties) + { + List columnList = new List(); + + foreach (var column in properties) + { + columnList.Add(column.GetMemberName()); + } + + return ColumnsIncluding(columnList.ToArray()); + } + + public virtual UpdateQueryBuilder ColumnsIncluding(params string[] properties) + { + _columnsToUpdate = new ColumnMapCollection(); + + foreach (string propertyName in properties) + { + _columnsToUpdate.Add(_mappings.GetByFieldName(propertyName)); + } + + return this; + } + + public virtual UpdateQueryBuilder ColumnsExcluding(params Expression>[] properties) + { + List columnList = new List(); + + foreach (var column in properties) + { + columnList.Add(column.GetMemberName()); + } + + return ColumnsExcluding(columnList.ToArray()); + } + + public virtual UpdateQueryBuilder 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(_entity, columnsToUpdate, _generateQuery); + + string where = string.Empty; + if (_filterExpression != null) + { + var whereBuilder = new WhereBuilder(_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(_entity, _mappings, _generateQuery); + } + + int rowsAffected = 0; + + try + { + _db.OpenConnection(); + rowsAffected = _db.Command.ExecuteNonQuery(); + _mappingHelper.SetOutputValues(_entity, _mappings.OutputFields); + } + finally + { + _db.CloseConnection(); + } + + + if (_generateQuery) + { + // Return to previous sql mode + _db.SqlMode = _previousSqlMode; + } + + return rowsAffected; + } + } +} diff --git a/Marr.Data/QGen/View.cs b/Marr.Data/QGen/View.cs new file mode 100644 index 000000000..7b8c002d7 --- /dev/null +++ b/Marr.Data/QGen/View.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Marr.Data.Mapping; + +namespace Marr.Data.QGen +{ + /// + /// This class represents a View. A view can hold multiple tables (and their columns). + /// + public class View : Table, IEnumerable
+ { + private string _viewName; + private Table[] _tables; + private Mapping.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; + } + } + } + + /// + /// Gets all the columns from all the tables included in the view. + /// + public override Mapping.ColumnMapCollection Columns + { + get + { + if (_columns == null) + { + var allColumns = _tables.SelectMany(t => t.Columns); + _columns = new ColumnMapCollection(); + _columns.AddRange(allColumns); + } + + return _columns; + } + } + + public IEnumerator
GetEnumerator() + { + foreach (Table table in _tables) + { + yield return table; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/Marr.Data/QGen/WhereBuilder.cs b/Marr.Data/QGen/WhereBuilder.cs new file mode 100644 index 000000000..7cada67f8 --- /dev/null +++ b/Marr.Data/QGen/WhereBuilder.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; +using Marr.Data; +using Marr.Data.Mapping; +using System.Data.Common; +using Marr.Data.Parameters; +using System.Reflection; +using Marr.Data.QGen.Dialects; + +namespace Marr.Data.QGen +{ + /// + /// 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. + /// + /// + public class WhereBuilder : 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.NodeType)); + + isLeftSide = false; + Visit(expression.Right); + + _sb.Append(")"); + + return expression; + } + + protected override Expression VisitMethodCall(MethodCallExpression expression) + { + string method = (expression as System.Linq.Expressions.MethodCallExpression).Method.Name; + switch (method) + { + case "Contains": + Write_Contains(expression); + break; + + case "StartsWith": + Write_StartsWith(expression); + break; + + case "EndsWith": + Write_EndsWith(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) + { + // 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; + return expression; + } + + private object GetRightValue(Expression rightExpression) + { + object rightValue = null; + + var right = rightExpression as ConstantExpression; + if (right == null) // Value is not directly passed in as a constant + { + var rightMemberExp = (rightExpression as MemberExpression); + var parentMemberExpression = rightMemberExp.Expression as MemberExpression; + if (parentMemberExpression != null) // Value is passed in as a property on a parent entity + { + string entityName = (rightMemberExp.Expression as MemberExpression).Member.Name; + var container = ((rightMemberExp.Expression as MemberExpression).Expression as ConstantExpression).Value; + var entity = _repos.ReflectionStrategy.GetFieldValue(container, entityName); + rightValue = _repos.ReflectionStrategy.GetFieldValue(entity, rightMemberExp.Member.Name); + } + else // Value is passed in as a variable + { + var parent = (rightMemberExp.Expression as ConstantExpression).Value; + rightValue = _repos.ReflectionStrategy.GetFieldValue(parent, rightMemberExp.Member.Name); + } + } + else // Value is passed in directly as a constant + { + rightValue = right.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(ExpressionType expType) + { + switch (expType) + { + 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", expType.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("({0} LIKE '%' + {1} + '%')", fqColumn, paramName); + } + + 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("({0} LIKE {1} + '%')", 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("({0} LIKE '%' + {1})", fqColumn, paramName); + } + + /// + /// Appends the current where clause with another where clause. + /// + /// The second where clause that is being appended. + /// AND / OR + internal void Append(WhereBuilder where, WhereAppendType appendType) + { + _constantWhereClause = string.Format("{0} {1} {2}", + this.ToString(), + appendType.ToString(), + where.ToString().Replace("WHERE ", string.Empty)); + } + + public override string ToString() + { + if (string.IsNullOrEmpty(_constantWhereClause)) + { + return _sb.ToString(); + } + else + { + return _constantWhereClause; + } + } + } + + internal enum WhereAppendType + { + AND, + OR + } +} diff --git a/Marr.Data/Reflection/CachedReflectionStrategy.cs b/Marr.Data/Reflection/CachedReflectionStrategy.cs new file mode 100644 index 000000000..3d80fc8ed --- /dev/null +++ b/Marr.Data/Reflection/CachedReflectionStrategy.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using FastReflection; + +namespace Marr.Data.Reflection +{ + public class CachedReflectionStrategy : IReflectionStrategy + { + private FastReflection.CachedReflector _reflector; + + public CachedReflectionStrategy() + { + _reflector = new CachedReflector(); + } + + /// + /// Sets an entity field value by name to the passed in 'val'. + /// + public void SetFieldValue(T entity, string fieldName, object val) + { + MemberInfo member = entity.GetType().GetMember(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; + + try + { + // Handle DB null values + if (val == DBNull.Value) + { + if (member.MemberType == MemberTypes.Field) + _reflector.SetValue(member, entity, ReflectionHelper.GetDefaultValue((member as FieldInfo).FieldType)); + + else if (member.MemberType == MemberTypes.Property) + { + var pi = (member as PropertyInfo); + if (pi.CanWrite) + _reflector.SetValue(member, entity, ReflectionHelper.GetDefaultValue((member as PropertyInfo).PropertyType)); + } + } + else + { + if (member.MemberType == MemberTypes.Field) + _reflector.SetValue(member, entity, val); + else if (member.MemberType == MemberTypes.Property) + { + var pi = (member as PropertyInfo); + if (pi.CanWrite) + _reflector.SetValue(member, entity, val); + } + } + } + catch (Exception ex) + { + string msg = string.Format("The DataMapper was unable to load the following field: {0}. \nDetails: {1}", fieldName, ex.Message); + throw new DataMappingException(msg, ex); + } + } + + /// + /// Gets an entity field value by name. + /// + public object GetFieldValue(object entity, string fieldName) + { + MemberInfo member = entity.GetType().GetMember(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; + + if (member.MemberType == MemberTypes.Field) + { + return _reflector.GetValue(member, entity); + } + else if (member.MemberType == MemberTypes.Property) + { + if ((member as PropertyInfo).CanRead) + return _reflector.GetValue(member, entity); + } + + throw new DataMappingException(string.Format("The DataMapper could not get the value for {0}.{1}.", entity.GetType().Name, fieldName)); + } + + /// + /// Instantiantes a type using the FastReflector library for increased speed. + /// + /// + /// + public object CreateInstance(Type type) + { + return _reflector.Instantiate(type); + } + } +} diff --git a/Marr.Data/Reflection/IReflectionStrategy.cs b/Marr.Data/Reflection/IReflectionStrategy.cs new file mode 100644 index 000000000..096d64664 --- /dev/null +++ b/Marr.Data/Reflection/IReflectionStrategy.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data.Reflection +{ + public interface IReflectionStrategy + { + void SetFieldValue(T entity, string fieldName, object val); + object GetFieldValue(object entity, string fieldName); + object CreateInstance(Type type); + } +} diff --git a/Marr.Data/Reflection/ReflectionHelper.cs b/Marr.Data/Reflection/ReflectionHelper.cs new file mode 100644 index 000000000..f50e4c6f6 --- /dev/null +++ b/Marr.Data/Reflection/ReflectionHelper.cs @@ -0,0 +1,80 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +/* * + * The FastReflection library was written by Renaud Bédard (renaud.bedard@gmail.com) + * http://theinstructionlimit.com/?p=76 + * */ +using FastReflection; + +// ReSharper disable CheckNamespace +namespace Marr.Data +// ReSharper restore CheckNamespace +{ + public class ReflectionHelper + { + /// + /// Converts a DBNull.Value to a null for a reference field, + /// or the default value of a value field. + /// + /// + /// + public static object GetDefaultValue(Type fieldType) + { + if (fieldType.IsGenericType) + { + return null; + } + else if (fieldType.IsValueType) + { + return Activator.CreateInstance(fieldType); + } + else + { + return null; + } + } + + /// + /// Gets the CLR data type of a MemberInfo. + /// If the type is nullable, returns the underlying type. + /// + /// + /// + 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; + } + } +} diff --git a/Marr.Data/Reflection/SimpleReflectionStrategy.cs b/Marr.Data/Reflection/SimpleReflectionStrategy.cs new file mode 100644 index 000000000..968406bf6 --- /dev/null +++ b/Marr.Data/Reflection/SimpleReflectionStrategy.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Marr.Data; + +namespace Marr.Data.Reflection +{ + public class SimpleReflectionStrategy : IReflectionStrategy + { + /// + /// Sets an entity field value by name to the passed in 'val'. + /// + public void SetFieldValue(T entity, string fieldName, object val) + { + MemberInfo member = entity.GetType().GetMember(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; + + try + { + // Handle DB null values + if (val == DBNull.Value) + { + if (member.MemberType == MemberTypes.Field) + (member as FieldInfo).SetValue(entity, ReflectionHelper.GetDefaultValue((member as FieldInfo).FieldType)); + + else if (member.MemberType == MemberTypes.Property) + { + var pi = (member as PropertyInfo); + if (pi.CanWrite) + (member as PropertyInfo).SetValue(entity, ReflectionHelper.GetDefaultValue((member as PropertyInfo).PropertyType), null); + + } + } + else + { + if (member.MemberType == MemberTypes.Field) + (member as FieldInfo).SetValue(entity, val); + + else if (member.MemberType == MemberTypes.Property) + { + var pi = (member as PropertyInfo); + if (pi.CanWrite) + (member as PropertyInfo).SetValue(entity, val, null); + + } + } + } + catch (Exception ex) + { + string msg = string.Format("The DataMapper was unable to load the following field: {0}. \nDetails: {1}", fieldName, ex.Message); + throw new DataMappingException(msg, ex); + } + } + + /// + /// Gets an entity field value by name. + /// + public object GetFieldValue(object entity, string fieldName) + { + MemberInfo member = entity.GetType().GetMember(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; + + if (member.MemberType == MemberTypes.Field) + { + return (member as FieldInfo).GetValue(entity); + } + else if (member.MemberType == MemberTypes.Property) + { + if ((member as PropertyInfo).CanRead) + return (member as PropertyInfo).GetValue(entity, null); + } + throw new DataMappingException(string.Format("The DataMapper could not get the value for {0}.{1}.", entity.GetType().Name, fieldName)); + } + + /// + /// Instantiantes a type using the FastReflector library for increased speed. + /// + /// + /// + public object CreateInstance(Type type) + { + return Activator.CreateInstance(type); + } + } +} diff --git a/Marr.Data/SqlModesEnum.cs b/Marr.Data/SqlModesEnum.cs new file mode 100644 index 000000000..7ee400399 --- /dev/null +++ b/Marr.Data/SqlModesEnum.cs @@ -0,0 +1,27 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Marr.Data +{ + public enum SqlModes + { + StoredProcedure, + Text + } +} diff --git a/Marr.Data/UnitOfWork.cs b/Marr.Data/UnitOfWork.cs new file mode 100644 index 000000000..e91ae4df7 --- /dev/null +++ b/Marr.Data/UnitOfWork.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data +{ + /// + /// 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. + /// + /// + /// 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. + /// + public class UnitOfWork : IDisposable + { + private Func _dbConstructor; + private IDataMapper _lazyLoadedDB; + private short _transactionCount; + + public UnitOfWork(Func dbConstructor) + { + _dbConstructor = dbConstructor; + } + + /// + /// Gets an IDataMapper object whose lifetime is managed by the UnitOfWork class. + /// + public IDataMapper DB + { + get + { + if (_lazyLoadedDB == null) + { + _lazyLoadedDB = _dbConstructor.Invoke(); + } + + return _lazyLoadedDB; + } + } + + /// + /// Instructs the UnitOfWork to share a single IDataMapper instance. + /// + public UnitOfWorkSharedContext SharedContext + { + get + { + return new UnitOfWorkSharedContext(this); + } + } + + public void BeginTransaction() + { + // Only allow one transaction to begin + if (_transactionCount < 1) + { + DB.BeginTransaction(); + } + + _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( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { } + } +} diff --git a/Marr.Data/UnitOfWorkSharedContext.cs b/Marr.Data/UnitOfWorkSharedContext.cs new file mode 100644 index 000000000..3e45778af --- /dev/null +++ b/Marr.Data/UnitOfWorkSharedContext.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Marr.Data +{ + /// + /// Works in conjunction with the UnitOfWork to create a new + /// shared context that will preserve a single IDataMapper. + /// + 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(); + } + } + } +} diff --git a/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj b/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj index 5d4582d78..d1ac785c3 100644 --- a/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj +++ b/NzbDrone.Api.Test/NzbDrone.Api.Test.csproj @@ -11,6 +11,8 @@ NzbDrone.Api.Test v4.0 512 + ..\ + true true @@ -81,6 +83,7 @@ +

k_{t&(UV_{>*q=ZH93eZ_M>wOX*d(5sPS4MZcc=3Ip9dkSl@Sw8R$ZN=#|3 zJ*WYqw=yAra88EgZo>6Rjkk{_m+Gb9lIQ>|@AU4Z2r2B<%iCO_UQX%7y`0{Ie4ph0 zb%st)+YvTF`c@E=H1EUYQr0wk5}jNTo5c1apCtBR-klcFPT~U_DkeoFZ0+m8+ap4& z!lVzwOrvv<-$4J!#wpNpj@46gkyt&;Dp$XjYbd5>HZxg`Im?BNh>=j?3~L{rDyIYx zArdEASF*16V8UOR+~xG%UM;8hbZnpG0_FT}Q~VeQQqpDlYEfoP`f#w&`D{cCp>OoW9zXtfgEjRZ9uBXr5oi zT1twoNY;eIt?f}ZLNn##Nv>cm1F<<}v0u3hLh0(&L|4o4qCs4vvO5vtpt;OD;8Ra4d|+cAP$qso+p4uFGAo~aeubx9#Za2^`<$-4sRehKU*H)nl~s}>R;wf@$gYwj zX~QZxA{|yqUi+fXo`(7=Keyd5b1$qe!ZtHk4QCJ4faM~xhAH&iIGbQZF%sd)lLT`#HnK(h&@T#Qw zg~E^>RF)=3nn_5pnQzBt1E!JlXOZKLJ>^%Bd5$hVdYmP9M8%t)?0%>ih|Ci-{nTRc zr|0wKbQvhQYp3g;o#c)k?QTx)=<$Pj#mby z3R4>l?D^4XUA01U@*T%o+L8y7c?MQ?m`a7WH05K400DO1Cu*A(gROZv-kOfWG(peU z%8VA-*$irCgdfU?%zte4lU`tN{zV)3WFh8QsbQ-m3HQ=TuI_Mtvdi;Dn0-!lt#+cz zJEl_&K%y;Y>GT4?oCTgTTrv1KII8itGsxiYl; zl57)R&q=Ld@dZJ4i%80HohP(zlC$7BNbAC8!26uc5~RRhJ*Tpgv>?J<$g^y>@yjF4 z;8zsxdm78qZ*me#c~YP(MdpsPyd73knkz6=w~Y1V%g?~%Fl7hXbxZwEUgfDlBAZd! z1THFyuVxyA`1Hh84wNN17w&l4YVDI&v19CBb-1m3$||Iq-ogh0FXbn!LeeUyt3oPT zFA9mSld{QL8plZHeV16~MUQl!rb>-mROkZA5!1cMAJg5&jTLh=Iwpi6vy@UgJr$2Y zN#@EYr_z+@@nWIQo_>$qz1oSXm@Vg&V!GJxv{d=rPxqOhKrkEhqipNwO<2J8F)<%R z;xOJnHPa3$wY6B??NT%v*VeI*6aUsPd;}mYk@l-5-TuEuwy=ntAA=A!Y^skX;_rCN z_f-16c-U~<7EEYy*xs&*7wZi4j*Z~4o6WJgP9l_TjRJ>t77y86S-^Uqa@PmC&epV@n1aH(>Er1#vZEFK3au25UV_w4 z9E9{DzI;nc-h^E90m;0zi&KXlc<8|gyC0Oy;te=)xX2@HmpD|8V?d(cVw4#XF^~h$ z#9V~dk{TF4xxVBJMyMXoM2p|Y___1f~_xCrLrl06+(i<795 zZGH834(7NebH@O(h>_zo%S#sVD4XzzGJwu7VfVh0>%H6V!04c+n+AW8xyn_!+i;<`z+r?mNR!u}BsRn^TJb zX0P9I+u)k8Q%RTEZq`}WS2gM-namZ?L+5O@s^fxiTmcKeap663YFBi@WwCrF({L%j zs4d4X!K^rhmPAGd79%AKm}rZsg?RIG#+|w$4bjZTpIBO2IOdDa;X*zeI8AHCO_E0dcaU#tMD0$J1MU}p-^m8-1C>~$0AR{)`eb7A_It`KxE4x*kMsOp8&Y7Edf}% z6o=D%1d0*I7OyTW&8y$?j_?Kf1b`b+Nkl*?!q9o9VgQVK7V(Q2;s_Ro0T~9my+0>L zq%aeVW44UX)GL+^q8fwolJf+cBcHnP+;d0g=I5@?vXfsPzyrU-aigBtgTKSkwVv2R zePa*zjlHjL?EQUXALtwVVBgq>`o=!oH+G_L?0LoVu;vyEO__8<-B1}T%UZ00)p--#hs>F`Vi5sRVyx&|J3|*&z)ianv57u z?o2H{V55{wMmGQDG9FMDqS1WlgH}JXJmBX>blWpz>@Cy>MIE>8>LB{8AeUX8w^}!F z&X%0Mx@I&u=*$q-+gL60YuSuEefa569~=+&X&5I@yOGqv;4Zww>DTCN58VR09kDGv zc0rji(bE?_We;ZbjihqYHcx9YL)z*Lry?Fbc;L`|Vd9?IB8FyRaNg<@O>EBmw24GC zEbf}o9ZE4Gg)=CbogqrR0D#72VY@A!o|s+)KulzMLZTlFa0@<&n+Et^bc9B{Kp@O` zvd8Ax%giZ@5`qbo^q@1e%34m0y(^7Z+uE6?4tcF3(9##o;~w zFyGF&IqK8L;tIMlWxX8FZP9Ql)^)MWqT?kEWF5~3O#&Jz*~}pb?5@MV5%3>B#S}Fu z)Jv%SJ{@H4&%V&eK<+g15!d`j0?Ye30eUW*5>!^>?Z&2}*~OPOdfN#z4Bb;jv_@LG z0?g<;_TyiQOCh|+bd^JHv$Xw%>Badu4HuctAjn|*{M32*McH~9TlW1 z>7{Js`T4EZ{2ZFb5BlWH65S{<#kzRE^2$Vas>C#`!a%vcrG!M$x*XSlN}4D+<5*t= zmK#ap8_mE?$iVvr&XX0Dir&RtAC@QXA%K99_X-S{d~@fKNr`{j%+#RRjT$&@mX1N7 z8J?TSIU=n>t_j=X&EnL4#_w59c)Y-}WaQ-a7k*-1JB4|=?&3wxx}ut6Q9opvYyPCd ze2gfLVl<6C!)TF{uS}K{=WHAmA_Xj(dRMN}Q1MA}Z;G~5hI>$>4oKY6VIJrMQy^aSg>5MYuMd$?k<@t{7bbxK~xQ@&R;7rS0r!n5t3dmgm)T`@&B z=f$k2BTSQ5N5JAGqpmOw@topO|K!b)P6;|Gk4v;ns)=I*rKk26ZUc@ZcF&UldUm4q zd_faG7XwS|S9}ZgX1MhSx z5cU54$X5HqG`xHlcrjxR%XQLcppU(UBd|ynr~t{RC-%t?IV6dZt&%9q4~3E_Sk1R_ zSz12e<7h9JmxE+mwJ_ui&A4SGN!|u01?%DiavuB@T2+E$dvGLi$*Ziyq zW+r3UZ9(mnE_ye}8gb5jCiBV^s!pb4XIYY+VnDWEsB@9M>Y=zss{O=pqk2LN?Res} zMd3-XB+_U*8GK~{hqi#3?TABq2P6M+#cT250x@ zC!c3CWGrCE+oZ?w@+S7*_$hW;XlA9PV7v#54;wVbluq8pTsremDBDzj#!AH_;gJ?T zp+VOGZs{S`?K5K@KeS7txF2GvEN3`ok97^%?% zZX4s~m24POKoK}CU#By+?&{v)LlEuUu1#dfPT?8{!5XUxqSacQlx=A_qWtneAxE-G zg7&ldV%Y8zP{NYh@5^T6m@9N4r)J05J(8b|ExtV)^jMYw7Y%u=_9T>aa&K(P4oNEQ zz|R@%OqbNd44B;SySyNo^%Isdbc-V!0sAvklgsLb{q8nmWrhP_*_kJf-p|BHCq#q! z8aP6-9zIXRGau*N#14|D&5t^GqIO_@deErLk!37Idb!U)7p4c&&4qe0LRm*e;|cF? z6LwsDzUZ8%rk1f(;n+Re-7FGd zAC0i3O1gN~l*9Z&u8|@vy-vrS3YW^zU`CTnb@>(hY8ABwTHA2MLccAn6{!($G<2~< zdlRkf+|}L$+ooAW8`ijl?3D_d$K^5cuEoO0w2jL}Ji@5$g?T<(ghoGdS4%VLgg_rf zvc_RXs8qJ^g2EgB)cl;D$14{?bBHAh;Q1g`MCn?Ogj%v|7V+5!zkQJ}AduLaAOVIn zmqU;kp3P~d$d4_~zi769Q4oh54Mh9EKdo>JN0BNn*3hd7R1xb|Pf+n!!)rA~a>S)< zR{0XE00y=}SrlcCJ$XjmIHS95ideHu%pXz2Il?SbB&9vB$m4P{wHO?FMFJ$x1NjFw zfbGp~7BS=tn>I^v?iyig+8bNMsEMwQlQ`{;gAe+2!11ed6SH_-au$ooKQYY)ri9&5 ziq=iC+oXQ7AUfiqaNW~58wNQDO3(PRg$xSH7!w5}z%HQirF=$;9#h4$dc2z>9#dEd znn`FPmbqrJPj#Kx67dXMI|FWRkOu5RoDFIb;kl&;?lbx`p)ZX{TwmbVvuNi3+q8oj zu5NmA>d+`voZOZgmw3Av-5r5vu0jWP#Dkx9gGA27#>nF}9Ej7I!C7sw7xEr+NVBcU z`Nf&(^U*x9hc&v*JGQdUHIX!%aA#RIa4-M%WtA-#I2k;XaRM*PhL<=qy{xZ+{aLnM z=iqrUlD{*GJGh`T#M$%wy-4^O{wZe%6@ zmn<|-E0zL8kIPzTY4HMeHL{OVgTvxrEiG6et{3V@iM;?$i~JV(gkyu+8q_6dlhks7 zf0M*GT_Rrlv&u)0W-pL;nmQ)3^VTojxmvV1`}Ai+J+t;J97R>HUGQRC?jhv`(!3W% zDYYZp=3h#MM}|dJ{Xg5=pMp+bl{uu+yTwt{)FNDRm?LExg5>~t2kz$I$E?P?L+yly zFF=VY`XU?nc$R(WDY)`+!s3-hw}MXh`3mt}hXdAKdR{b|}&$k=DCr@2~&_C1}Q z%FbkGk;4gcFBpY6`=9sZy|jOlkf^ke5|ZU9(r2^EC%bDt7SfdavwVx9C!mzVi{PXF zJIB`yQngxbU#1T6Znh=Ce|x#6?C{gHO?)a26VG#&tJK04?JwuaK3axR(CI7L;Xt?h z_{~PTA!XZ+(~2kgcb0$IPPImDx|f=lXd$h+wLk3a)@O{8>?tT=mpxeYX~B7F_5Q3# zktP%^mZ8|BeP<6I=T}-&IFvayP3fja_HdfIS~*EFliD(-2iGpibq!$0(w|(t^f~gomR+Un;q{a~13g{NHL_&;o-m3o^r2=S?C$TMyNp>6Mee;> zsVq2|Gev<{Db{V-jwF|-mF{~ zwU71B?{q#-n`EoSJgd?Bq|r~Kx^%@&>3x5iquf`Y2gVW_RQiqeE$Ws{r*R@XR;F&b zuO6dcg{ktW{3@6-LZl;J8OADQPOMetl;OExe~*JZ6JD_B!!_e;K{*45MqBKv>G~KG z$K-@tKe0BXWpuBP3-I5gYvsKuFWtYs(p?ao(>KfT8E5T~mBKVm68wkFIn`` zD&_9IS-Gy>J>6~PJ#Ax^w8SF%4JQE9l%5e)x&0Qc_5$IfiR@2@8Jo{!vQOR&qC+|Z zG+A!a<8j8BeY(4BOzDqBH?G;ibHIx;$g^N9p4ABLt%=YQj@YXb&+~7dIeMAHSIae5 z8h4UhpFW3n<$Z5j)@>ahLw=-ppARked2E^=9s1j8xQxp&xJ_2X6qs_=5&iNEF?0Oi zDOgzTn(s(9R)@6;=iI+~i2}Ovh_1&&E3>1km+I~djWF^BW)|6&?5wBcq+qCKDDC$6YsuEpPJVJMN*RXl=R$OxyKGisa8t2u4^&-?&c%3!nTyJvf&0ELwWMD z-OAhTsmwO+BdxOW6Us5bzdOgNNtUksRx`w&7n9xLbP=Ul2qkt2$qhQ-R#_{H=8u%U{7j_UcGp~!&_!^vE#h_M zgbus=wq)ZKd~4b53x+Ru@{(H^&x8>!+bs{P{4-~OAw3`o&W2jm257D+B0N7XL*^fjLoZ_5wd!r$4us<4k7vcn&-9JBPx3;10tA5v`WKS2{myGKlN ztC?zc*V>d2S8JU_GCJ+Oa>wdWvpc(c_puV4vu)hOZ0*pvDc^a@3qgwDWc6cVZRRp_ z@Qc}pv%6U|_k~Jhh4qoUQmIz|mnDF{x^o6B zWLeCZO!cJilmr$2Yw>ZmQ8i=(_n@Iaj_&&+|4awYmsf|%J-YTfQCf>z8~57kkUa7C z^6x?Z`U-*8QpEf4?}sAKi3f{bPyOcEMq!w7UrM@e^_;o=UfS&b!gkz` zj7q1-=MpXd{qRPgQX92Wm2G$K_%bauNgY6gkEI+bJ*yPwbNF}nenR4X%@E_NP&T-q z9;fW?*#2YmvpCR3qtiYg@s%WBv2qFH5^3^DA2-m6%B^o7DVoGhk}9v2>!b!X2WxQn z1TtxQbLCIsXTt=_8}Ee+Q?Fr0mM@}7{F*S5hFeuGvC+UZL9EKDlzt=JyIJT^Q?x{+ zpP?|lYI%%!|ef__Usm615yV(4vOgK8IJaYtKeo^p0m7Krd4OBIAr%KtSP7lWp~r5jSbo;lOxhh>|?MVRDRL7 zK`x)N&ar^$<<%00^&&WUF2|;_O`Rx1K%L83!V(s}uw>Lqd5d-$Y+6=dc_`FSP}$o% zK(=}ZYzCCH)iME72G2Iit(@Ij9xkHG&dnJu=4KPA$Y`ca}O_*f-)3S}G zv~E34sE^ZdcNq`ml{j^`!3McI%6ZRFheSYwarS5`?ozd>>Q(EC^xjlWGP+H&)5fb( z7lNfSGLn4wE=!Qm@|k{KFo@H zq!nXpEdVE$Y7}?LMDwAluS?@YX^rYVE!>NnlUCTGVr*sqJaI9% z)S8d31v_c={k<)9ZYnP(e9mZ;n`DMnH+|&(V$VHvDHeUY%GrWtceyoddb}4uo_nDV z8PO?U3(A!~eCn;yBI`0e>YVArR4-)Dt9a>d4Qcf1T?H45{u*mypZXA(O=+qkNr=nn zWocr26Q!c-BCS>Jc-kZ+jzLcXMI6BOZj>pED){|lYf<(L^+&TIJ0sq6sqkeRmvYBi ze6MzRey^=uSLCOX?wb1LV5&wLWsCfqGi}(n7M1`VbD155b4l8*cN0v;;+*F$<9|~* zJ=(vV=v$WPe(o@vynVVqgl@X7TK@5(y!2!2{W9jgG-C^Ji7T?-j&N41snCn%lD)9A zQ#XU9GKGe^DZCRmgC`fLc(P3MGTiKwCrx$=lm59rGA?c0>>B@&+@rl^)rMk>Y?>QS zc7Z#lCkV@GiA$*2#hXERlKdLm)r!LLuQ62;?+%T$M&4m7l{kk)VCx<3tw$--CCwps zWieuBpL~k2+b?A;dowPAGA!Gq+NF2u@mxNR`UF5@i(B7WpXEr|Rz6{91*d@ei$_Ii zgOC5+HI|nw%Jx||EwYc6vGs8#)uUc__ObQlLziOHr*c=uM)0MB`^diaV$ig zVlzX=B8Qwda@DXel{C#iG$&CDrGgj#p_bky!hJ516n{dGF9y_zc-8BlT>`Hfg z^(`}|GI3c)BT&i<+2PL<)=1{#MZpPWzS5ZI+ch*ctybnFm}tDzcxt2R$?lxx+M_d4 z?QG&4(iCk=-c;u{y^$a%^LBSFE$yj4R)KxFv3`#1%7mw>!phk$;5$%RwEXW&_O+ zRyH0{++FTqlilHSIL+nM197IC-Jw~EHrr|ZcK76q^zkAkvO6yH#N~6}EnlQRKjBWG zEyX-L8+2%z_lB?ASB1av4B9ih^^EnNs4U$nm#kbD+3v4^YoQ;t6hMp2IbTR<8tbzs z&Gtj=#)h^SZWEk5ZcJVR*DrEC$9@eZZdcy2YcHm#+zIg0nCaz1iow@h-SE3p`I_YD zmh3dQwia9NdzHKFEYxCWw{4|)x8>id{Lv2);%--5(ISnUA?2uAa|AxQ#Ej}W`Zzj^ zBnOfdaijA4SCr~SxH>@iP#Jc4Tnc%{RExGAxkN+G?yhgE)P{D~N2>L$_1#-{ z*T*WmNA9w~Q2ZcvZxXA#L-kP#JsLhnj>Zp4=1IWYw!40=YG8=1)zVOXXi&lW&`^~&e^SlmO1*t;U~{F$H(A@) z_ytz`8qHs}=7UJPS*1xUZQ`37f!l6|Tumx?v|6k2v#nAebv*7-)%Pgh%3lrc9%@#7o9@-Lj*+TRLX_m}=FQF-0k^18_?At3>Wie+9mPF?xS(87Pr zuYi7^LYD#B{s9qz|K6&%-=aC~AF8eYEJw+|gus6xuQL3I4ChF%xBtwz-o6CvN7Ndj zzXeo@0Z>(uu)I7ZjFO1A4DGkJh`IrweTUj>NbXMyWQ@f(ip4id4iNf!=b!Td-$Pf` z`wBhsN~rgGy?s3}Nar4^y-udms>%aE z-$4a%UH)y!X_y=;QHoN#)FV5rT{|EdqlSSZK{|NH)}W#-GBAj$LawcSvFaVMg)w61 z=7CaGDipe86u6<{QJ0(cD0z3g%CcXg9~G*KtG7obLTR35P?H)Eilf#E0j4@>)Ed)( zO=jUkQ#AIojgZvQQT5A-fqG|rvzS6aro`MM$El7>{x+W zD6Dbt(dwv-3uDSFWELA~*y?|FVQURE?79iR1)=#J7Dd3XrG3 z00lIcmjF-tJ;3(jBT@tRd?4`pLv+ctfL-`eU#8Jyr8l}FX$=1vwmE?2EtZooW(S`~ z1e;HFcl?eGieVSiBm?=w5Zn1Sc%fzSgxhG>~F%WHw4q0SG* zXVN0qFgZ*X>BwtAx)?;LA7Ea|wUN$$q)DnzpD|WODKUVx-`&n|F_-h28+{ZKqj!Fq zLbMje+`thF{4`Vnij+~zpNAOGmDpdE!m^Kg`+LRbcS75)THCIAsQo<=yK|MCM4%kn z-!noeBw_tdOjca1w}2Mz75lxD7mN;&35u2Ibiitl<^a~6qZjE)rHIgx<;d!-0Z1t;^+Yx0;PP?agV>NJlBGg-7%O{*?z-zHUZw22 zEqf`7YN3F*)_SG$gvdRl2K(1-0ap(Z{m#J1%DS!oJ-u4aNPo`?Cz*xgp`oZ`JP46YWXQzKPhGY0 zYL1{Zf$00)s^fyH7LN})x4gMQs)|t&atNTrY60V zCf$+XLvBdsEg#w(cmNW>IkKuQ4{r8f2x2PmGI9K8YT&Yw(nPNFWOYbx0Q-fdjl$M8 zB}kCbmd+v}B6h>KrH`+fDEepY5D$?C*a_(Zo1-vira{G^S1SfRoA6rBILxI8n0csS zPW6)^3>_f?@O7h1T4czla-}FWO>{BapIM5+3~I9QLYqlt;D>u8Y}Sb89=^6gY0^^3GBZU~>I8DGs>YTlhhN0(%TqX<=D>rPMxgnn1 zz+4n-^jvj76T*QS{(*rwq~EABO^U5hfc#&i75Y>w;&*IC9&X?8u~j+W32bmf*0RPY ztGunsHE7<5b3sLDIzmMKrH}C;D_{IWtyU}&Ce~tv>B1@!Cgur*i9sh!40&B<2GxdT zs|i~Pie`*H3AuX!heBni==Rb+C>4Gy;kPO_zDG0sioOoyU-#u-59MD+@~_Y2U&r&W zC-Sc+^RK7#ue15r7ws$1YOm22w}E_O_`(WT(9ViHLlt^STGd=m^m6c3iN7q$Y$*A1 zLU>rir=>6iDp#+QYD}77RTtJRHWTadY*97NstP}Nri@hPsj)Bf)F^42C*i+<`f`Z4 z_o6}@&C(PXkC;M?ER)Z1OjpPrG6VM7_ClzyA=vVNj#Y;KUfi7d-e0Qnw-fXSi>-3S z9Sy$yQ*L-|-KnQp+@Jm{XluL) z^2=C@N$RBH1d?jeX7CU2Z>c4u4McF=YQGY+4!kN?r?lzs$qAD+kIX88$5&1 zuuBD74el7@t$?ZKe#31@=w^E=8C6$r(F;agvzOnw$W@VC!BrWpWVc#n4K690ez-M3aJLqKwSCEM)pnVi+N<3Z#_n2%J9kX7p62AcCPp5%3_X|9(g zVt9P+l5Pc^YF1oFgaRv+TYrI~Xz?Yi9iRB3+R5-5SAtrRedYhCWOsOek$}6rEs$R` zIXLalvE5#NmMyt*aU)$jcywk-y;+$)J2c2kz`FZXEmCOWN*=1+W)~=iN3CNWD)@mi z_%z$u+GzKC@oC9DeM%X*9mlbcXv!JnYJz5EniQfEq$0df~Kk?JpK zVxgO~u3SksA=xl02rEw0t0K`OF&pcWYAK+8R3LCJnu#yI4|p(RY1Y^~0OAYJr=2 zZ`XCb6BqS#jMRM!JZ;#*`n$9Y@LjZYPoI)0KH?>7@yh>xvxY6VF5Qy6ahX6#RZ(?e zxsXp;qD3Puw6-R?BdOqBNawBH#nvU8Pp;kKP0I^SuGzgUyrk39m$EkUA!)fEvlOg! zldA444=?^~T|(eeRV@QsjKE<;WgzzVM6;7?x2sXEEErI$)%HTsh~n!rmF#f5k2ziy zyHq2ASO8u1>#4Qdb7y%BT9bGKt;I>J=NtxA zGrV;_c~$fsmW5z*52CB)wtp6{K@&*dnMp@&-}fI4jepi?|}#J zom+14F7o~>t>#TAzo#^atPfGc_7q0m1ungPukb8LeTIn&4=+r>=@aig^rD{ewfk^1 zQOIl3rQ+OgfN#PVcO@`k^7HepDaE`iMlTv*U-ZLTX6y&c$`-SJVR?ziQC6#R{$?#G z!_!^9dW&*3aOb|OY6Nqc}VlIYV7_S;!`KOg^M{&n7 zqRRElA3JvR;DZMbJTh*N16-KqqWm+L=9g!hPfWbP*t@tmHOVV17cNX+5od4vtz{m- zxe)GP{;aO7ZcMit6LXE?0V_hy#`&v_Nj-eTvnlg)jg~=d@LG4{#p$I>jipOd4d#fm z4SVi{>8VDwpwKS8$4K5*yh;G7b01v-F>6)1+EuCc|D%Lw@DsRZ{uL-mqvLLB2xi4* z>vH?UurCIvjyDIxK#H4(xbfzeB9r?r1mLK>q11Aou?)9YvMolj zF*|X!F*m=|I6u``ZcQ~C({qif7p4}kHYWVx$g7R%C8>dFdxO9-EY0^p@qL7y@x}WM zpZCxre7`mAeM#@%AhixTe!p?j`wbssqFmX%kFd*eayEaT8h^i$!F9->5$QLS`ddw@ z_mO6JUvkp>jXs6U*%v;Z2fp}~#);X5rK^oc8(*=Huin$J7vU6PA4D*vU7k{eeSEd? zRaw>>G*jMjB9H7D%n23 zhL@uRD|}K#coIvI;d7nfk5{sf0<@`;O#l_nKa0s-f$Ic!wvs&vxCIqn09>3-@Y3ki z;uMpXmQs3S*9rb194#q$IsXJuzv~2HMEz7Hy9+qw9vU|ySpSSS*9q-fC3_A)M?Q7w zx#xH{a1KW)&0H#6FXx5c8p)A8!Yo<24TBI~CmMk@@^@$jCJ#`>IFFfLQKWsW2{bV8 z$%!w|FRFxn0D^_(5TTDXK^Tdj#upA{E-ub5FW{6CsQ3-E=Cr&}yeFynJaBWUg-8TE zb55Jb1GT6Z&;&6Lyc>uQRI=N_cxFNnW+uRL$v&4R)`Y@7jOuhV%U+br6(-+v_@Z9lS9>-Zk@Tc*NBwKN!Fn`xG zZ^7w?ttfY5mi_Lp46jCxSHy)!<;-P%a+(+C<}Mm)TPoS6rHRFhyzc5tYLQ^SZp2D7 z`*kCuoi{vK&HfZ8l92P4#kqFl*NvRIaQ?+Py}0VwEtWjGxW}h2&V2-T)PegSc!Wm* zgPVybhTZYZv)_+R`6G#~Ua!<%o^)i~VgC>h2>qH+SXi9@GMdX!P#;a>6aRJLW8yVd$!=S8Rq&*fN6*kNEXhL{ddu?t z8hHrE?-u$u?zYG>5w5k&CV)jQ(>3h2vEDW_F?Vs9?Exv)!>sTv@y=?9Tdri=3tygE~W zn@|q!m;AtPGEbT2umjIeUCqJOk_6_?dHl{KK2B%z#8siORUofso0R^EE4&r1sc>G_ zPa@Pd2$MR`iBB!gP0WN{L|E^6zAnVp#HXHAyJ-eFzc979gn9URTpv?M7nqwYFo#;|v+_S`I$T=>8ne?EFWJKX)RhI1>4mBH2HU@l zP*(my$^Lw1YG!)k{PfK9($$YMZXP&r;1Qjc!Ip2#pVzdn#fAx{2vd!jdHFXQJUPG6 zhuViek0DPkv+0#@g89XZjn)!2{;bV0v|Y+JY?$rO7L4}~Chy~ZJpbMagc~)#k%gkS z{|1QrH&8{cE?ei+r{kQPosXeWCH z{hQ3caF_qmq+9vd91ShF_3@ec^Aj_{8FxFpdYB3A8O|0=U$}aVW1Y*3Q{!_N=2?BI z4Tpu7@#gMu5X0ThoWgi={>7=q?)}csoMj-!5Ly(=v#l9V;NsNmJV!&>7pal^luiIN zUc59td8yHyXZcdQCR@u33-gO`mqu|%puxf)#wC{JFWvfe0N@(DzaawM79FJWjLJDV zce!zSnVq8R!15Q&qQ>?3S8a9S@Im)u9Gh60ykzdxiJ9V5aewc{eJF~k3TEap#n|bJ zhO={jEKk#>7bh0y%t&J|`|LbNoag5Z^Y;rLtCDf$zxkC|uLmR(!8kQ}zyfBTfTzez z*U4b}iuv85ecw~-J@GQLXJGXaRC}@S$Ng`6v5OIOewGvGSC~#@*~)+SUUKzsMXu)D zFG@$_99y{;=g)JP;~v!2RKn%0#{48__cbO;0O+?lJJ6Bx`l>wkODEtZ>=9kG?zPmPKUJ2e zRYtdd>H2s@Tvik8g8QvrM9JJXMnL!ZB^QV3vS>xEdNG(F>E^jNol1O!i{8~JU1UG7 z$?9DBv9SNoiWL@WU%1s>@i&Ckzto@a>d&<3$Yp}Dt=x$Imf)v1uRHNJTBCJ4Sm&P$ z(05e)Wt9{Cb>;MkeN$U;$9YJ-q26h~vomHUPku*tEEFTSnA`IunRJz0C_75JD4~n* zM(JX8T4zipbj@pe12&z~`2v61U$ovjNP%b76kTeq2E3tY%uU@59lqL4ebF9nxz4|8 z-hN}KC4E!%Lf{&^su~2o8gjSy3UvFA74%!Eg}~A3rfuG7x*yRmjSj3HgVmgPo*a;} z%i8Wy6Sv-Bm{C5xHQ6|#{hHGJ2FNrqT&Zm3;9}3)yw&rHtxGpZ=Lj5uj!LWM-4J7l zjEu|OlH8zd7e|Y+^Q*?y`iH8UHtSa+-u|Zk{EKZ#rPm^9xwUf#@uf?SMHah}INaBJ z3t>q`yrtM1am~rCON|FMjkz>(Q!rQ5lN}lH9#ubHtp>)iONf*2?+8TOt``Qw&g&{^ zmnd7j-8Sr%a6@Z^v7-Big<*Q5a1z!Xi-sD~Lowj4{M^Y314AUgaCZ*?)T9vPqja-w zY;ifZdRr`2$_!lflCDlo@8P{YX70H%_G2-{ZXGQyjtv*h0xHIR;$}IMyJq)$-Zig_ zwHrAdpPctSIFz$qepRe@6w-of^4w4sEhD9v8Dok(k*B4F44U57=cu6+%)Ez`K65#mm@Isuxp)xS}r=e zr?Z+v?Ygxyhnn2X8Mxi<)u-~m8`2Oi}9!8%5!JM;OQ9<)jX>|e}X38$G?LLyy42# z?yyvN$Z=D*672kQ4Ou)Uproz(GpaxP^rtC(QhHoLY*2nup*xG&qYLxF*}BxKOyNyqA?X!m>jC z!iKjD^JZY6k<~UdDwP@^L;B!PRv+HTm;P3{JbJJ(z&EcA5#ra9b`3VF^-(M-Li(Fk zYCCG18(dC`%98y>G(TG#m?*?xOVvlM;3)M}29;5-JW-2%sLtb-&fg4sjq+%=zZz1N zD}Pl#PIZ;~uKMxKjg9tOb99F4LzJrV>=Fpnw(uvKcv~|Nhyl0t!?uHyG0-^qHahhBeTJD(fEEA!>}s`$~j6lf#{{9uu$a zMOFvFOP0s>9jaE(Knd`kY{!lY<4O^((uk_MVANBT$J2+lWt5aQyvDyHyRD$T2&E7S z_DB@foF<1M{2j;bI~I6Tf7?%66gIE5-rtWBy#-XvNiS80#H57z*kBP=742-b{-{=g zAROrSZCgDjW-X?-!N2D4{b$AMT=}M>;Tc}_46hD1s)E9Q7QqW1^etikSm70oYeQKD z6J0qV2R}JIG49`t0UO_Az~BK{kTCJVh`)j_dmpqVOM+<${DmE9Bvhq~Yn1a3x}N?g zE8VIejKSJ2@+wtbp?iaEdhkK$(-TSBqZX@YjV>Eo*o#$=aG#Orvi<$i)&r3dEE_0K z>w{SOk;}vz70hNYuNlV~?Wd zn6wD^n8LloLCCY&cm+m=>dI)EkbrZ#8V9n~9b6MF%CG#mW^A$YVJzFL2 z1;|^_U`VfV4S|-B--lwU)xRW(-sqh9GV?HFftQ^$FOR5er6;hK>?V1M1R?RB_+BAi z3X9q&^nwapXN&`=e<(X~Z(HHFt4NQd>-S4Ocyg$DvcMH{#sg;OTkG5 z_>PTP6>aMJ{Dyb(cTmgXUtzJs*h%)a9%}*v3lbKQWM_u-r}!u+7IA(>zkDySojUX+Zt2^lV}RG@(P4yvaCG(41*7|<{G1F2G>0u zbUGuZ>2g^flFc`RY{JK4!|g&{OsxPP&Y-B*KlZAFvVPWt;92TcXRf^BuwDT;^wHFO z*czv=d|>SCa?$q5dhs0w%+BoQ49q&Syq5L~R1op7Lo)b{x2YsDyj`e%dD3F_JCqGT zRl*vtm#i0;;;6ge7-*JnBwZ4g;;^N9lL()b0G61yMIej|3oId>m3q?xU@ShjCuw-9 zN~P}_6%c#LF{`4FJAXnnNO*c`)gz&Q^NLz;w3e1E0prol#2kP?B@wYr8VKsGI8`ENyvXbeCy{ zvisrMZBguAqw+NER&_^rBr(A=rnWnuR>7!N?6kFkw12;dLC?E`C5ayYheY$G*R^?NHAfUaI*qj}e8n;B(8 zWYe=Ipbw#xLMK~9R6WpRSse`twWyyg5(ETVMGDR76CE&`35=ANS@sOQ}`% za1gyFlD)b^^9Wgh{P#^k-=X%3=V*+%XGRR|G^Q5ISb>0#IbZ@l_Pq^48x(PuNM@J> zG@K*QT0b|z`nff9<>yj_2)SM@0&^RV_=W}PvCn{$z2kU?1xsF;JejT#x0VytR*g-i z*j(Odr(uX4o{v6FVQi#AYf5P}KGGfC-<5Q?vL!wu%JHiR#%3JDc|mBSiuUrbV6YTu-XN22mml2K`Ek=j0j zYWMPJ-J;(@R#J>^U38As$~GLej*XUY(A&X}Rrdy_ZvxR~xsBbAWfm7a5`sX22H!Cz zkaC1yix7Ak@Dd@P4}aeCe(qHZX{?w5dKDm_#iFn!wjYedt4bpDz( z8XLwUOXjFH?=W^s-DMGNOdW4#nn;w&rcq{o{A9O~!{`A41^{UTM$LOhGt7-7)`TV| zTCWr@1WADkKP#R`V~OGskgi+7nReN@hWU@n*=4v%bAKlMQhG0K9L&($KGw_3AQyjO zG5G40TK%Ql`O+u<4F|=_OERW&rU7pJ^EURH$j%5pYBm(@I3k79K4O)$kAQA5=O_1=#qs^Ty+ew%uzf?y*k2ODX2W&JpNjtAYN%R**-LWH?KPV2#i=0s zobiwmZYMfMv+WTxGt(!dZA#u482?L%}lS*&XXzy|S}KaKbw~jByl+ zkM3BXU&CI@@wsN6A3T=yveXdyrQm#(EYAh5aDvPY?A5*2hu;8w98a(j1Y{6sk_g3{ zGA3;C1>6NG8asl}`I*&%X7aSsC}T-HSKa{LWx490)@4|cw(z=7fENAIq>?;sWmax@ zQF@^o`aB=m)uW+k>{2g!-9!oz(wq;b2A=v2|018YG4UVKBQO0Y%eT9}dk|(F9(n1f zavqJ2j^RwJ43M{48yb?8!Q=5_0Q@u!f(;$;O;8&JVo~x$T(cXYS~Q ze2@um9*w~jY#_g(PwFCvP1HtKzD31&q`pNdl!leH8QjY2gV~0fFovL&&7!W<6%oT(8U)X>I)kcl_s` zFMFAtNwvuBVCwB}1L^B`825K`xwRDa281sLjP~Vgy&c^yfY+G1sVZ zX!{*swa_@v`16j~wf#1XOMme7jyPn@m|6Tv?0o_LN#G@i)P*?hKp*0OthC-EQQwQh`09n%H3}c#fn?^bf|Ue?crThRY6_Izc;V zTIVj&uk$hRu6OpC1<|>GEPa-;BQO26HQ+L{-+E^mjvINoB3BSILjtvtmp4XZ6Xo7PXsbigUg(3N z0UPbC3k}Ba8QXS#uip7R3x5rwFz^1F5bb=8%EXZ!?5ob#fJq0wUGMxpyDczutIXT) z3jURC8ZTJHlf5#kadf2fFHPm4e&vWYvlgQR=#q3gW^AKuA!9|i0RMXAwylWlys=$9^nGP$UzViYVGQEyE&exH{d=FS*r;ex zslU8iFgibyR_XkiMRoqnOdH;T)>!M`e{SDK5^;c)xlZnS=WSCl*74qzuVa{4$~noR zn9PPrl$um+7%@dF7o=1ViA}SiJ(Ul*Al;B;cC~ny5tXT-yQOF>Q zhc~dkJ`93&Tsjy$D$#z!JDRCZ{*8vZJZ81G-8|^()Mz7kqW|@-KwGJW8pCMz=FKB7 z-%gh>qXJ}gFxLHPez73s>r~hcs6z5?xMt;jP$np16DB3?cO;!dn=>DNQIwvE z*`G4e3;uLxLu1}ftepz$5{6l2kTTHi_tUjUk&SB6MYHzzPgIW$fk)GYNO6R)8yb+i zt1|NPUjO-+|Lof$<74FIgCj5B&#EES$Y3Sg!vl9`HU154xSJ*M!Mj0^MQa6)Z{*L) zUy)X|uWxJ|BK*@0{5=7kEYCedUVx=pGVju#(*}b9-U_`jtPIk=yEf~~mu6$B8O!q> z`}?N;wx8DD&Q>bjX5nj=_D%bKmA_g%)Zag~n9KI}OZNBWSQ85gyra z)prcdrHI2qUhXxOYabP~l^^G^hC%0b!`?k}I|M0pnt7Ue#;owqt?HlXo7o{~oK{5V zb;!dQ&7X??7**SQY3gXe1r;ICVe(_eN3G;H1_qg84zh1y5KCsmAgp-rAd`4{$}nNE zzb2y)eXPlcZ3!SQ{$kLNZXVo77Cx})1~*lQs^qP09z@b;3A-3}4{p$$z&=OrvS0r6 zjU8B+ItU4xqh1%6>`k~W73$i_iH58XYuxjw47iBiCs$~{kPW&GU)ER0B1 zIaC|mU=;-AN@f-Zr3}SEtqxH}xpqw5X-8Q(vbk_IH^Raty&_gQM zxupMAepoHXvQE`h?B>A%)rP+NemMi{A2Z4@L=D=A@%($pl^)+FP&e=ti3bt_^1zJi;JX%v% zv%^t8?SBRxY)gX>c-m54A!VROM$rNL+8e>X8gIRdOS2oAuL$BmZDbIMmMewPR8^xt zfVa=uaM}KT$!HO%BR5-n8a$!I6^0klLP?MVxf|VXBw7KAkl&f@vhd3m&bKh!F*wKo zFFqgj}nX#EyZWUVN)NF_T7u2@{8Rv6pFnP0fel|2Qxcf>7^#Qvq>Bz4I?%N3l-lRahRk z0|ND?Q34IQ^5)ne?xVM%9NGEH4*sxB?s}c&6bT^7d{)&;GV&PRf2ki39btciijWIR-oGKza3WU9gRM&L(e3OCb+xcFtwsmk*ZFG#ut@MxjRDJO_ zApL{CqMy1~Y#^1~IVT0Ay$N?nPxw}|?q3bM>^L(XI&C^}n`5cfFjH}eE~B#Qd6o|p z8`*96Yd>p3Yc`ju1V#<4zxc_qfIy-SgNhU?O1HBM&g#sLFkES80EbxcIn@{H3+Mxl ze3}%|ge5EoP*(5AgG*fXR>{DVE&@^rc!rUZ_9Y{8EXfEW%Lp;4q1>z=*segi5i8Bd zkd&=r24yiYB&lbXtRbMcAkONvZ)ebMzwPyl;|p-(wLg zE|Fhle*!w(>>wXqkYzR6Yls3kk*;R0-x(Ndh=^JpOn`-?J)M>@A z$DpN`e=F}JdPbcX_Y`2M$q0x+LPY?khk0X?&S{ucU@Q1!6FIM;*20=T5|QLiz))dS z9`(+TB}pKL(8UhvifEc>Sq?l?bVm@udK3N5fizmw`H6Gw_l%C;5m7q-*?|5Gb+&LozdQBQKr^0pI82%F*scB`~@GeF?dw!?SE_hXxe5AfHB!M_*_FSHG^?Z zXQ^K8HVT9vYEs7WI*Oz7my$CwxPfS$rm-Gd?+>gaUxNxjO6PJWv`*UU9rV9T1>6I> zTXarv3i{C$qrKrEk&pOP?~%xd$(TwzmYQ4TAsTA&E3c|xEGcTP{Jb zAP72hs)`7el^tCVDo|Mo%Dv6*;Sd3Cv&-XPCq zlTOKbIQXI#C6#V>rpVKcjIYZ4&;aHnyLOv8#o3|$k}oZYAKKUUSYIbdlyx1WU+f+- zH*DMrY&Rj4(me)b5ItWCs?G!Ev?Il^OsWwTUfSA^3b2GXn!o7H`b$_#LH!yLO$h7| z53^inQw@r>>L&)bfYA+UI8(sk$WQkU=}-*08F60~5fo(5xNNAnJw$=cNNCKQG-!amGxo z7Y%6|B!#n4+HB*HS+zJ46&|R+G{WkXfxhK7aDxdm5>B?e(kOKmOoEi2AZvCYrJ~>wE z(BP(_A+3rU**-ILtNPN$Xg6;R7&9AVm*d_UUj&xwFxuRJrZ#!R5yqE~CFf|5I`;0c zD8uxfIuIn{?lGUarb+}3q=Ea=z(Z-^NE-M|8aSQ?o=5{vrh%u^z}YnLMGr_P)a9!X z4fnz#XsuBn5fzd>j6F|YYnA=l^qApX?eJ;1=C=1j(7wmMP#V3P|2h8QnvT5~21#2e zJWgK$x=(Qh9d3hjZ7c|$MMs=Drx3q}m4i2s4;#H~`iT+kd4f@>`2rIb{q1}s4oor) zem1tzB7f%D8J`TKg{1$Iko)tb8aHCGn<7(7py1C?EgJt!olv!ZqfgjI5l4F1T(cwo zV-HFd2SEGxVLIBI*>NW=uTa6G)-lBR<_2PDF-c^dCF`KcgMKxM^55@=-yenFABKEd zP9>i??SV65a1^2C!)^SY+#txT8xHWF6z++@xPpGrOXKZ!DBt3Dm^`NqjK02|f8B_n zZ52VKuabM{fBfAqe(v`Be)6@Uga6Y*pZnGWPu)NCr=NcAzaII~=0~!7zPwcX{Wm}U z)c^3WZ@Koy_qJ}(U8`08eg41ySDzT(&d2{#Ih@_X@h(2O4>bGV`28nj_&+yg+2gD< z{3F88P>Scev;Ui4<@-nclAMk5Z5*PUXfB9A({oBGnKxCif-*55nd;H^J zRNgYs|2AdEX8zkh+LV3$kM#GC{^(Em`)~d~=H5N7s%q>1pL4FYaHD9bsEDX2sAOJH z5kc{e<^>hg6crN{)6GQ?Hwpp;UdRef(oD+|6%!Se%F0ZOjEv08jLOnXIvz5kvZAu0 z`i(K>V6#1*p6_{{=lgs8e*Q6>@A(;Xj4|iB?6uckn@#r2(-)xq4fX#(Jz_hmw~=kX z9o4|?!Q0)$o!fEj6tXkP&I6yJB6&N;U$H$8e4eEK7s%#hqW?S8K1XpbfNdhJu(=t4IURX7`LRw1_Y$QF>@O!g`AETQ%Z zYM-X|TFU-0wf{l=*UA1w*4pvt)6GP4vaNOm>}V!>lN|`Qg&4hKF4$E&9*6$J)c-o! z_rbb}tC?58{sd#)#B&K4=g%D;JDZ8lJ3V%~i9S2Af8N@V-^ez5 z20gogZ7f2b!IUr&uy^fw^y#j`e^1aJH!%p>oy8bZqez_vJ#Jz?*k;1MCwWgZ zv7T(~5^O(3?E_?=2isk|vggd6#^RejbEhi__YyPL>@MylUM@!a4%yGab`@V2tG!)?*WS*1 zJBvYkFFn&$jNTixr>ltCi~X~~b{0{4)LtL4a4+sTnPA<-n!PyR&E&HWteZFrJ&ncd zd;i$pOuW1I!rsQ>r@grHZu@Y{dhWw@?gSRLEcC!O-B$;5pT2MLzGmY7eRy;{NOm*W zq2if+LJSozg6$>B_hHSSf^`!&_TgS6(b+pL7LIQpo7gs)Ikq8qU{I$ceM}?9DIC72l3RwV+T8k^I+Y? zb@FsOgfV>%`R{5idVzHlLk?lg>4!SQR!ce*4ek4(-BoNPyBBN=@yemcVP5ZnXJc{g z5Z3lPwB1C@Qp_GeHiYcxQk=^aXm=O)mY#uq@;BMbx+eV|EY|h#pvp6d>*<@-Dz}y~RSJ0P^fF97I7x zgG4S-IC%~i4-4t%DaL{Jlo%r(r&{I|EGiH{Tgh{6=AwfBK>LW&KtsY;T2HZxXp%T7 za;2wulrl^fmc+U5Cb~JTW93^^&=@lCH3XlNpIA!>Nwbv-hWuiC5o8E>XNq@5Ahh$aBdR_}># z74BIJfx_fD@iS=-^85&B2V`0WB*f?9c0@R*2 z^GSQ2w4a2X=or!OBFn&-r-0yET|7wI2c-Qe@=2>Et*v~EwBJcQQA+ir?kB}B3 z`wrZm&RFJ@aUf<*ATS?DwA>Y zNuof~7RaZG!iW~iJw#)HD&Wj>!o%H?^Y5TcXv5>X^k zg}g#EkLZ-VMwCu;T3#nwPjp89OjJa4R@M<6B{~NWlEV^+vqY8fCvmXqR!?Fr9|9S$ z#hR}{`WU`9qJZuo?YwM3G=S)WY)dqm=#unvGa$-bqAJ;qD23=t*_&t$(O0q$(Gx^p z%YH-$h-zeiqEkda%fW6IWWPjoLk=bSk*H4INuFXG*7>K5Bx*|3NZn0y@FTLR=|p!B zHC8d?Ihe>p%_W*d)LO+8EgrxW~vgRP2@RC9VF7TjZvjU z#iY$v&k-FZva45!J|tSE%89-rTCPqK{Xvwj-XijR8doAyy-nm#l&#(&8bEZPdY5Pd z(Hiv;k=}dOsY|rhE8tagja;uTlcu-PBkBtxUG)Za#YHPn)h^nj>MGG3%CJ#gBhvfC zW9l3Ddsad$Ma|f(zH^hJhtWG$icw_ek-eX6vC$X$Unl!1_1qxa*+l;cvZ-c&XzK{B zV|D)qGc2}~C^QGhPR`j!{zu84Bm1>^CyaHoCW7r~-34}pg^|aRy_>A7j~@Gf_UR-R zz}QYgXJ10?75@^gxp=^u9@|_TwC)A_p|uQbopnE0f4BQ%r`%NSIJfmsm1`wfZ67h; zt>9n!6Wy@>bzqx|J#Jf|t*bpl{jL!%P)wZBWcVvlXpKf_+k?F|myR|$rz`mkAv=Za zJhJIvrFe*JA=u_(f1{mXUuv`m>|2fYgL<2bnL%oOmK|QVAF>(;u(e7_S_C0Rz!K%i&uc!8> zpk}!L6YQhz*T5ch{|>h4Irmyl)4xHOb zoM;>j_Dth2u$LQ;fJ$l_PXgPr$vp5J*aY{z(M?uSh56t!waGS!8QY`?+KZYLgI&_( z0Q96c!8n_mJO_4L6O3Qn+T;_k zx}M*fT!xr#9=OhJJ!&X>9rOfvnDeCQ>(LDCM30v9Cg&u2;2yHV!+TzHvC|^}dQL%) z6qRHzll_Y9Ut~KrMRhdUd1Q0Q?f{EhA!J@3aiZxkupfc#Bx;&A1ABwi2dq($<)AeK z?1xqo*gbB!pf+i?4(ica&Y7E`t#fs1);qS37})Fy@EP4~JM>R!hH7#%oYy1N-cRj! zz*CBvX3vA#_%>|ce%pE&JN7nznDrFuNhXVXQT^7>zwOApX*mUCw}RE%SBflk9NLeN z-9h$6vU=UN--dhO>tt~arTBs(W0uJ|e}bKo)7%qls6QgG=K3SzEogW3M0?cs9@v#+ zpC+g&O0t<`?viS?`IVKBBm*@4aRHDX5d zA<(mg+J8SIj69l-_eYvzyQFzB?4<{-;ZW^|)?})D?tH!fNO(twTjUF}zmjcfU=JRT zeZ-s1mqJFpE%bKL+vwxw*%0S?^Oa!Rw0IiqkQPV5E@*KIY<`O`z&_ui7VM`jY;o|1 z3tF~|t3N8cw+w{#COCej2x*Dy7SXb29QE9qgMe7eUV+w{)-%SdYX_&Uv4* zdNyJbdUI^IX>k&&YVLIo zte+Row5!)eXbaRV%Em{-_LI z&|GwH6$bXMR^!30XcZ6k(-yf4CgCIb0c?JY z4)@~zFahkTRMVBvtWH%WB-WOy7D)mr)86O!PZ$-VEx_XLae9F!agFtbtF7@IWwt@<3pPabAUl}sB(RfnV#%h0t>5b&Xk&+%MQs)@oSbt2`um9THv3_A zd)!hXd!4lctiK!D=Uc1=d#Y6-*iTy=2Kz&+GN|OEHu$Pn-R2aue{ORIteZFXwD88B zuG9{t_Bd)!qjmzdm%Fq#QCpAQMeP^J=K{5Vptf6EjM=&^#_vk)5NeO5wvIU)+AZah zw&%ce4XKYpPyJQs_O=ak*VjC{=P>nuaEm&F&SSr{{TL#cK6oa)4J_PO_c7$^z8wZuTMA5m4G`HX^gtq&e||1Q@Ah|{dyG_bzy zW`XV94zmnw7YprC?c%^jwOa%c?rxU^Hl-cL(bs1A?eMkWm$rDFcJm6P!>EJpvcVoD z`%b&wureRDdjNXAgi#njAENz2t9t9$-^kze#ZkAB_3_Pzv0capkqz*{Ea9XMCp+GE z^E_OWjogp%w?G!x_}YI8`Oo%!3OwV;rclo+vYUOk#<__dzPL3@sQnVPPx$Tx|F?Xz zp?pT2MfBHY?vgqUr}2DZOO zYIlee-2OeNIh1-L+OKzBt4wJB?V@Qp(O}_osP?!Hy7B}b`=c{*YI|J&f43EGfKN{Q zKfpfHUfJEm;}oZm?BVvfRwrOA&a@GzXQ^MW;Rn?Iti31nW6!^f|9$&wFt(1YM~7D6 z*@~=xhYrvV>Cg>qWCx5ti)==RKKB1koHZSA{Wnw0104oIv^P791AC65RdtvGZC#<> z`rmYD1~byDbfW{Tq;Ty~9b+gv9^-$vKiE2A##SBgB~Lt}T|GN>#I4YcY~PMaki9?I z!88i@4ZOCmgkxtM`RHSOaz|YGXtHr+<2$Z^<2SWq$hX7PU;PL2E2TXI)#Ow~k@{uIUgw@&NO z(@7ic%|xXpTKVRpmI+tBxqy%UXkBo1TL?cUT-_F;2NSMtOA*P0tJ_jUGhx+UBAp4V z_7bZ#QPr))6-`i?!`@2VaMF|Z*21exebqbd-Xc&FtVprFt=QC!G;v$!QhPh$9f-6U z*5^gLFE-)dNozpoWA^qMfJSzH%Y~-HxDHNjUgtCRj!trPzF_wgH54-sDyy=066WnF zS4)i9Ndz!q%ueDk6UOW;V!LZkjOi~T@6d!XX>x!ko38HsPdiPA=+VyKG>}!;xys&E z6w$amTXE+UpmL_?J3D~RGQA2~H*uZm6lmQ<9Z|JAW`J+o_Mq(Um?2OE66M)G>io0) zb}@!1*Y-u{-+`vG_HAbw-(5IZGyNL@D3Jpp<06sZQ=(Ahm$(Shlye$oo|S!(VFn_ud)vj<^3>buJHHo z9zR6*1#1fO?-M^vL=okQq5cm0aN#uowE}UM|A6=rB93Xce|Y>zv6d;(e@uLYC}zs^ zzdL@ExXAR7|E&1Y!WOEd>;k$|1QY3faEzGBgljfN#4;W8Ul>1D1{Ml zlrZ6%jT1-M6ZgUK;yjUFrSaklYq$?a3NwsmYr}moQg{;K(bz10f=JN>=dP6aNy1|w zdg>LNBw_{;(H=5MWHDj(Nn$OL-bPWPm`G=j5{Fs4?4M$vEGn65VRa{q%S^U_6#HF5 zgky$0TdM#EkcTGPd!`6KB3;W=v1KrN7KqLPE8?e$(@fz3_XE{w@*WqE7k{@1AA+8G z&F&U4Ojz~ZViA$9dYV|vg!}b0v59GNz{2?H;xNv6IDG! z*oIPeI;Lle#9zG(vxeI*MqFVHw_l97t~J_zvqjW! z0sr(W%@O%TtAst^q4+tXZUk!O(h*P)KUbuT)O3HqI{SQ4MpOW=A@|2G5Z8%tpK#dk z6?IP98V^5!OaD$beJmiuZg)~qK(-4#A5a({@6?WiX9K+po=&np3wS0zq23e7VNZ0S zt?@}t`bR*OJ=ukxiFYN3J;g=a8o$Iz-#~_?F66K~Txe^2nv-q>?2ljWLeIu$2(Jk2 zsCLKMW{F59oNbnfX2SVpiw#UTzihFM30LAiaZHoM+1@8AoaC_IFDjk1H9kjNX2RL7 z6g5tICVr*3;UtHBl`uz9W%3^ZTjN&?PbQpgp73*$!~THi;iRqcYeg0l&h{a(!AZ}? z=ZnD67*oG8KO%x|qG3*Q*dGxknna^6N8&dK-#cj>&^r60qLQgqm$LXxV(l2M1pqxR zYMJ^1Jt0cRqLwR$ba^ZODN)N5+2ve(p;$dmYtdaU#P1NbM5|zrtg`PEVmxXaZAo1& z$L|!;Ou1dEfzp}Qb@>`-HPhBE-^cF~hnZd`y2A7+kyoUS`3q45lTTM1=U@saDq@;R zRLPV^WKPggHW39gl@i4$*6ua+s8C}5(FIfG~_QyS4~rVT`gnf4J~ zVS0ziYm$!gHBkgpqd=M;Q%|BIrm;koOpA!jC>>=zQ7}^pQ5@3;M4OoEh{~B--%j&m z3L^@btYbzKO=Vg}w3=xv(P5_JL|2%qh`iwC1NRBDJ7$Pr@+WdIjUXywnoU&6ltpAt z(NVS%1v9-w6vy-l(I%$fh{~CK?x6WGg%AZy)iLK2O=Vg`w3=xz(P5^OL|2$TBl5ai zN2w)>VDj!k+mC4kQ4!M|qDrRwh|Fm^$}XZ{rgw` zadtXJ*(`eXG!mW>9?_Zx_jFI#BMN6A<=LWodM4}@HB7NRTLF2@)LK%{4nW>aD|!Yd z>=U6(c|CX9_lXEi;?bT}_Wh!kXtQ{#XU~NFA|{5Sh@CzAC6tK6n&`}MKpZ2|XNChJ zdNz7)wBecIfJkS;Gs6L~n(2j}-^U*mhnYSly2A7ek=H#sigz#cj9>~Qaxl#&Dq_kd zs$?oAGUs5F0`Xq2u!MsmkVw~gNR%^SorlB~Cam+2xXy%imI}XET?^J(Dx#RM&Qg)X zgmspRQYNgkR9t4lI!lG;Tmk=JU5JJ;VV$KSg-F*~if-`lWYe#DrP!a<0Q73-Aqfq1 z{U+_VUL##Jx8APQy!Ut)t^G~Z<0cx^dtrP-27H&?K+(NjDefj(aucoSJ=rDS!@XxU zko8#adx195Z1Ze5zvskZrYCwkfXbNg-Qe@$G84WVd|uROqP_00sAUbW2M-IcdALye zJ=bC3$Fu_~dqE6iDuE0yh)7Koev{yt7(|OQd5sR35 zfObr*W%?4dV`39ibI^{9!%RPec3hMZRlDQ1eN|j$O$8+Y)v$(J`ZeJZrz^wzA+HH< zqH1^C?qwpBwT?j!pa|A*FL+(Vv33V&uZtAcaGy9KHn0{7+6l3ZX+`g(gg3-7rr|-$ zfGUWp-0un+l29&e3$T_d_q3q#Kx3G;5Upi;m8gR0O3+B~^u1S)Yt#oRh3WP_lY!0? zRm;hJa)4}fQLV4K-V~lh1!88ObqQ~ZsYDya+CGmZoD^A3+MV#0ILn?}`n;I%wg^~+ z`3mS=&)Xu1XtOBka}=}~rkDG?mT*dBF`emiHsQ1=bJC@RcSRjhf%v3PZNhsP5dNKP zdaX}g!dVSKzxGjy4P@)CQr{(~vbkajW zA3A9l&__->3iPp)DuK>BsRrnida@ez3rzgfg@O_35)S z>&a>t9F+K-lX?e7CH~-~(V+e0q}jpK6K~X0Q+x3EgkPPM6`Wyjp!GM=_TYOGe-*y; z77+K{c|cK`Z2N+f6YE3@QLgR9;548t)=mVwC;lO}vG#s&7Em#3mxEU&{wXS0`z826 zph}|EbdLN>+#o7OoMO40*8izQDTA1jK{I49(<&fShB9sLzbDa>dSS|~L;VjXy2)_T)`-{pzm(WW zt|clEXZx2W+GHhDRsXjW-DRzgV#BH%OV7oWU0{aBGLQ*lHjzu{m&*g zm1Z)I)8`5NMkf9{X}O1dme@e;Lars=COw>&%G%gjSYlH zV*@<~Rr}O?T2F`gB{k5&kgiF-PVHbw@1*uldI_|SPC6NKA>PkP=RzFzPEPtPBrK`3 zldb~!JLyKqk@x^7i2*~Bx;n{yz^J5dPHGDjD9iP1VFexb+odfPR|)Pqz#O{EKqj1R zcR7^_XWL!I5}lM-=N)n_6K3xrYlw11zyOE6mt3?IW1f^ahu$*4q3LO$AQ|B#zob6$ z2otVSKY7_ny_14vN}3*rs}v&Pi!&4xXEZ>@5>-o_QK&r38m>~9yvT&BG*Fs!o8_d$ zbr~dk5UmzN2V95`mx)BVA_}S=EOUqoMC^b8@k8V>BHYg+lZMJ#?FlqDX}I)F$6BhT zW5DX9Q8JdOO0FER4k$-!?uQ1fw2zWSOs@@i6tr@tGXu5&UDhNo40syIGegI`FkpYu zXt|oHu=C-hJEi9etrd1Yo-{@V5#@?62b3p`mC;Op4tN)+i0Q(BOG)EoOeT&i5FVjl zB}K|jOog3qBu$iMPBIrq$r>i_&}NJ8lF?auTrZ%hvXChx)MxQDStkWc(42@qrM`qoJ+4ID%&??`C|Cd{5GZ!lr@ENNRsb9cuXWy>B+IHPPCt_fDYbn$(15ff&=UzRapow?Fu zwO&E2bEOO>g0n_g_2N}>swTQJUhSO2(y$*FH&AZaZ;RJBwF?7`~+9f|Mz1HZ;FyDF^#f14Dk!zVS!v=Z8Nyn241#1e zxoAA_p5*QF22<;SiOGdB?m^V5WtV|z$vfnRhmbalo&&R!cgY(};RExMi)7@(s1=B* z1M`!2%XA{l=deE`H#n&vd5;wN=y{UPJ$q#U(P|sMd)X@^h;nWC?q#oxVoDwOWb!_l z!nA7O4xlWelXRBaFH4#5%(q`&VZt+CiA-OI`Bs5vaq1E>XPgVw4C!aV_5%Jd!3%QCb8wF2?`z)zBo$_+%hqWPfe zqTG*fq@vq5Jq5^T}pg)u0dt9u=4QiC~x~yX@ zZBXl!6Vi7Rj?;JY-;jYsxDs7b-jG?EYZl4uP1*Z1BnWR|6qH{r?Qr5z~GFO3o@Wkd)_rTFXf_) zCMpo~2j>H2F=Y>aD&;e|jp^~h2U9M|W18sf{kgnI1alw!TFU2A>=5t|=~T*P8RMk$ zDPPDVJ5ehTZw~${$Bu(_nV{(ROH_qs2gg!ecb2=}Z8!hNuT zejD6$$(J&iVphBBC{u~_zWb$&WesCql{u`P>fC0@RhiEkX81~$u!j5YSMmsJnD3fA z&l>LY*W?w}F#FfiEW#P(iIzhg_OE3MQGxIt(tb&etRdPcdJhR)@~sTtO`fo&tL)ch z6wzk*l)Uef>(aqAW=PnQ?`0*^L3q;nCRZA>YQjCOqU6s9&I!vVRU^P+|PHo+iM(QGw&R|nL z_Tso|i5Z%x5+b;c9I|W4ZK{?iS8NzkvcyxN9~=QqcMO5&tekWRR>?`$kS<4-w5->x z<3nCw;^n0GhEyzRRZmSX4;hls+DYFHSs33yYN)H$d?;$Z`!IX8yB-%#lxGVV+Ga^> zHHI~e(nc*}t>4fDAO~w0(_3v~Z8VJYR)wr#hPJAVwYyHQ5#oO|ko`ElgQM9Y94)8-{+qq`fL<+A;K>Kxdhr zgK-_yb*6F{*Fn`WeFRl^RK5qOGTT>Bbw?GbiS`0NXPtFJYnJ$_AksG5#IT>2_^T+Q zYKfY^n(8vHtBN7gdrw!j$f?yW>8jG5WTpnH5>2+I!&2;bsMAEbHs4_mp!2NV0a_1r zgSA1R^-$(PT{Yg_>Zt;V^nH|`Du`+PFyF*pDurntP;XVrlm!%|t}{If)JFv!!kD?X z{Xl(H3R5{yKUKy?EwE-yQ$y4kC$&lqRk2R$oH|IE&!T6wJKk>{ zsyvzSe(Nyh%Y^q^hpRv)yx%%P1vBA2)sbo#6W&veP?1b{FLsoQX2N^1qg5OeUUl86 zRx{yM*PUtu6W$vgqe_W#Z7qjq0Ucuv@3)Rs7g-AgZLGRVlxM@MwQgTXWszfI}6hA@b6P=We`UNFUQXbD^Or*fXyVNkE zTv6D$Z|YQ4Otcy(Jaw9iJWSa|`0%vE>8hM*JW#Z{L6j$2L%tcx_XYGsdMJLT8s?;+ z#26JvbW*nNH#~8Uy5iK*66Y)P#rko9iE%1`2t7j+?^Q8G<#IaYv#Uy?T(Jl!UfEvK zl{towOifVXMCCFUC{d*_tpiF@MeO+`&|-C-Y2Wa1smb-nU(=(*Q|u}AWW6zbN@|KS zk5FZ{cS#Fix;Px3zEMF;cvLM>QA~JLEm1KFO$L z_*$HnX&rQu#iLAW~TBb@T zs&>cEKC{#o_WZf`=Zmvc5o`EKXtpY6?L27N>MU#cx#)fBI%{8ocAu(a4L?b{U-`bG z>%>pe?pJ|C)$aI7T80r>E;AAE>U1v%j*=Ff( z6>wU6PVUnlsGRBkkpWAeQCaV5ZPUo^OZTWPXEYrg*=K378uOl}qaz0|-KQK(=SD^> z-LH-?eLgaBX^E;~sv9|V=|Saj7RTk;nnWy&KcspvwTnmq3T48p?NSv@q_4J1RUB)0 z|MOY3nl*en^I5e)YjDT2SnJHI>|a1vebp}%y&cuokLf>rH`l-B7M#}qBdxa z&RIv)HYWV^|7CTI2|xXRSyk{jJZBwMS6Rbz)=^c6Mp)C zObuhgPydgpNFsgCIH9Rkel3;W_J7Rm2*evtCo>tl>H9HFcIX z{DiSgU1x1##OI64R2|dYi17)ptAI*f=aPu*rLU_9ru!pS0Yx#b1MP%LVR{O*6Do_S z+8sZ&d_!$x?Lb5}P%&%xsb#sUVC}VthnAMBO4ji6%{NsoYo}n`n@W6$wdiLSPAWg5 zJR6>APpTfQ;hFX=70DW&Y2Q-Otl^oqLZ!2YXW9z2nl(JrzO9N_!!zyMs+2W+X5o}N z%Njnja7tZdS^?TSs*Y(mXzwW7M|#aJM8KT|70C2;1l(Cr!9=TUzehZ>^j$TTsnMv% zfMSXCS@?`P%!Fs*Gs^Zc#;kJ3^X?gSo(Z3dIHSDIqgL&Xd-)kPOq0aD{EX5c!Ry!e zGinQI`t|*cIzp5yT8>IiJfr+R!6*grgz7dJSH=`RYS+^D)D@=qQKd`ID%+>pbIYjX zOW&_Q#;lX0UQa&fqz{2UaMD*ml`4?N;W6g0f2hKpq!K?;=|t5MqkOE2nJ~(Eb)E^M ze4_j=VD?;F-6-G0PgN|F_h{e53#yQ50MJEsndvT|&y?RqJuU_4l8R+o5A?YzWI6QpD_{_q0>M9dHvv6J2GU2lh-z)foX7I1t9iMghL3t3Jlh1zOyV{}^LpK2RX zxomZ3TH;^o;uok@OMEIq80J5a@JvlXUMMpO4)*uJc%hvMNU3W-iid?urr;c*qU)nYU}G11)U;iSOC7DhNxu9$k~ z$W$*Qg((TBl~KgB2B@`ho@qN!8^hx(jF~I&>fYN3WjcE2KbE#NiikGC6WiC9`WREM zp=W`pz0-8~8YM&%`j_phH+z$I06k%O>_^iyAeZ# zN5|)jyBjG^I^np(*g%Az2A+2GGb`I@8jzmmGbJ4ce3L z`S&x*i0~eDLVQ1?lBis)9$W1QHbTF}m~ek-?01g-#(5(B8A_;egSD+=e{+Nx`QK^J z=f=vkfyPCm0#P>BNEl?;uA`PGD#tcT3pWaxz8ITrA8Z_B`g-g$@k5LnO}3xLX4r=s zp5N0rsH_Sokf_=n?`{n>#xUXCt)a$LO%m^J4Ks3>@b1=dqlO9ZZjCUe{?HI}q!Duy zEpk$`w2?--lUk)k7y&=(Ik=7MoOY)X>7+Z-#u&v!xuW&B0cqonvrLg)hNVRsb?k|? zOfWD8Y$Gdh+}N~B-t zNYo@&j+>qsV-zzzJT5J9wo%FS1kgQ(_=U!aXU2sk%`sw`UVw42MlsWCKy!`jOmB^= zvd=R-Zs52o`R=%s_<2SI(+?1Ho)O8^dOX}AFs3pM9)B${&e%khCl-uWUOTk-%l?#idn<=(~FJ6tl_n3vQfzzUYjNxms!K-g;NaiJJku-rsvX9 z3=g7ecYJzyiP3|#cR^cXgtCV3r&En+*6`Xi)reyauT7U4t69Tq)1}4+*6`ZYVU)6l z*QO5R7;AWKnr575!t2O1qm~J;9McT%I;^wW9j_JBj9?~wAD(7RCDK=iX+|t-cy+kU z$YBky4wo7Etl`z+a-)PbygFQN9AOQw4%3bEtl`yRx^aayygJM<%s=#+;niV=;Yn2O zj?X-=FoIYc9&{mXg%QpgUae&sS2fYqTBfn-Pnr?kjmb2Q5ao)Z@m2OrqgddL{sQs* z`0BJw;|9}PKv_n#L``4UWgF>41>!@{vW+cFKaBq-?LOlO(`}K}Y4;oFnL0%NoR(wU z&_rkU-1?)xY0t>N(i#ZgRvOVn zdfZCms`hlp466*AN#p1J4>1o1sMl=&XO}O4L-L-~K6Fy>u5>-olVsL}8 zmMB-W3kh9TV0br1&jOJUIcnLX#&xELBA2HO}W2%UJ zJ8`RVnF!Zx;j$-;8${&-tKM!zdojD zYs2;2X?QVx6Pdnjml4dQCOil4AwO!$oFZljW@TH^DOdyGx=z$9jWa#^u4 z^)}3)x9tJLL8Q0s0V9VstmU9l$Qssi&?q6&+xC!gmbKxL`<5Ltt}{hNz5rB5lxJ%* zVPSl!0lzR$GwL)U0Vt4YwGE$rebyMm8b15_tTC0SoVN6HMky1{=y}7|ykR|GF#MQs z?k^f+G|4+AyuR!u;|SBF3GXd?*~n_4$KgKlicw0W_laXhlowS7`^2@xclGh z8N;N9OmoF|;CaT#A}W9#)M4#m!8Ffr+ zCx$Hl*a&Th;|j!%e(Sbo8{$n?_0g^te*L65O+7H>^l zx%`q5&h!b;=SDiywTTZdzibo}Z4^IGd}Mi*k={Y)`(t9^@-K|TOid>hF8_y7N3>aV zne^=PD@Hm!3|k<&PbyztZTQi{umxh+q)(QAY209%KIzKxYle3x9W!oH?eZEUoN39V zCh6Z8F--SO>YM(pk;U}zq*>|T8QYk)OnM;wx^awY_oPkf-y0X1o|_bw@Pkpu^y;L& zsXrRN^zf~|=BqVgi8hN1lXj)o8u?BtN&nd>b<*MV8^(F2FDAX3{<~rNW7V6*_mfVi z|78R^>7#UME^<;;x@m?7XwN?;eUskEtYG?n(qHNB=AtfIYaHc~;b9)`s;O<1cSbYw z3Q>Uw0P-}wx}jDeLZf(74 z8O>A|m6_4WOlN8~d2L2#vy91aa&3aYd6}v2RqqiB{OH(3HA2Y^DWf}d(k?t(a|mHlqB#!1c6?lwg~%&^(k?XI*H(@a02lkm*f#N=saDUm(` zrklrz^pjQ7%?cv@4a4cCUog#&z6m+q9L9v-Fr02iGWEOb`}k-xi)kuR3DXjyi%c7c zJo@XHhls+N-Xlt6`UlZAroV_zGX+e+e05C2hes529^M-gje$(@dd6bxgB~ zdW7kixkNEcdx`RyJ|H^A^cPVLQ`c#j!FQmJGKMIUDTyeHX*W>`(;Gw=nf^)SF-S*g zG#z7xGj%0OWEw-XjVYGsG*bpq9n)5#9^pFX0iqbD_lfeEz9l-wXbt>Cy_V2)wB5It|jz2+jOZ=x4E7Mg3B{)#@h zVv$+Q)O^N?74c>T(PsJ{LxMRhLf7d(tS|${G(^cVgNQbZq8TzX%Zy`sc7}UqwpqgT$_&rU`^>XWYLj`t zd4s8ZMyJeN({HS<`rM2G@vF>8qK)FhjIe~&W;zkpc}M0Nvz!R;{`AkxGcObAXXMwK zp5rizenx(+8AMb~Pxh}hr!wJ_{cBAJ6F$qo*4#jpYpb4tbeL$B?ZyoByr@0tIsdh$ z$9T+84YWV$K{JO5pVWTHEOFBDq=(J(M7aW=zs@&3B5|C4>VKV?PNd&cK4NYo+6do+ zADa1y8887o3xprgqb4-r-${D!`&d0$y=O*dI;rWfnJMJ1duCYDR;QLU z(_w$YN!g%1X(sA?v`=g^^NDb+;VEu&o0BGIK4o6jMBj?uZrUc&jOc#PcGHU}&$e#n zjLbqan6;;7&H)-mq-VRs^q!34Hi`o?@6Fs{4r6+C=HkpsF+K0n z<6fP)CUd_T=cN415_7GS3NjCvr9}Gm?~r+#wThWrGY^@5Q*@NGGYd0I%`rrqZI@>5 z$$Zw#ViL1TfQmGUcC(%Xs$mM6btLmSGxTnZvRRCr^;+ih=4z&?vrc9nHdCgf23Pm9 z-phQ^+@Oh8_a)OiT93<}RhjvcnZ@)Z&{6Xu)AO@F%RFw5nW;UG0=;I!muo2B(OF+* zzHVNgr8AU+R&IvIXzDVsQPxS*YqqAdv%Iq2Hp`eko7Fw*w0Z0v9p$T8!C7Za?>XA@ zw^<{z&YDF`jblb;oiooec>`6Nbxd7jCS-kN`o`*UVKH;F&YR&(kuj-RpO`UBb7C^H zJ~gw5a9>)Pb-~=G$(9X2?)wmh(9LY`)42n%@w!%FJiNm|vKeiFC{_%p00) zMG*6f=^cmT@@%CL^NJZtr0cw57BS&d)mKchprOuxnp25%%zv7Rnrz1)=9gwZ(+P0tU6Vt!+8BGNIxF@?RM&TmZoYnQmiFy^;rDUq)8 zTeCtFZKLbvRiVMjA8Ph zT>z9tr0e|Ayvl?ze>5W!8|thzw-D(%Yt2$kwqS_)vw4EeRra{bK%{U@m=db2jCXD&3*(153&fm-&B3<4bqZ?{k&Y>>98I!(S1#qL}dS zYuKz5CVVQ;W@Rzq6E^PFHYR++#@#B`L>U@erL19w#@1OT%+T1n$g~HvCRQEO%b+!} zY-xI}-vQ0T3S{~MG!H9S6J=;>g|mirHnn1yu+FB|A||Y}nYES)>uhFiV%i>@V!zEg z%=8*O19F>Hrin6mS`|c9^4HnBvOKL}%dj$iE#+y>nR6!g$cqNpnxW=Ky%dYLcJc zQ)TzG7BT%rl*pbwbDjs!d?MT`+2C2?q$+!J>#!y?5ib59v@ ziRRGKie$n$w6vnw6X(#Mkut|X!rY-a^9;R-?&O*Er+Rw0qDvz=8&lq<%;jC?KIDyoIHyRYS` zNkqqnCHY#xnrw?>J+s?eQ<<`2eSu<`9)cNluyUBT!i+jt`I;zZM{ARlZqM#$m9i&3 zz2C_?OQfIP?_^zMtvEJ&X=kgBwd1j?fNZPvY|q8^%l5Z&m~fT+twJVTC4VbCPp?v& zxygzCR*WWEC4Vc930KMAI>w&3-TkeLM0%C{tvaGyF>`K}J-`ZjKxbbxcW`!qwT%h0 z2Uy3LFnfSi_@K^SGB*Xrm1&~v0agVQW)H9qKcqb|dw^9*q_YQDwM4n%TgcwU3Vawn zb449d3{&HIsBIv^aY$P@HTFi`LaL^D#pn{9UBT?5Gm6?AxtcBKV%o z{AEC%8)&w)X5B4sBK_{AyA?o$?{=%~-K`)ZeAXc=ySo*`G$|x6yN8wIGOnkUPo%d) zPixanv`v#}F@IflPpg6{Z2so#UKak!EUY}`>uvSWBx2`JN$qU~KkBN5YZA*yi@ix( z#M;XFVM)EM4K7-crGKF{5611z4ziBjq?Ply&GV-x23hzkto60@v1(Z>oxeZ3j}@>H zwJM3nUtenj6Ydjztx_i3C;D26kCCT49)EqUd`+}Z^tCoI;XcvVs$ow&0{U7Wo9nY@ zgVuv6SKwaI&x&0~vsN?z-R%CB z_v7{Xs_X+SKTX0tZhGPXE0RdhHq|s_d6J`&yBA;r=9%dyH>Fi-vK2fgd4%r7r)Q4io9Q53(wWa>bKSS-54}p)>4@yO)kyfyaHp&WTZT*54_l>e*nRYDbc;9GigNx^#))v+d zFX(mOofiJ;cc+g3TkDewKg}$c=3k$SG) zVy{t;wzpR3+Vfw}#dSs}eE+L0{&%x>jb8&*Dfn#?jHBB+W-ayCQO)L3RNcrn1FJ+U zYP;6#+SWckxA^=g{T(R&?KHNbZ3tZLzu&|ElgNKpVM7-9#U^L=`i#A(lA+Z9C#{lh zk9O(53)+T=`I{b86{&_uAfHs~83#wJA(m0kO0vQ28|L^B^k!)~|`) zBKkPeGu`ImquWor^ti6Z|9kCTJ~*S@l(*Drm3WETxAkOwgztI6>Vq04{ z=g_Ez9#_@1t%TlhZ}q&(C2xcJ_x|Xk#Q(e!DW>02b1d{IkwEeetb|{8Yq0**({Sv&?$7o6gR8%8_n~P0XzXk{Qu%Xro>DBNe(jS%ZEZ8Dy@jl6 zh5vbb2aSE6>=ClBf>lDVgFfeAkEC@_qKw+-Tu6lK)*R#o@hL-Fwn)8K2%TYGR%)~lq? zY4s}>NAdqI@_*i+LN(~^sM~m~8GNk$YmX^%|1afrtq_mIn~%qaWAo-K7JWUV*94Di zDGI0_*Ym)C9SyHQTyq)1^=hXfj-vkhHFv#kY4CwxvZN#HR;ztn=jeLGJxMj=>$?)V z?dth|x2>-tTyweBkJ**L*Dpg9IagAj@eEN)J;%wOaIxjoZWvpCp09xZ`muOi!*wiO zQ|W6SZCz*AP@5DVlK&;L`V96@YU_S|g@$95_?G&099`AV(4%-;=vR5yEV}J_9j?!@ zx<{XB_1X2-QU71{=z0A?Gu760tLe7hqx5Uyt?GZba@Qlh?Q%T#{{R0-*Rys#-d&IG ze?4ZE=mhhsf4_6C(+(|wSug;2I2jQ2|sbAYPYA>fa`r1`rE2356K2r6y+!|`T9uf6zyn5L{YQr^{ zqS_3ut)X9vr^!>l`_{ADO+Bu6a&CP!)Y;K0u?MUnUZhdl6Rm<@tHu5ZIx{r%!*AQu znfVR!M+;Zf)P9%RmDF~u9b8Kf0OF5T<%EJzem&WyIm{PHDIp#_1Bf4rxF;ges*~8 z!s1tI*9f<^c`moabj{^DFWqiX>#NmqTwe_vj@;9nI`d%DHkaTZH?@RSLcs6wr429XS5&ceay9zThH^Z`-bcK=>A)`g{!~b zi_THThPENj|4UE(`$6=)S#7vNsDBTu<6u9mFul6D_UL;=4L$XdwZA^gHK>NTwMWr= zC08HaqbtD`Q=%{V;9WIE=L89N*y!w^w+wY}eS#|h;nhh;pR$gk`7qD0wDlJt7%WQ^! z%D`b2p22@4;ve_4HaNh_OUo&>t|g@x>N+e5 z`%M2@i~pL>WF@J%zkzAu&H&`dhiv*Zk0+KR#+?C{m*#kh#neBxiPWx;<}TH)*I1c9 z=F6kx*DNFLjkHaLd(KPg9{7mm|DKWR)hPBk{?9S~$B?xIq@mMUNm?Fvec2gVx|8tN zT?K(;{B3H|d|&um{scQ;NcVy|#*WMA^i_}}otbnfMxq~O8 zJ7fY7^JIJ$9akzt&vAd8f29mP3V5RLFqV|*`2TUzlxKA)L(fn=nbRSY%jUJbL&nT9 zB^@#ykNS>tmySLu({bIUjO5?%qI4Yk&tq9Hx;3O@pWdV9h?JqnFaF<<)i(6A7M|Yw zJ;PeC9j7mqmyZ38d0O-Hq`brWN<*dclG2r|bYD@*NcYXQv9UU~a;28=-_I|*SV^g^ zB27VRCrFBm$85)pRO3nOE?p^{V&h6{C$&cY`=0s7KDl(Ug18eU z@gR7CK4z>(A-2aREK@zHAoX}(Pn;C>WH934n8HtZWpftt zEM9{V4<|dwD8&x;dobqm6JFU|6NWcocoXukl6btbxmQUve&bp6$TgFdcx7|VRV{$1$u0xfbMFkZVD%1-X;R zokZ>=awm~HiQE~?>kM*dFt0Pnok6aRd@XB3u8rg<+K_8Q&Xt^<>%a0oxiWWbrX!($ zM`vWJnD@z7bB7x4lW*rvM44alS1BqYPYXosn5KtrX=AhR@S^!I)3?J%={xfSklCD(%5|Q%hYdZDzlONY`ywNk8#wO>w%D1p z6ZuQo+}CiU=DnN0h6|n-^MZ(ZeZYpc=WoW)5@Q25W8M{(>4!371OE-@&Rb+$!!3qp zj`shxh70R=C%>69&AXFd%{>zm^FlLMKJPvKtDI8 z7VgMAH&YvTa^8;_7dX$sy-arQ(mXHIWzIg&7k)R-4?bqGqj9v!mADzxGj3zta^odz zc{}tp;;;J2P~&r^L1esfs_8!W?c8amhg>j9KI9VMAoQI!-E@h}G1j5W&X3YkOgNH_ zX>gHop~;c|Rm?ZJ!eWyM%T0s$<;G30%DBUH4SUXR(_Oab+-1i_F1OWqKVvMn*SNq) zw=jBnB}a{Kn0|P=*IhKeh0f31((8R6t*&+Q# zLmn$r!-mS()5w}Qx}UcZb;cdj9*)*I^{fot&mWnL{D2I(IhP-j;bgAiMrF7{ddwbY zCHJuD(4(oBxdHW$+U$t^wm+o%up>7)!`FP4P0f*u%4mLJGq%_wV>6$YG0JS_eRLDd z`&q`3Tb>bQ?)lV;3}5W?8#A(vIF2&H%p>_Xod%|kd0KytMCM=FcK*GL0%I(e z!49)~jZBT@y?WhAkLBq%JZ2$(dSw)UK4YdiivKtx9rLA6LZcqa&ce_Y#w=ET5u3sy zHibn`GxWO`d29-<Jld9r~QK8VoJZIm)I+eLLF`e`YjbzB}@U zoF+ESCVok;AJTgYip;g<;w5e5`YQsylCRZc3fN z(@*vqKhOEq)Fb$&@as9(_+00`sn>Y=9I-_s%@60#NdSP%Tp7hB*%$RP95+;v* z$J|EFXQY{|9DQciER>{m{>02S%!NKv%asIY-ZC#jzW4k^0$txlLay^i@FQn`!yJgMFRa!G~G9LXVbaUap^Owq^(he%nDlq*@3>1(>cwx^>&pI6ny${!bgb+)A*7wEovTu`Vg3=KkY z=DO_TLQmD*)I+SyQlUCiRd7h4-w-(^)Mu9E9TMm_N%ja$nSm>h3e6~Q=MQ9dEjTK? zmD#7@F2ZVNuT@eA%_ z+42f5Fdqu?w3jn32;H(!4-5UWiV8-uX)a7K+pnMj^Kb?B0u?%UVcyspXV@p4%fo$ZF{7$&QW?#pP;>8Duh z@g^%n&wzuAhT{yl$oQD$@3Q6m&niEfi))6*9-rl2Ids?#QyM7(L?sR50Xa>Hy7|w#pycV56W~#-?`Ou-MLL#=WzKp!G>;yqoNvxql$n?_HZu(Qy1Xz2{a*S= z1wAUyvh6_Yx{E~*@l4{YMRtBp&ds8w^6Z@N5SwxyV%)R1lCtxwa^%HP{6%B8;%jXB zdCW2RKa}V>-)1e0wrx%}PpPEr#iFKh@CAezCHbMW+Ps?n}SzIi0pzC$~ zY3Aje4_3M(V>gzuZRqYm+e=I3ReC>D5z5r%6)EWV;hR|8q@dqpU!9Err3*?$;p+|Hczhnvz#r(5Ba=V z2T}ji9)Y9Cjhq4_ZN+Ubrf12W#rLsSOfyAc`|K;W^FQVsE{-Dn{I@aGbv~{Czn$BP zj4ADdVo^!I?c9L1u%pG^cm9RqHQdnonHg(1{ru00kIG}_|EsuJo-zMsvCe@W^K-Y1W13fp*V)?wMd0OwqI?!Hb=SZ>^;~WQ?&qA5pf;nsp zWWmFEu`EA}o1rr_&#@z@em#_%B#!%6mHZ@tBUCAHP6e+(5#Tuve3K=!Kzmc_SU^_VjY3{ULZc z(tZ$hEK|hBEo0U*_b?AJk2})O`ncmqX?;r`vLn3=dtKm4kqcPhjm*OgyOC2&S+F|4 zhE1&rW7Xv~Inq86mU3YW(leHF30Tub3c3$mW7B-hyvuw@<}MhWag&X8(~6qcL@zBFuJFmt zFQLapaf!Rrr_WqUb$6mITX!egTamjH?WIU+IlDX2ebwEG)=2J7w2kfVM6V#4W%T}1 zfMR6s+LHTZYgR3mUza;NL$5eLv#z9pB;+=gjFlO3_ms?#rRC!Om25ITqf3Ntp9>gz z2xGmFd#q$6TSt0bk7rVl<+%$@!`WRLdX1l3aEaYbLSL6?y<8AXxh*A&uspj>c~11$ zxzF07c~11)P1~7Mi!KOjF|RDP%tcSj(Dq3YYmb%TbHu*K<~T>THTQ#(+rqbVKPj2O zG8f2|Oj}6}i*?KM>@yS7$N)6vzN2@mA#&%kL0n3;$97XF1!lm zPp{14%X=y2g{kO~o5gSIEUn#I3``yL~CH|OV3o<2F=<7xS}UcKk_6mI19 zTCi=(JP}G6}`qUQqkTi%2c$!j4~DNCnJjgDOX$_ z#Xo{YD%x8`jVky#pXD`J3*0eK(Y`IDe39z&UP1GUR6q2pFcb-N%M=N8%M=N8%j61p zg5T;Qp;Pbpc}0Rp??gDRw*e0A-IQ7+&}A+XXnzz&fxIwg^%2~GIiFJ`(C^O{2|>N* z%-f@i>zxf3^sX>?uVc;{9-i@hG$#TUe%zd4YMEX@_a6Z^IJfeOPXM2ys3|3krSS zuojmqvnMP!iZFZiAeI@<919n%4uGcBdWgGBEI)&p$}~be|BtzpWy~yI!>nd*X88uV zYIQS<_cJ}%Ri``KGBXJ61x32sCfSJu6U8%~@c&4uP{tjHO0IkNPDI6xgK91udU3D9 zfm{ppU zz_G69dUhqZx_;8DE4knGYA-eM6u<5z!h5}j5FhaeI70jtju+*=gB3VBCuLFOf~0p^K~$eZC6QY%`KnWJfeMolX+Ij~U-^t2(9;Aw}m zJZ~VM3LC}OJPGe9p7d11_dEyje%x74E#jVDfqbL*td|~{v0esbO1x5GnU@Jxdzs+| zuR?yeSO-1DYw)0G_p%`V!m9z^@v_42yjtLoUUvAa*A4i)mmqlJhM1$^$2s;(MLfOV zY~h%g)X#{_!G0!Wxc+9S=wFDubAJos#QqJ?(7zFxx&5t(kM?gBTE!FnZHQa@-#~n( z|7}d+Tz?|-6uaY*JguU~07sc0Ck{{}vtWP-7Z316e%Sym;`{*tuxNk*t{IRDD+aWo z{Q3d6;f(>D+*9-$D8kVL1K@;#dbuALFfbKy#XuwC^#jd_uMaGge<0o**ns%HH5}qC!V%tDIL12wPV_dw$=)V7)!PEYyc=MYw-v^Fx4=Yi8#H*^;aqQ`^b|9^ zIXK^2P_~MTyw!-8dV3;X>8(Xv=xtEm5LbJf5tn;g;5zRHxY64R>%CiGgSQR7>}`j; zy@{iz_`0_mzTxfZ*ebr|twsEfcOc?Z-Ui1TVw<-GnGe0Ku-)4RKl3I|p5o`;YWQz& zE&S5k;B-U0>z#`DzPB0vzK zo<0q5piiUoF>$a@3u2Q`tMeVP*oRYjilsh+>Va4ZABj6zd=v_r_gQ?N`Gt>&lEhaF z<-P&X$=3io`=&y7UlZ)~3BJU|Qw;Fs;1pk( zi?b%wSB*H*SA=oCS~$};0H*jF;2hsnX!JF~9A7hB=xc#Xd>h~jUn?x|J%#C%_}UPc z`PyN%FX@CM(U*gDzUodlM2oM8c&Bdw+~uq58kjtuN4`aZ!0pld{1EtU;DOoY8UUptKzc3HViEsd;>#Q z55A3j`C!u7Q|vLsv2(lFXNVf{t3y0H?-uvMgW|Uk$HI_782ZZ)E#ktVft@k#P(3o` zLk)D?VX3gsFe5UWVW!SE#J$7J$Q&592ARXdn&IT(tr+Xv@HWI3hubhT$?pa-&->l( zd{s>MbL`SCE`V3XM}F!qs9{EUBI7v1tBbRyVZME&K-D}PV?nGN+W_@r8b64@!)_)=oirJK0-4Zj@KLrXoIZ*cI3|o+(!Iy0N2e^yb&P4uL2yqjn{k=;EDL> zfI;2b#m6vE^TK2r7r;=>&B*~6`qSh<_-Jw}B!RP`Jg^Wt1vbLYfvwOz&<=YB-hgNt zJcD{BP=EsiML0Mx0Qv={!qI`V;ed7pe0a*I|Du8 zuD}4eFE9`u3QUDZ182h%frYR&uo0dKwDxEh&jq$1R_a=zi_Q+a>Tba9I<6;LXgUFk zIuZ8Qc|spu034PYlSg7JDje&0h4rGFHiA#odDBy zBFxfx!dzWyZ%^?>of$6IH9(WD1s3bqkQN391;Fqi1B?zzg$Y3>I4j5uQ-drpEvNxz2DQKiL3X$}h|_qA z%YsCh9~1zKf>Pm{ATz88YJls5THvN2JKP$?d7=&pGJ1~Jyb`p=vt4Y3p_+bE)_4YL zhQW3*c!~vOPE2X`jM21CX@h5`+=l0-a9*C`#VI2EbV>lcHYF9>r` zx4?*C8;lLM!x_P3kf)d&%)!~gYB(=ggxSGb_(E_1d@0xf^MX^MIoJ$W1vkL8!B$um z+yZNZZE#Dl9c~XM-kxGpFbA81)$p}o5grWI!Xv=}@aD7aAEbFy{Tg7Sm0K_qR1LEoWRK!Vo zGkjj(0MqqWP5trdM&Kc2f)pG{gA8T zHoXzPqBkS+y1oIvp>Kh2>Fw|xJvS6rXL=E~>CNy%eFJRQx4_TzcKErT8-^CFUW8xj z1K?eKD!i{Z!=LmG@R7a+k`OzThj7DD&xeSxb4UPm4@reRL(K5mkOt@((gFvD*x}$1 z&JXo`hzLiA1i%R)sW2eK45x&MBT&zW1i;7;1B?qvg)>7;FeStc=Y&|GF{AFNOrbPeTmwT1e{XM2$Vf48IO(fDb}i;NuWGl!uZr zIPOBZF^L+tP&HzYP!X{hszvM_8i06cr~&bq&{V_|LrsXMgqjhDhFTEEhBhFc5!wQu z54FS0P;P9ZW>KgJSA+(@RiUY{GSmz=g*L#3(B`o*nq8qSh~ElrL);o_M|?5#HsY(H z+_*%|y->$-F`8dOMa0UmL5N+$0ucLz=@E|%OGO+LW<)$IYz@o|Yd|JHtQqm@uolF1 zVfJx2Lc_T6I6}ij_*z&1JQ$V=kA#`w+hGmxWLOJ)FU$_lhH(>cgoc?X;Rp?DfLFt; z&=zKcx5CugcJb>lE&z3SxCj%%1K_OiRG1oWf@$HF$*9A_t#CoOPUoyy9Nq+%h1;f} z4iC46w2DRHHn=97grW`)H^KGc7Pu+g3b%&a;Er(XG*9uBa2wncP9i+T{ow}qX1EC+ z3%5aQIEnNWPlv1F`{7#nQMdtK3OB*a;WqfMa1w=9Q@9#_6|RNfgd5=Z;U@TVxFyn3#!5x5UHmI0JVm(FYX*Sqpnk5b`?P*rnYnl!Aokrqur94dy2Tjw$A=3>Fr|JG#mV6ntBFW_z{MgxH^t7!HftCoF8F@iy~}rX#`2c z9ia#{ER4{?)e&SCu8t$ra9xBJZj3O%`Un$jh_JwyBdl0vOz__k7Wid^72b`o!TS;9dDJZt+PUrGqX@%X zoJS%}aCW2x&Wp6d>_}T0&Lfc|9p{lqHO!0DLUW`6u8K6lwUHKB6={XFkv6y`lFY+- zBvK8VBDJtN(g0tJG{J+B7I-Am3g3>j!IP21h`lsY4bMhu;e|*8{3Ox@uSQy+Ez$~a zMcUxkkt74>kw`WCAyNw;MjGI6ktQfaku01?qSVkON(;M2X|r)2i84TsC=(Q;EUZ8a4vf2~FoEfc!DbWTvC)xy! z(H58!ZG{V?$s*LZ(Q3FNS_=!J4X`BI1k0i=usYfbH$>ZDT{KyY`Zih(cSdXBu4n_? z7j1%vqAl=fv=yF+w!zkD@*?WnXf-?+t%Vn(4e--w6TB8}f%a%C{36;0??jWA&|ZpG z!yltfOVIv_w!q(`txy(2mZSX>qlTSgw9qZa0DHukV4oNZ)WlfffEXL}jcLkr)(nra zt!x)Z#aK;P(-<3E5kt&a(-<`@iP6Hc7z3=1F~JQn7FZW!g_amY5!N)u1b4+)ird9~ zF}79sG-F8#KFwG))WvF{KGv`XpJuEH#>Cp-^jK1gRza*9J|C-v>9Gcw6>EaIu@?AZ ztQ9VgwLw!XS&LRdtQwZaYGGxp0oKHt;O1Bh+!kwvjj@I@wD4n1aBr*y9*DKV!?8Aa zJeHKBg&(Vi@5XB32eAftKGp<3j?rUeyk1NjwO|7;m4}sgIH@7TKKUx z_#~E8qlF)*h7NHCsERW|*EkD&Ce8|b$2F~+tx?C3^=RS8siAkA77mRgHE7|-so~f- zEqpG{00ZMlEn4_-Y8W1;h0$>am=I@yv*N5UHO>ap;>ZS^9pcn*L7WyYjnfg@KT%wUXCO6t>VApwDs-cjW|O+_KkQG+!=3yyW*{IU%U+- ziYHrf6vV6HiFhq+jW@tE@s_Q)I}~q)7vpX4(|EG2RlF9jhW2Q#MAIACVu{u_ zaHPXlQ9a8B`@we6dlq@KRjiw(hL%~{H`~RXuvP4tWP{HpsSl&ZPSV1GNd`DL$prn9 zEO2y^6;4R99nRDQBpHrmYNjNaU}%yBMkZNdT$1`|re+EJ(7#k|Y}}OCrZIHPuN5xFN{|>yj+cl4ONDlhnsEHM^3ua9@%E z9!fI7qe(V+B8j}6scB78!!t=*crM8TFD6;xr%5(=Es31S)Yy{@@QWl9ypv>s-z8b$ zk4frxGBv*@Y2oil1}IB5LC0hp?37HbnHslbHSCeBg?*AOP?Kzh1CnjfH<`3#YKA8p z;HYF19G`4~+GJ}Bu2_?8FeF)f5?8Fr1{j-cf-{mWFge)@XD8d>ykvDNS{lh(_(HM) zzLac&dC3-NPPW2T$>bDT8p&!{m8^xe$p*M3*#x&ITVPYN?Np|wIhmZ!)V!9gg$I)j z@JO-+zMX7^CzCDjW@_F`w!*W?S)d6;a0za`rszT)_Prbd~fg)S)u*fquWK3WI7U+{=g~L*8 z&_9Ko#kna(eKu1wDMbr)DJG~-vA}65HW-sa&Sh$*r)XhPiUB^KVu9%?R+yDSKFrkQ zrl{eIDTa?SHOo^>(3E0@#VIyenxa0Rsi{oS!kQHIrA*D{6fNAAVuFn+7Wit44em`L zA7^R~q-a0M)ErJRz~d#^UsCW#OqwStYRDT5(7|AWDuWfe8f@?xgZgTwrnf;0)dmynXRttT z1Nz365pt3El}bOON=H47{7T1_PGUdO5c*D6x~Me2bWLeZ>8?_1shySly7c?fpG%*V za%*YiSK771?@7o!Debj(=vu#0|Fy|*?pitrI*%u%e~g*2cIne`{7Ng=?pXVzbO(F? z1f^bK5uN_=wJmEeVAvBj{?)biwO_9NcI}UAg|ePyL(9gMMV8Gd%P3n?R#5iGy8QqB zJ+8E*>`7@&nWgOIvVCQT%1)JCFT3?WEX|YB|2g}gmX>Y>>F2w$hh@LBA4R#M+_l`J zd{FsN{ERL4E1g^(S{`4XUY=F{Liv(%x|LRz7vc9S<@+)8NoiL35ez$5-d=vW+*bZ2 zezFkLX@7&^56XWnPsX<8DjX_0QgbXVqEFuir|WA6$urFip3R6 zE7nwOt9YxT4e^Kg`J}>DLFe*Y#cvhL$|t3rDm#urOZBd#!+nsazn_$jtbDFAqHV)N?IzX(x-BHGi6cRW`Pj?pC=} z52+qgy}W9Ab#ir9bz{{F)p^w=)u*dJ!aKjxbZi~J(reXURR380q}pj+=XJf;4P7^C zoo-#&x*6-9UzfA4u__%?qOT?EvQWBU9bHzsRsFElSFii8KTog@FW@ucs=g_2TKA-M z*Sa^>wXS;~ef@e``blZi`sVcq)*o4aas8yy z&)4^@_;US&^<0Irrdv(#8d_Q!<4LKe=6cn@8b2Iyf6RxDFU^@ctY%mZj%K_%4oA&V zL-$Z^&BLpQ{GELYL(v~U1Sah4bT(hr+ z=I`PCA3s;t{aVwh)~9xHMRaX=&79hVnv7a}4z=m!EAiW}bWQDV6_vI1wUexwsQ*wDA)l@0G~IJJRp%?~i%g$+B_eu{T=Yuwy$7t!~~euFiB zQu;#quN&kWJ8vAeal*!cjiDQ7ZJe`_9FKsMBL`$sQm{nfC@m0K&#yN#(N%=<` zuOL4GV@%li#l}aqZkwe2LE3-l5z`l$ew%))7`Z86Q}Cv!O;1XvZ%WxTZ_^?~ja4O^ z)@@2)Thy;~`=+n(^V259X7%QAn`t~{bNuE}rD>a=V0~%5O!rtU*XBi=mv64wd~kEi z=6`R#xB2mA)t3HS{I)EvNJkAcWeeTL(s7f8_kN|3TT-^H-QrNOcFTq>^;>pq*|+7$ zmJ?e}Z#lo^>K3{^9V+Y?2YdIHhg<%C)!#zhGj;v&N&GeUgc?KLlG>cQMRoagC3V$x zo9ZT4Y_Hp~c5mH*y0_|1*7dFEi@iwNuCyMLYQ*4*$*fkSwbx`;%e`NBsg9PrUU#c* zQ2D*Odvy=$eu4MuNd2tJ^zyqK6!o3z`_}u^`;|_r53Y}_pI+ZsRan2T{+;?W^&i!L zS$`ch6h7tpNA;IcV{uy*Tb;Ld+1h<;|E)CcveoC$Xw08c(4SHCpHa%6QP!W)(m$hB ze@5&7j4Xdfd;g5yLi9b>=fD5Fw{>F0$6G(&y13#7mg>&d-|@c7HV*H#+Z0=$lrFB& zZ}TgS-S(t3Y1_n#91IzS99^?T$Yqret1%&8u`R1y%5P$&Xk8O4 z>>SxfhmR_~i=*enHd^K!ruU?@ot67zYBWQSdV7cLJ;YD`oZsXMr|oXr>5~01&%W%P zH2+$5R-*Iq*^YV;J*{}6kGIk2Ib;la4VjJ}LuQk1=zHie_bxocyu$p3$?-JbojHU# zg_*+B$4BMx^dUqY<2*LBlG(s~li6|l(v+pSyw8UIoB5FGCj2!=Y3|Y->D+lNzkr@j z97q@Rf8vU@cn1BS^nBX?Nnfmu7y3ULi2hG}F?1;UK=DIQDgG$+0_jc`lO7}=rPrWe zl#Qe}djIesThKpBJ<*W;*e-8i?4zU~d7JbnRx*IJ5+C$CG8j+p9D<%RhLR7^U&=W$ zoSes0FOU&vZTgdTY|BruEk7lb$!A1Iu9B(f6D643Cez5bB!b){k>ml1AwQEi@(8_l zJjOQvoy@@BV$LLT^n;@0l1OhZne^dO@Yk3IBBI}uiQF7AnM)&qTsrzq$s|GO^JFTQ zO=hCMlUeBNBnf?;q;M~w&y$6Cu<0W7@3DmBa!bhz=-1?B^lP%4D<*rn)np%6O7?SW z(LYKVd6O$AN4N^|7FS7*b5-O7S52(kI&zX*Pfl?)Mt;El@$Rzn)r5S=yu$o~`P;H;g79-uUh^J?3j^C{-=zzgwnWD$sNo0{b>`GD!N zf{r_w8OBU!u4Y=8hneS?Uo#ae=~&M)wan?vh0I#!KITQ{&rFwmI+ibUDl?U7Vp^D1 z=5^+;%x)$&7IP}oz+A@M#C(H!fq9$R)lA14#*AbdnR(1g=04`f%x{^F1#~QL<}_v& zv!vi% zzu+F`!NM;PA7j4DyiiE@Qt9aUnw9^a`G`r1?qaA*(NFM?Dbs06d*(Z6+W*)5A9JMn zJ!}fcnC~(VGXGC=^ZzE$0enoU?qnHbswMBGY4lysXW&5ef3FW!sF3`338Q6ox z9W!B09)IQs71L>sDW>(t6lP4Z2192S_lM83akGnk5u1wXmRZN#S?u>`Mk=|dcnmTJ zi=TtVOzWb{#X6L{SsVhTZP?Mjm-&XxT~aD3t*?AlB&N`H z6`jj3toG>6;{MDD%&E+1W(spYQyNR!L!_~!Ddew;|8tuEY3O{mch#(-pU(DGbo;!< z;-f77Px&)!zMri!VEVUM{D76`OVW^WE}?7j3{%>+(mHnh9HsS@wxouY9Ke*u^<(iL z`)9{JKMT{}Q%L>4+4E_6QnVOKdw$2gE`-fX+V|2*Osq^XoL@q>hO}*^PilEd1@eC^ z6zx_WQM|6&_v!y*=vJKPexE9j#OGlZs zoYEGSjv8spOGk~g&80OuRYH#%X&;a>7fPBstYgP5|G%qqIv#`nH)}-eB57%_l)Q%d zN?TjnA3A2FWtNU*>G+k_=!jSmu1@T4odT4D%NA zC+471b{=8QW7aVbFh6A8V|HFkOO9a9V49gt%#+L;%7l1jQfQtVer=Q6I6?w?X-a-}QE1hY)Xl2MhtkWXZJsdi1P+=Ovos-)*T zY1}s}=@ypaA1eF*IY++47jegu|0(V`g$Y%(mY`EplBFDvtF2{Fy87bqTfo)Va!!VP z9;EN(xH6MLC0W5KkY5RTymvre0hJ`5QzCDI==mR4ZX9xsc$IQa$ghPw-a8}j1eK(W zQz2gtdAxT)UImq;g6o8QCFJqGGxD9Fl2mbBkgtY3-n$~-1uBUb-xX0m$m6{m@?D{l z^yl4?9{_p0?}ofPRFc8`Gsq8tJl=Ol{u!twL-`)a4}(0e8+#((2P)CqVlU)>hCF!| zSCt&;3-OIP-UIoEkSA)CMNhC$Nq*&@Mg9@waoyP$c~6M%*70iOpFke{Y>LQ_hDyQ- z8svG%ld&kvq4!Nsq#$c6sMDl}o<7Ana{f;aN#Ax{Eu#mnJJ)(80uf-mwHAy0yE70i(-P)XW_A;@2XJg#+zA|DKu zfdzbTAF{ubnMo$QZ%Bvg_wgi*+U z33(EQZNQOei0>f^W01cMd0b16MLr(#BmryBlj&Gj4%gKa5vODQIP@?&39%9D#E}e$ zC#;b`#95F>@1a46b0AOVV?8*s0PY<~#8r?d zo6#RChdUD~i0eoytjAp%j%4R~?|%g*DzM>V`g*2B+9E&LbmvGC+N)`llGtOrl*SOcE? zn{06SfVH`!H?r7i{9?kms&LfwMyycLO?dx1baECAPUUL=V#3ZNx5+ zCtbNa(2e^B8Fz?Ug}Voz;qJrk+ymHy`w{l!eullchp;#I2zqc&;IrKCurG&yxJSgH zCKfpXYB)LcvKm^xGSR zI12J4nwyF^2J$49(<6?9Jc;K*5hp;@4_r9n8IZ?)vcVS7jm;P)*{H0#oS!@BA1TLOAz%2XN1LECS1j3 zBVPh}vYML@*KoOT1Gf-voNvCv99U{FK`WKjStde;MNF+YSle0A+k5bmVtJcm5UFjc>+S&p_<+ z{BEe>_afs7vDfkY5c@%%B=Y;=EdC%eNf4h4{|4d|$dl##VZ?cmCoA})h*v_MSz8v#AT2tukvlMng0Op z;?Kd|{6}yPe*y00+fnj0h^@_kjQDlPll}ashz~%X9ON$}J_LF427eX4&wq~02M~2H ze;uCZ?eGGB1775B!FK*jl(_^^_wu(9e*&@n`8$X|gQ$D?ZxCOBs9pJci0u%yD}NvH z4T##6e}MQFMD5D|i1orvj`%rP!JjoKqBF=_9$rHvSUIFoJDPbbwe8>}%FbT04VhQ; zgQ!Dk91%ha;%0~=LP$lt8{&u%W+UDU z(GC&jBHjn_EIc6{@qWmY1A-CpLCBLsLMGxjAWz;DvJoGKsIP?ih>t?lS3)j4CM<-< zg~jk~;U#!NSPDNAmZRilh!%*j0`XOd7Ko6K_;ZLBh+sy19pV@f3K82O&c(7~D9TEp zr)&-KUJ!eoY%QE1D~A!XO5`IU_5@iq;%JCHLAD-oEX1B5t3@0Su_wqjBAyO;GDEf* z@l1%?Ojd_@7UW5iY%Ahq$deS=c4&|_z*Jcyd|tK_&X&D`GIJo#GO}jGX%KB%*>1%1 zAlkIDy@)d)+O)ELh_fKtw6gt(b0FHZvV(BB>_8#I2h&@Nv1}(A=kZFL}b7bcbH$v>;vtxL5WuGOt0@nX*q2zYbAn$}S^50D00Xy9!UqK1b#>xkcjXxYl_ zP?X<*{p7b`fBBafIsl@zBfkv?$?rgK`8UuP}@590J2XKh|M>tgeGaM#=2#3ob zK|lEuI70qA94Y4nw0Y#H;K(Ss9FCSdz%g=1I9Bcq$H`sbczI_yLGB7C%H7~|@@{aF zygSs&d%^&DZ#Y@r2L{UfLY-WML2^$xMcxlil@EZy@Zd?CCnUktCvUxL@=OQB7^9NvY?&`Gfoc2aD{l6Qt^lPl^FyFs*16k8E@ zgJ_d0wj=Hi(Hd7YAnpax8do$T_JBO;qu7b~S;!N$;uXXq}S$aVW%Rt#}7UD_UTJq80h+ z5TB#sG~$^Mb&KLX#K{nKi=qv&0iwOC_yDFW&cSTOM{t4S0$izRhi1jcaE;id(Q<@g=;hxDBrgOPtKj|+El8qu-I1|eptwtTpXgYq~DL&@W0 z8cMd1IVjme(onL6%tOf*l7W&fBnu^5NDfN2kOe4tlDvqLC&?0&JV}wh{Qijhl)KJ- z#XaC2b28qI_u##FUw#ZfX1kW3%1`5G@Tq(@pU;=@ukgqDv-~CAf4l#78~-)W3tfaB zf?60ZgbH&7|LxhrVxeAW5)KMyg)!Sd#_Mxr{kPvjbWeCF@G@ta|8_ULddoaz!|{8} z_Ng#JmLN08#%!M_TPQ1#mC0&kjk3MSzacvzYm;5UjaC2co#nmdUh={6QStzJw0xF4 zQ@&hYD6f_`$`8pe$REjN3KzvQia`p0MX(}KVN@(qtWne`+7y=*I7Sr@6rCNqI}CIf z<>0@4qJs{GIwU%zIlSah=&;^lo5OyGw;e7x+;Mo|@T-HMR4KbDy_Cb0W0YFu6lJ7x zhB8&TSXruEr`)XEq1>fBs644WtGuebtNc;v<2ceW!ZF2hnd2Hqi{l>0w;j(r{@d|e z$DbTKJN0);cY59FnA0Vv2TpFz{hfz8k8z&j9P6CqoZ-CCxzM@7d8_kI=VQ*NoNqb* z>@28Ms_rU{YM3fO6`?Yy7O7UKR;emgTU0w#M^$H3pQ>!Cdn(?=)y2!j-(`|Zl1q+D zkxRYHQ5UyP{W}for0*2hX=SHPomx9x==6IhkIvqm$94|sJg0MQ=U!dHyBNAG?o!mH ztV=_e{avhG-tTg?%bhMVR~OeIuIa8Xxtd(pyKZ$o?t0eM?)sCfL)Xz=bGyFOwW8~m zu6w&)=*qb{x%F@xgHO_byV`A-+eEimH-pQEV zK4$y)?ayr=3J9F{n{{I?ikR$lcy?dk?#7h}$9+C!B&BskQkdcWGim zEf5ZbrWBHxN);elXh;G=s1zkl+CtQjwrN#Wef(y2&S%FKqEi1ObNcps?94acd^_IR z{pPdQ{hs^xZnC7E%`eR3Gk`6iH)MPG(VrdQ_(2?t-0QQ0*(#SVpVxjVeXOw=y-SM7 zBRF?f{tagCUg#Zn->!5ik3#mCa@74e@E9PMeG12~Yg0IG$o6V40>8rPjoG)dZwu-J z;ERAi0SxVLTso+IMM^9K=Cn!8!MUT_2~7j_n#TOMKD!k3Q-|<2OqCJ*Y2$ZC z3AQG9p*SMxpO+YpHMy}PjF~Gtuz>9Q`{&pD;eq+Gn+{$ptIgdvPgaxDC$6nmll$Pw z`Lg=M*UG+uELhRLr^==uWugl4PbN>nA&&LpB|0&=_+@F7jxcW^GU!oqU)~!u&N+Nn?jyl3it3da_!QH)26XLq;cm@BYOQU#A=6ecONFd z0=$X%^IL#l15N?nCO?2z2-bpPjt2yOK;SU;lvqg!J|*zOLM~CeoklCUe>J)su~vde zZ9Q+dgtEN^2z?%U zn)6=zJ6_+5_$?FEAPUQf8oHYEU#2YnNf6WR=KKmo9vQI$kwYLvcJ2OdrH^plhZr3r zeDoma8^rh;ftjF@KFj4Bg?zKXEdsYuwlhZ%r?vgkF8FrA-yv|Pkar6HtYp`JSK!|Y zd|u!SvV9%?DBJyC6?m0m%hxHk{4T}TbC1CH3fwPnNZ_3UM+Hs`Jwrmz_XOS}um-HQJ4DsDDdQM_7)@jJfC5x=VGsF9c2)+;c@1V!%$FxMgjCxmSxojcQm7zUy zEA=BzHb$38yCFL%cjHa?Za@k!sEpA7(=FYm7@$pQlM(^{E_94bmtgNH@aOP`eh%;F zf53bB7|oEsDi&Fyo`EgnlCD}Lp=RhF;3oh-SN#-kTX?gomiMR`5-iG);UWv~0bTM7 zMR!5ALzapy^2Oqv@*3%BrM}pYzA^xdyjFZnenNd!{;)U$pJWjI_R|E;CEy$8hh-%x z^%6;`kdfO;EHY4%p|O$-c?k4(0dlFII!i6GwDfgJaeP~0erj-xDqVoI!%z1(EHduc zr!d`7C zGqeNvUf_Z9b*fRGLEPId$EI_F`v%{i*v=RBfDof&$uVnR9Ztd^(g>(W1* zZ>Z%`R$0C{OPlC#Xr;?f_qsBGQRN-b|8!ZTRNJBMb#=)%X_x6$%_3iLXXq_% zi*&kQl^$?s$Sv+G#aFez05Uk=gSnJd%B3#(6ZeLaY1#xB-ko1Dkt+v!Y8H=M!AyB% zM(%Ho#=knKZwZ795;23(xUTzp!}#1O%74BzwYzPBHZ!8@#Q)6$EHi@;4-%rPfV+W# zW)=3lgiOf=_>w`Km?n?s6hy8IjYhD4Pp}hz4|c12@LqN|s1EFm zZw23hV>@V8jw_e{&7Mx!?@>I%X9JL$I3K_|AHrFCZAR{#@*&&2g-r?63PIk#fTovuoq9YC*}t{qeo)B7 z!T$?TJoiMgj~+qa2DIR#v=~*A+zvhg&*#rF3wptK@?LaM+t4lwn+Rk}X7t3ktwYce z;5u;{9!f%&4hw^z;&>8b?c32h$+gZN)mmiJuuKfw8*Qj*q7@rk3_1#9FW87RpJ&hH z+w}0B%vp6;;r|;Ltj2LQM$Wi3cqU|JEwXVClj37%78kS$^F}nw)mzj{2zq1v#(WQyo7A#ew|zQTJL_EZ`*kLX4ub05W%bq z!%H5_cs8EG75X$Cof0K9n zw)WcfUXPgw7`=gT)X>X2$&IHYL(X$2^gV1-Awe%?hUFuo*88wy}FSIC5Gl$ + + + MarrDataMapper + + 3.17.4747.34302 + + Jordan Marr + true + Marr Data Mapper is a Linq enabled ORM that allows you to project views into complex object graphs. Contributors: Rick Schott, vitidev. + en-US + http://marrdatamapper.codeplex.com/ + http://marrdatamapper.codeplex.com/license + ORM data mapper fluent linq sql relational database DAL entity + + + + + + diff --git a/Marr.Data/Parameters/DbTypeBuilder.cs b/Marr.Data/Parameters/DbTypeBuilder.cs new file mode 100644 index 000000000..8b57a1478 --- /dev/null +++ b/Marr.Data/Parameters/DbTypeBuilder.cs @@ -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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data; +using Marr.Data.Mapping; + +namespace Marr.Data.Parameters +{ + public class DbTypeBuilder : IDbTypeBuilder + { + public Enum GetDbType(Type type) + { + if (type == typeof(String)) + return DbType.String; + + else if (type == typeof(Int32)) + return DbType.Int32; + + else if (type == typeof(Decimal)) + return DbType.Decimal; + + else if (type == typeof(DateTime)) + return DbType.DateTime; + + else if (type == typeof(Boolean)) + return DbType.Boolean; + + else if (type == typeof(Int16)) + return DbType.Int16; + + else if (type == typeof(Single)) + return DbType.Single; + + else if (type == typeof(Int64)) + return DbType.Int64; + + else if (type == typeof(Double)) + return DbType.Double; + + else if (type == typeof(Byte)) + return DbType.Byte; + + else if (type == typeof(Byte[])) + return DbType.Binary; + + else if (type == typeof(Guid)) + return DbType.Guid; + + else + return DbType.Object; + } + + public void SetDbType(System.Data.IDbDataParameter param, Enum dbType) + { + param.DbType = (DbType)dbType; + } + } +} diff --git a/Marr.Data/Parameters/IDbTypeBuilder.cs b/Marr.Data/Parameters/IDbTypeBuilder.cs new file mode 100644 index 000000000..02bc5f283 --- /dev/null +++ b/Marr.Data/Parameters/IDbTypeBuilder.cs @@ -0,0 +1,34 @@ +/* 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 . */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data; +using Marr.Data.Mapping; + +namespace Marr.Data.Parameters +{ + ///