diff --git a/README.md b/README.md index 0bfee108..1d140b4b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ NPoco ===== +![Nuget](https://img.shields.io/nuget/v/npoco) Welcome to the NPoco! NPoco is a fork of PetaPoco based on Schotime's branch with a handful of extra features. @@ -19,4 +20,5 @@ List users = db.Fetch("select userId, email from users"); This works by mapping the column names to the property names on the ``User`` object. This is a case-insensitive match. There is no mapping setup needed for this (query only) scenario. -Checkout the [Wiki](https://github.com/schotime/NPoco/wiki/Home) for more documentation. \ No newline at end of file +Checkout the [Wiki](https://github.com/schotime/NPoco/wiki/Home) for more documentation. + diff --git a/src/NPoco.JsonNet/NPoco.JsonNet.csproj b/src/NPoco.JsonNet/NPoco.JsonNet.csproj index 429714ea..b6752654 100644 --- a/src/NPoco.JsonNet/NPoco.JsonNet.csproj +++ b/src/NPoco.JsonNet/NPoco.JsonNet.csproj @@ -2,7 +2,7 @@ Provides an implementation to use Json.NET as the serializer for serialized columns - 5.0.0 + 5.3.0 net461;netstandard2.0;netstandard2.1 NPoco.JsonNet NPoco.JsonNet @@ -14,7 +14,7 @@ false false false - 8.0 + latest diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs index 3839127b..9c33199f 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs @@ -13,12 +13,43 @@ public static Database Create(string connectionString) public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) { - if (!parts.sql.ToLower().Contains("order by")) - throw new Exception("SQL Server 2012 Paging via OFFSET requires an ORDER BY statement."); - - var sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", parts.sql, args.Length, args.Length + 1); + var sqlPage = string.Format("{0}{1}\nOFFSET @{2} ROWS FETCH NEXT @{3} ROWS ONLY", + parts.sql, + !HasTopLevelOrderBy(parts.sql) ? "\nORDER BY (SELECT NULL)" : string.Empty, + args.Length, + args.Length + 1); args = args.Concat(new object[] {skip, take}).ToArray(); return sqlPage; + } + + public static bool HasTopLevelOrderBy(string sql) + { + var indent = 0; + for (int i = sql.Length - 1; i >= 0; i--) + { + if (i >= 7) + { + if (indent == 0 + && (sql[i - 7] == 'o' || sql[i - 7] == 'O') + && (sql[i - 6] == 'r' || sql[i - 6] == 'R') + && (sql[i - 5] == 'd' || sql[i - 5] == 'D') + && (sql[i - 4] == 'e' || sql[i - 4] == 'E') + && (sql[i - 3] == 'r' || sql[i - 3] == 'R') + && (sql[i - 2] == ' ') + && (sql[i - 1] == 'b' || sql[i - 1] == 'B') + && (sql[i] == 'y' || sql[i] == 'Y')) + { + return true; + } + } + + if (sql[i] == ')') + indent++; + else if (sql[i] == '(') + indent--; + } + + return false; } public override string? GetAutoIncrementExpression(TableInfo ti) diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index 68659430..6ff0815c 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -131,6 +131,10 @@ public override string FormatCommand(string sql, object[] args) sb.AppendFormat("DECLARE {0}{1} {2} = '{3}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, value); } } + else if (p.SqlDbType == SqlDbType.NVarChar || p.SqlDbType == SqlDbType.VarChar) + { + sb.AppendFormat("DECLARE {0}{1} {2}({3}) = '{4}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, p.Size, value); + } else { sb.AppendFormat("DECLARE {0}{1} {2}[{3}] = '{4}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, p.Size, value); diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj index 1df466e1..c68284fd 100644 --- a/src/NPoco.SqlServer/NPoco.SqlServer.csproj +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -2,14 +2,14 @@ An extremely easy to use Micro-ORM supporting Sql Server. - 5.0.0 + 5.3.0 net461;netstandard2.0;netstandard2.1 NPoco.SqlServer NPoco.SqlServer Adam Schröder orm;sql;micro-orm;database;mvc https://github.com/schotime/NPoco - 8.0 + latest enable diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 0f2e58d2..6bd568c4 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -79,14 +79,7 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable x.GetToDbConverter(cols[i].Value.ColumnType, cols[i].Value.MemberInfoData.MemberInfo), value); - } - - value = db.DatabaseType.MapParameterValue(value); - + var value = db.DatabaseType.MapParameterValue(db.ProcessMapper(cols[i].Value, cols[i].Value.GetValue(item!))); if (value.GetTheType() == typeof (SqlParameter)) { value = ((SqlParameter) value).Value; diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 3809d328..85fcb019 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -465,7 +465,7 @@ public Task SingleOrDefaultByIdAsync(object primaryKey) public Task FirstAsync(string sql, params object[] args) { - return QueryAsync(sql).FirstAsync().AsTask(); + return QueryAsync(sql, args).FirstAsync().AsTask(); } public Task FirstAsync(Sql sql) @@ -475,7 +475,7 @@ public Task FirstAsync(Sql sql) public Task FirstOrDefaultAsync(string sql, params object[] args) { - return QueryAsync(sql).FirstOrDefaultAsync().AsTask(); + return QueryAsync(sql, args).FirstOrDefaultAsync().AsTask(); } public Task FirstOrDefaultAsync(Sql sql) diff --git a/src/NPoco/ColumnInfo.cs b/src/NPoco/ColumnInfo.cs index 6432d270..c6b4818a 100644 --- a/src/NPoco/ColumnInfo.cs +++ b/src/NPoco/ColumnInfo.cs @@ -50,11 +50,18 @@ public static ColumnInfo FromMemberInfo(MemberInfo mi) ci.IgnoreColumn = true; } + var complexMappingAttribute = mi.GetMemberInfoType().GetCustomAttribute(); + if (complexMapping.Any()) { - ci.ComplexMapping = true; + ci.ComplexMapping = complexMapping.First().ComplexMapping; ci.ComplexPrefix = complexMapping.First().CustomPrefix; } + else if (complexMappingAttribute != null) + { + ci.ComplexMapping = complexMappingAttribute.ComplexMapping; + ci.ComplexPrefix = complexMappingAttribute.CustomPrefix; + } else if (mi.GetMemberInfoType().GetInterfaces().Any(x => x == typeof(IValueObject))) { ci.ValueObjectColumn = true; diff --git a/src/NPoco/ComplexMappingAttribute.cs b/src/NPoco/ComplexMappingAttribute.cs index e89e58f5..799f89d6 100644 --- a/src/NPoco/ComplexMappingAttribute.cs +++ b/src/NPoco/ComplexMappingAttribute.cs @@ -4,6 +4,7 @@ namespace NPoco { public class ComplexMappingAttribute : Attribute { + public bool ComplexMapping { get; set; } = true; public string CustomPrefix { get; set; } public ComplexMappingAttribute() diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index a00fbe7e..d4cd6273 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1493,7 +1493,7 @@ private async Task DeleteImpAsync(string tableName, string primaryKeyName, var sql = $"DELETE FROM {_dbType.EscapeTableName(tableName)} WHERE {BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)}"; var rawValues = primaryKeyValuePairs.Select(x => x.Value).ToList(); - var versionColumn = pd?.AllColumns.SingleOrDefault(x => x.VersionColumn); + var versionColumn = pd?.Columns.Where(x => x.Value.VersionColumn).Select(x => x.Value).SingleOrDefault(); string? versionName = null; object? versionValue = null; if (versionColumn != null) @@ -1760,7 +1760,7 @@ internal static object ProcessDefaultMappings(IDatabase database, PocoColumn poc { if (pocoColumn.SerializedColumn) { - return DatabaseFactory.ColumnSerializer.Serialize(value); + return database.Mappers.ColumnSerializer.Serialize(value); } if (pocoColumn.ColumnType == typeof(string) && Database.IsEnum(pocoColumn.MemberInfoData) && value != null) { diff --git a/src/NPoco/DatabaseFactory.cs b/src/NPoco/DatabaseFactory.cs index 4a093d68..c3ea9c77 100644 --- a/src/NPoco/DatabaseFactory.cs +++ b/src/NPoco/DatabaseFactory.cs @@ -55,13 +55,14 @@ private void ConfigurePocoDataFactoryAndMappers(IDatabase database, MapperCollec database.Mappers = mappers; if (_options.PocoDataFactory != null) { - database.PocoDataFactory = _cachedPocoDataFactory = (_cachedPocoDataFactory == null ? _options.PocoDataFactory.Config(mappers) : _cachedPocoDataFactory); + database.PocoDataFactory = _cachedPocoDataFactory ??= _options.PocoDataFactory.Config(mappers); } } private MapperCollection BuildMapperCollection(IDatabase database) { var mc = new MapperCollection(); + mc.ColumnSerializer = _options.ColumnSerializer ?? ColumnSerializer; mc.AddRange(database.Mappers); mc.AddRange(_options.Mapper); @@ -105,6 +106,7 @@ public DatabaseFactoryConfigOptions() public MapperCollection Mapper { get; private set; } public FluentConfig PocoDataFactory { get; set; } public List Interceptors { get; private set; } + public IColumnSerializer ColumnSerializer { get; set; } } public class DatabaseFactoryConfig @@ -145,5 +147,11 @@ public DatabaseFactoryConfig WithInterceptor(IInterceptor interceptor) _options.Interceptors.Add(interceptor); return this; } + + public DatabaseFactoryConfig WithColumnSerializer(IColumnSerializer columnSerializer) + { + _options.ColumnSerializer = columnSerializer; + return this; + } } } diff --git a/src/NPoco/DatabaseType.cs b/src/NPoco/DatabaseType.cs index 26a9d75b..9b9d59e7 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -18,7 +18,7 @@ namespace NPoco public abstract partial class DatabaseType { // Helper Properties - public static DatabaseType SqlServer2012 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServerDatabaseType"); } } + public static DatabaseType SqlServer2012 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServer2012DatabaseType"); } } public static DatabaseType SqlServer2008 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServer2008DatabaseType"); } } public static DatabaseType SqlServer2005 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServerDatabaseType"); } } public static DatabaseType PostgreSQL { get { return Singleton.Instance; } } diff --git a/src/NPoco/Expressions/SqlExpression.cs b/src/NPoco/Expressions/SqlExpression.cs index 6b42780a..fd810da3 100644 --- a/src/NPoco/Expressions/SqlExpression.cs +++ b/src/NPoco/Expressions/SqlExpression.cs @@ -68,6 +68,7 @@ public interface ISqlExpression List SelectMembers { get; } List GeneralMembers { get; } string ApplyPaging(string sql, IEnumerable columns, Dictionary joinSqlExpressions); + string TableHint { get; } } public abstract class SqlExpression : ISqlExpression @@ -82,6 +83,7 @@ public abstract class SqlExpression : ISqlExpression private string groupBy = string.Empty; private string havingExpression; private string orderBy = string.Empty; + private string tableHint = string.Empty; List ISqlExpression.OrderByMembers { get { return orderByMembers; } } List ISqlExpression.SelectMembers { get { return selectMembers; } } @@ -91,6 +93,7 @@ public abstract class SqlExpression : ISqlExpression int? ISqlExpression.Skip { get { return Skip; } } Type ISqlExpression.Type { get { return _type; } } object[] ISqlExpression.Params { get { return Context.Params; } } + string ISqlExpression.TableHint { get{ return tableHint; }} string ISqlExpression.ApplyPaging(string sql, IEnumerable columns, Dictionary joinSqlExpressions) { @@ -371,6 +374,10 @@ private void BuildOrderByClauseInternal() } } + public virtual void TableHint(string hint) + { + tableHint += " " + hint; + } /// /// Set the specified offset and rows for SQL Limit clause. @@ -1417,7 +1424,7 @@ protected object GetQuotedFalseValue() private string BuildSelectExpression(List fields, bool distinct) { var cols = fields ?? _pocoData.QueryColumns.Select(x => new SelectMember{ PocoColumn = x.Value, EntityType = _pocoData.Type, PocoColumns = new[] { x.Value }}); - return string.Format("SELECT {0}{1} \nFROM {2}", + return string.Format("SELECT {0}{1} \nFROM {2}{3}", (distinct ? "DISTINCT " : ""), string.Join(", ", cols.Select(x => { @@ -1435,7 +1442,8 @@ private string BuildSelectExpression(List fields, bool distinct) return x.SelectSql; }).ToArray()), - _databaseType.EscapeTableName(_pocoData.TableInfo.TableName) + (PrefixFieldWithTableName ? " " + _databaseType.EscapeTableName(_pocoData.TableInfo.AutoAlias) : string.Empty)); + _databaseType.EscapeTableName(_pocoData.TableInfo.TableName) + (PrefixFieldWithTableName ? " " + _databaseType.EscapeTableName(_pocoData.TableInfo.AutoAlias) : string.Empty), + tableHint); } internal List GetAllMembers() diff --git a/src/NPoco/FastCreate.cs b/src/NPoco/FastCreate.cs index a65cf015..05c1f740 100644 --- a/src/NPoco/FastCreate.cs +++ b/src/NPoco/FastCreate.cs @@ -8,7 +8,12 @@ namespace NPoco { - public class FastCreate + public interface IFastCreate + { + object Create(DbDataReader dataReader); + } + + public class FastCreate : IFastCreate { private readonly Type _type; private readonly MapperCollection _mapperCollection; diff --git a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs index 7e5e3fc0..03fcd31e 100644 --- a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs +++ b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs @@ -290,20 +290,17 @@ private static FluentConfig SetFactory(Mappings mappings, Action sqlExpression, List new SelectMember() {PocoColumn = x.PocoColumn, EntityType = x.EntityType, PocoColumns = x.PocoColumns}) .Where(x => !newMembers.Any(y => y.EntityType == x.EntityType && y.PocoColumns.SequenceEqual(x.PocoColumns))); @@ -103,12 +103,12 @@ public Sql BuildJoin(IDatabase database, SqlExpression sqlExpression, Listx.StringCol).ToArray()), - database.DatabaseType.EscapeTableName(modelDef.TableInfo.TableName) + " " + database.DatabaseType.EscapeTableName(modelDef.TableInfo.AutoAlias), + database.DatabaseType.EscapeTableName(modelDef.TableInfo.TableName) + " " + database.DatabaseType.EscapeTableName(modelDef.TableInfo.AutoAlias) + exp.TableHint, joins, where, orderbys); - var newsql = count ? resultantSql : ((ISqlExpression)_sqlExpression).ApplyPaging(resultantSql, cols.Select(x=>x.PocoColumn), _joinSqlExpressions); + var newsql = count ? resultantSql : exp.ApplyPaging(resultantSql, cols.Select(x=>x.PocoColumn), _joinSqlExpressions); return new Sql(newsql, _sqlExpression.Context.Params); } @@ -130,13 +130,13 @@ private static string BuildJoinSql(IDatabase database, List joinSqlExp PocoColumn = new[] { x.PocoColumn } })).ToList(); - joins.Add(string.Format(" {0} JOIN " + database.DatabaseType.EscapeTableName(member.PocoColumn.TableInfo.TableName) + " " + database.DatabaseType.EscapeTableName(member.PocoColumn.TableInfo.AutoAlias) + " ON " + joinSqlExpression.OnSql, joinSqlExpression.JoinType == JoinType.Inner ? "INNER" : "LEFT")); + joins.Add(string.Format(" {0} JOIN " + database.DatabaseType.EscapeTableName(member.PocoColumn.TableInfo.TableName) + " " + database.DatabaseType.EscapeTableName(member.PocoColumn.TableInfo.AutoAlias) + joinSqlExpression.Hint + " ON " + joinSqlExpression.OnSql, joinSqlExpression.JoinType == JoinType.Inner ? "INNER" : "LEFT")); } return joins.Any() ? " \n" + string.Join(" \n", joins.ToArray()) : string.Empty; } - public Dictionary GetJoinExpressions(Expression expression, string tableAlias, JoinType joinType) + public Dictionary GetJoinExpressions(Expression expression, string tableAlias, JoinType joinType, string hint) { var memberInfos = MemberChainHelper.GetMembers(expression); var members = _pocoData.Members; @@ -167,7 +167,8 @@ public Dictionary GetJoinExpressions(Expression expression, st PocoMember = pocoMember, PocoMemberJoin = pocoMember2, PocoMembers = pocoMember.PocoMemberChildren, - JoinType = joinType + JoinType = joinType, + Hint = hint == string.Empty ? hint : " " + hint }); } diff --git a/src/NPoco/Linq/JoinData.cs b/src/NPoco/Linq/JoinData.cs index e25f53c3..44a6bb04 100644 --- a/src/NPoco/Linq/JoinData.cs +++ b/src/NPoco/Linq/JoinData.cs @@ -11,5 +11,6 @@ public class JoinData public PocoMember PocoMemberJoin { get; set; } public List PocoMembers { get; set; } public JoinType JoinType { get; set; } + public string Hint { get; set; } } } \ No newline at end of file diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index 1628e4d9..d7780999 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -107,20 +107,22 @@ public interface IAsyncQueryProvider : IAsyncQueryResultProvider public interface IAsyncQueryProviderWithIncludes : IAsyncQueryProvider { - IAsyncQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left); - IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class; - IAsyncQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class; - IAsyncQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left) where T2 : class; - IAsyncQueryProviderWithIncludes UsingAlias(string empty); + IAsyncQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = ""); + IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class; + IAsyncQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class; + IAsyncQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class; + IAsyncQueryProviderWithIncludes UsingAlias(string tableAlias); + IAsyncQueryProviderWithIncludes Hint(string tableHint); } public interface IQueryProviderWithIncludes : IQueryProvider { - IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left); - IQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class; - IQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class; - IQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left) where T2 : class; - IQueryProviderWithIncludes UsingAlias(string empty); + IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = ""); + IQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class; + IQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class; + IQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class; + IQueryProviderWithIncludes UsingAlias(string tableAlias); + IQueryProviderWithIncludes Hint(string tableHint); } public class AsyncQueryProvider : IAsyncQueryProviderWithIncludes, ISimpleQueryProviderExpression, INeedDatabase, INeedSql @@ -164,13 +166,13 @@ protected Sql BuildSql() return sql; } - public IAsyncQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left) + public IAsyncQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = "") { _listExpression = expression; - return QueryProviderWithIncludes(expression, null, joinType); + return QueryProviderWithIncludes(expression, null, joinType, joinTableHint); } - public IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class + public IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class { var oneToOneMembers = _database.PocoDataFactory.ForType(typeof(T)) .Members.Where(x => (x.ReferenceType == ReferenceType.OneToOne || x.ReferenceType == ReferenceType.Foreign) @@ -180,20 +182,20 @@ public IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinTy { var entityParam = Expression.Parameter(typeof(T), "entity"); var joinProperty = Expression.Lambda>(Expression.PropertyOrField(entityParam, o2oMember.Name), entityParam); - Include(joinProperty, joinType); + Include(joinProperty, joinType, joinTableHint); } return this; } - public IAsyncQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class + public IAsyncQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class { - return QueryProviderWithIncludes(expression, null, joinType); + return QueryProviderWithIncludes(expression, null, joinType, joinTableHint); } - public IAsyncQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left) where T2 : class + public IAsyncQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class { - return QueryProviderWithIncludes(expression, tableAlias, joinType); + return QueryProviderWithIncludes(expression, tableAlias, joinType, joinTableHint); } public IAsyncQueryProviderWithIncludes UsingAlias(string tableAlias) @@ -203,9 +205,15 @@ public IAsyncQueryProviderWithIncludes UsingAlias(string tableAlias) return this; } - private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression expression, string tableAlias, JoinType joinType) + public IAsyncQueryProviderWithIncludes Hint(string tableHint) { - var joinExpressions = _buildComplexSql.GetJoinExpressions(expression, tableAlias, joinType); + _sqlExpression.TableHint(tableHint); + return this; + } + + private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression expression, string tableAlias, JoinType joinType, string joinTableHint) + { + var joinExpressions = _buildComplexSql.GetJoinExpressions(expression, tableAlias, joinType, joinTableHint); foreach (var joinExpression in joinExpressions) { _joinSqlExpressions[joinExpression.Key] = joinExpression.Value; @@ -725,29 +733,34 @@ public Task> DistinctAsync() return base.Distinct(); } - public new IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left) + public new IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = "") + { + return (IQueryProvider)base.IncludeMany(expression, joinType, joinTableHint); + } + + public new IQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class { - return (IQueryProvider)base.IncludeMany(expression, joinType); + return (IQueryProviderWithIncludes)base.Include(joinType, joinTableHint); } - public new IQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class + public new IQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class { - return (IQueryProviderWithIncludes)base.Include(joinType); + return (IQueryProviderWithIncludes)base.Include(expression, joinType, joinTableHint); } - public new IQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class + public new IQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left, string joinTableHint = "") where T2 : class { - return (IQueryProviderWithIncludes)base.Include(expression, joinType); + return (IQueryProviderWithIncludes)base.Include(expression, tableAlias, joinType, joinTableHint); } - public new IQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left) where T2 : class + public new IQueryProviderWithIncludes UsingAlias(string tableAlias) { - return (IQueryProviderWithIncludes)base.Include(expression, tableAlias, joinType); + return (IQueryProviderWithIncludes)base.UsingAlias(tableAlias); } - public new IQueryProviderWithIncludes UsingAlias(string empty) + public new IQueryProviderWithIncludes Hint(string tableHint) { - return (IQueryProviderWithIncludes)base.UsingAlias(empty); + return (IQueryProviderWithIncludes)base.Hint(tableHint); } public new IQueryProvider Where(Expression> whereExpression) diff --git a/src/NPoco/MapperCollection.cs b/src/NPoco/MapperCollection.cs index 7dfa1357..fb9c7f0f 100644 --- a/src/NPoco/MapperCollection.cs +++ b/src/NPoco/MapperCollection.cs @@ -7,6 +7,7 @@ namespace NPoco { public class MapperCollection : List { + public IColumnSerializer ColumnSerializer { get; set; } = DatabaseFactory.ColumnSerializer; internal readonly Dictionary Factories = new Dictionary(); public delegate object ObjectFactoryDelegate(DbDataReader dataReader); diff --git a/src/NPoco/MappingHelper.cs b/src/NPoco/MappingHelper.cs index 26b8c865..4e539152 100644 --- a/src/NPoco/MappingHelper.cs +++ b/src/NPoco/MappingHelper.cs @@ -23,19 +23,16 @@ public static Func GetConverter(MapperCollection mapper, PocoCol return converter; } - if (pc != null && pc.SerializedColumn) + if (pc != null && pc.SerializedColumn && mapper?.ColumnSerializer != null) { - converter = delegate(object src) - { - return DatabaseFactory.ColumnSerializer.Deserialize((string) src, dstType); - }; + converter = src => mapper.ColumnSerializer.Deserialize((string) src, dstType); return converter; } // Standard DateTime->Utc mapper if (pc != null && pc.ForceToUtc && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) { - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; + converter = src => new DateTime(((DateTime) src).Ticks, DateTimeKind.Utc); return converter; } @@ -71,14 +68,13 @@ static bool IsIntegralType(Type t) //var tc = Type.GetTypeCode(t); //return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; //Not available for now - return new[] - { - typeof (SByte), typeof (Byte), - typeof (Int16), typeof (UInt16), - typeof (Int32), typeof (UInt32), - typeof (Int64), typeof (UInt64) - }.Contains(t); + { + typeof(SByte), typeof(Byte), + typeof(Int16), typeof(UInt16), + typeof(Int32), typeof(UInt32), + typeof(Int64), typeof(UInt64) + }.Contains(t); } public static object GetDefault(Type type) diff --git a/src/NPoco/NPoco.csproj b/src/NPoco/NPoco.csproj index 4b5caf72..33678ba6 100644 --- a/src/NPoco/NPoco.csproj +++ b/src/NPoco/NPoco.csproj @@ -5,7 +5,7 @@ net461;netstandard2.0;netstandard2.1 NPoco NPoco - 5.0.0 + 5.3.0 Adam Schröder orm;sql;micro-orm;database;mvc https://github.com/schotime/NPoco @@ -17,7 +17,7 @@ false false false - 8.0 + latest diff --git a/src/NPoco/NullFastCreate.cs b/src/NPoco/NullFastCreate.cs new file mode 100644 index 00000000..b6fdb7d3 --- /dev/null +++ b/src/NPoco/NullFastCreate.cs @@ -0,0 +1,13 @@ +using System; +using System.Data.Common; + +namespace NPoco +{ + public class NullFastCreate : IFastCreate + { + public object Create(DbDataReader dataReader) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/NPoco/PocoData.cs b/src/NPoco/PocoData.cs index 2e54ff5d..42b0e006 100644 --- a/src/NPoco/PocoData.cs +++ b/src/NPoco/PocoData.cs @@ -17,16 +17,12 @@ public class PocoData public TableInfo TableInfo { get; protected internal set; } public Dictionary Columns { get; protected internal set; } public List Members { get; protected internal set; } - public List AllColumns { get; protected internal set; } + private IFastCreate CreateDelegate { get; } // This is used on a per query basis, if we have cache PocoData then this will need to change. public bool IsQueryGenerated { get; set; } - public PocoData() - { - } - - public PocoData(Type type, MapperCollection mapper, FastCreate creator) : this() + public PocoData(Type type, MapperCollection mapper, IFastCreate creator) { CreateDelegate = creator; Type = type; @@ -78,7 +74,5 @@ public object CreateObject(DbDataReader dataReader) { return CreateDelegate.Create(dataReader); } - - private FastCreate CreateDelegate { get; set; } } } diff --git a/src/NPoco/PocoDataBuilder.cs b/src/NPoco/PocoDataBuilder.cs index 738a97e1..f59e8f7e 100644 --- a/src/NPoco/PocoDataBuilder.cs +++ b/src/NPoco/PocoDataBuilder.cs @@ -17,7 +17,7 @@ public interface InitializedPocoDataBuilder public class PocoDataBuilder : InitializedPocoDataBuilder { private readonly Cache _aliasToType = Cache.CreateStaticCache(); - private FastCreate _generator; + private IFastCreate _generator; protected Type Type { get; set; } private MapperCollection Mapper { get; set; } @@ -79,11 +79,8 @@ PocoData InitializedPocoDataBuilder.Build() var pocoData = new PocoData(Type, Mapper, _generator); pocoData.TableInfo = _tableInfoPlan(); - pocoData.Members = _memberPlans.Select(plan => plan(pocoData.TableInfo)).ToList(); - - pocoData.Columns = GetPocoColumns(pocoData.Members, false).Where(x => x != null).ToDictionary(x => x.ColumnName, x => x, StringComparer.OrdinalIgnoreCase); - pocoData.AllColumns = GetPocoColumns(pocoData.Members, true).Where(x => x != null).ToList(); + pocoData.Columns = GetPocoColumns(pocoData.Members).Where(x => x != null).ToDictionary(x => x.ColumnName, x => x, StringComparer.OrdinalIgnoreCase); //Build column list for automatic select pocoData.QueryColumns = pocoData.Columns.Where(c => !c.Value.ResultColumn && c.Value.ReferenceType == ReferenceType.None).ToArray(); @@ -96,7 +93,7 @@ protected virtual TableInfoPlan GetTableInfo(Type type, ColumnInfo[] columnInfos var alias = CreateAlias(type.Name, type); var tableInfo = TableInfo.FromPoco(type); tableInfo.AutoAlias = alias; - return () => { return tableInfo.Clone(); }; + return () => tableInfo.Clone(); } protected virtual ColumnInfo GetColumnInfo(MemberInfo mi, Type type) @@ -104,21 +101,23 @@ protected virtual ColumnInfo GetColumnInfo(MemberInfo mi, Type type) return ColumnInfo.FromMemberInfo(mi); } - private static IEnumerable GetPocoColumns(IEnumerable members, bool all) + private static IEnumerable GetPocoColumns(IEnumerable members) { foreach (var member in members) { - if (all || (member.ReferenceType != ReferenceType.OneToOne - && member.ReferenceType != ReferenceType.Many)) - { - yield return member.PocoColumn; - } - - if (all || (member.ReferenceType == ReferenceType.None)) + switch (member.ReferenceType) { - foreach (var pocoMemberChild in GetPocoColumns(member.PocoMemberChildren, all)) + case ReferenceType.Foreign: + yield return member.PocoColumn; + break; + case ReferenceType.None: { - yield return pocoMemberChild; + yield return member.PocoColumn; + foreach (var pocoMemberChild in GetPocoColumns(member.PocoMemberChildren)) + { + yield return pocoMemberChild; + } + break; } } } diff --git a/src/NPoco/PocoDataFactory.cs b/src/NPoco/PocoDataFactory.cs index 64e39d1d..ffb65016 100644 --- a/src/NPoco/PocoDataFactory.cs +++ b/src/NPoco/PocoDataFactory.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data.Common; namespace NPoco { @@ -13,11 +12,13 @@ public interface IPocoDataFactory public class FluentPocoDataFactory : IPocoDataFactory { + private readonly MapperCollection _mapperCollection; private readonly Cache _pocoDatas = Cache.CreateStaticCache(); public Func Resolver { get; private set; } - public FluentPocoDataFactory(Func resolver) + public FluentPocoDataFactory(Func resolver, MapperCollection mapperCollection) { + _mapperCollection = mapperCollection; Resolver = resolver; } @@ -37,7 +38,7 @@ public TableInfo TableInfoForType(Type type) public PocoData ForObject(object o, string primaryKeyName, bool autoIncrement) { - return PocoDataFactory.ForObjectStatic(o, primaryKeyName, autoIncrement, ForType); + return PocoDataFactory.ForObjectStatic(o, primaryKeyName, autoIncrement, ForType, _mapperCollection); } private InitializedPocoDataBuilder BaseClassFalbackPocoDataBuilder(Type type) @@ -78,7 +79,7 @@ public TableInfo TableInfoForType(Type type) public PocoData ForObject(object o, string primaryKeyName, bool autoIncrement) { - return ForObjectStatic(o, primaryKeyName, autoIncrement, ForType); + return ForObjectStatic(o, primaryKeyName, autoIncrement, ForType, _mapper); } private InitializedPocoDataBuilder BaseClassFallbackPocoDataBuilder(Type type) @@ -92,12 +93,12 @@ private InitializedPocoDataBuilder BaseClassFallbackPocoDataBuilder(Type type) return new PocoDataBuilder(persistedType, _mapper).Init(); } - public static PocoData ForObjectStatic(object o, string primaryKeyName, bool autoIncrement, Func fallback) + public static PocoData ForObjectStatic(object o, string primaryKeyName, bool autoIncrement, Func fallback, MapperCollection mapper) { var t = o.GetType(); if (t == typeof (System.Dynamic.ExpandoObject) || t == typeof (PocoExpando)) { - var pd = new PocoData + var pd = new PocoData(t, mapper, Singleton.Instance) { TableInfo = new TableInfo { diff --git a/src/NPoco/RowMappers/PropertyMapperNameConvention.cs b/src/NPoco/RowMappers/PropertyMapperNameConvention.cs index cfd37196..2d024512 100644 --- a/src/NPoco/RowMappers/PropertyMapperNameConvention.cs +++ b/src/NPoco/RowMappers/PropertyMapperNameConvention.cs @@ -36,7 +36,7 @@ internal static IEnumerable ConvertFromNewConvention(this IEnumerable

pocoMemb { return pocoMembers .Where(x => x.PocoMember.ReferenceType == ReferenceType.None) - .FirstOrDefault(x => IsPocoMemberEqual(x.PocoMember, name)); + .FirstOrDefault(x => IsPocoMemberEqual(x.PocoMember, string.Empty, name)); } - internal static PocoMember FindMember(IEnumerable pocoMembers, string name) + internal static PocoMember FindMember(IEnumerable pocoMembers, string prefix, string name) { return pocoMembers .Where(x => x.ReferenceType == ReferenceType.None) - .FirstOrDefault(x => IsPocoMemberEqual(x, name)); + .FirstOrDefault(x => IsPocoMemberEqual(x, prefix, name)); } - private static bool IsPocoMemberEqual(PocoMember pocoMember, string name) + private static bool IsPocoMemberEqual(PocoMember pocoMember, string prefix, string name) { if (pocoMember.PocoColumn == null) return PropertyMapper.IsEqual(name, pocoMember.Name, false); @@ -94,6 +94,9 @@ private static bool IsPocoMemberEqual(PocoMember pocoMember, string name) if (pocoMember.PocoColumn.MemberInfoKey == name) return true; + if (string.Equals(pocoMember.PocoColumn.ColumnName, PocoDataBuilder.JoinStrings(prefix, name), StringComparison.OrdinalIgnoreCase)) + return true; + if (PropertyMapper.IsEqual(name, pocoMember.PocoColumn.ColumnAlias ?? pocoMember.PocoColumn.ColumnName, pocoMember.PocoColumn.ExactColumnNameMatch)) return true; diff --git a/src/NPoco/Snapshotter.cs b/src/NPoco/Snapshotter.cs index 72494a79..a1a953bd 100644 --- a/src/NPoco/Snapshotter.cs +++ b/src/NPoco/Snapshotter.cs @@ -77,7 +77,7 @@ public List Changes() return list; } - private static bool AreEqual(object first, object second) + private bool AreEqual(object first, object second) { if (first == null && second == null) return true; if (first == null) return false; @@ -86,7 +86,7 @@ private static bool AreEqual(object first, object second) var type = first.GetType(); if (type.IsAClass() || type.IsArray) { - return DatabaseFactory.ColumnSerializer.Serialize(first) == DatabaseFactory.ColumnSerializer.Serialize(second); + return _pocoData.Mapper.ColumnSerializer.Serialize(first) == _pocoData.Mapper.ColumnSerializer.Serialize(second); } return first.Equals(second); diff --git a/test/NPoco.Tests/Async/QueryAsyncTests.cs b/test/NPoco.Tests/Async/QueryAsyncTests.cs index 9d075709..ef4a388b 100644 --- a/test/NPoco.Tests/Async/QueryAsyncTests.cs +++ b/test/NPoco.Tests/Async/QueryAsyncTests.cs @@ -61,5 +61,14 @@ public async Task QueryAsyncSql() Assert.AreEqual(item.UserId, i++); } } + + [Test] + public async Task QueryAsyncFirst() + { + var user = await Database.FirstOrDefaultAsync("where userid = @0", 1); + Assert.AreEqual(1, user.UserId); + var user1 = await Database.FirstAsync("where userid = @0", 1); + Assert.AreEqual(1, user1.UserId); + } } } diff --git a/test/NPoco.Tests/Common/BaseDBFluentTest.cs b/test/NPoco.Tests/Common/BaseDBFluentTest.cs index a6b15168..bb1d1772 100644 --- a/test/NPoco.Tests/Common/BaseDBFluentTest.cs +++ b/test/NPoco.Tests/Common/BaseDBFluentTest.cs @@ -239,6 +239,10 @@ public FluentMappingOverrides() }); For().UseMap(); For().TableName("users").Columns(x => x.Column(y => y.IsMale).WithName("is_male")); + For().Columns(x => + { + x.Column(y => y.Name).WithName("myname"); + }); } } diff --git a/test/NPoco.Tests/Common/User.cs b/test/NPoco.Tests/Common/User.cs index eb719b73..0c6b26c9 100644 --- a/test/NPoco.Tests/Common/User.cs +++ b/test/NPoco.Tests/Common/User.cs @@ -88,4 +88,10 @@ public UserWithPrivateParamLessConstructor(int userId) UserId = userId; } } + + public class UserFallback + { + public string Name { get; set; } + } + } diff --git a/test/NPoco.Tests/DatabaseFactoryTests.cs b/test/NPoco.Tests/DatabaseFactoryTests.cs index 309717d3..779931d1 100644 --- a/test/NPoco.Tests/DatabaseFactoryTests.cs +++ b/test/NPoco.Tests/DatabaseFactoryTests.cs @@ -54,7 +54,8 @@ public void MapperShouldBePlacedOnDatabaseWhenInsertedIntoFactoryConfigWhenCalli public void FluentConfigShouldBePlacedOnDatabaseWhenInsertedIntoFactoryConfig() { var db = new Database(new SqlConnection()); - var pocoDataFactory = new FluentPocoDataFactory((y,f) => new PocoDataBuilder(y, new MapperCollection()).Init()); + var mapperCollection = new MapperCollection(); + var pocoDataFactory = new FluentPocoDataFactory((y,f) => new PocoDataBuilder(y, mapperCollection).Init(), mapperCollection); var fluentConfig = new FluentConfig(x=>pocoDataFactory); var factory = DatabaseFactory.Config(x => diff --git a/test/NPoco.Tests/DatabaseTypeTests.cs b/test/NPoco.Tests/DatabaseTypeTests.cs new file mode 100644 index 00000000..73932fa6 --- /dev/null +++ b/test/NPoco.Tests/DatabaseTypeTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using NPoco; +using NPoco.DatabaseTypes; +using NUnit.Framework; + +namespace NPoco.Tests +{ + [TestFixture] + public class DatabaseTypeTests + { + [Test] + public void AddOrderByIfNoneFound() + { + var dbType = new SqlServer2012DatabaseType(); + + var args = new object[] {1}; + var pagedQuery = dbType.BuildPageQuery(0, 10, new PagingHelper.SQLParts { sql = "select * from test where id = @0" }, ref args); + + Assert.AreEqual(pagedQuery, "select * from test where id = @0\nORDER BY (SELECT NULL)\nOFFSET @1 ROWS FETCH NEXT @2 ROWS ONLY"); + Assert.AreEqual(args, new object[] {1, 0, 10}); + } + + [Test] + public void AddPagingOnWithExistingOrderBy() + { + var dbType = new SqlServer2012DatabaseType(); + + var args = new object[] { 1 }; + var pagedQuery = dbType.BuildPageQuery(0, 10, new PagingHelper.SQLParts { sql = "select * from test where id = @0 order by 1" }, ref args); + + Assert.AreEqual(pagedQuery, "select * from test where id = @0 order by 1\nOFFSET @1 ROWS FETCH NEXT @2 ROWS ONLY"); + Assert.AreEqual(args, new object[] { 1, 0, 10 }); + } + + [Test] + public void SupportNestedOrderBys() + { + var dbType = new SqlServer2012DatabaseType(); + + var args = new object[] { 1 }; + var pagedQuery = dbType.BuildPageQuery(0, 10, new PagingHelper.SQLParts + { + sql = "select * from test outer apply (select top 1 1 from test2 order by 2) where id = @0" + }, ref args); + + Assert.AreEqual("select * from test outer apply (select top 1 1 from test2 order by 2) where id = @0\nORDER BY (SELECT NULL)\nOFFSET @1 ROWS FETCH NEXT @2 ROWS ONLY", pagedQuery); + Assert.AreEqual(args, new object[] { 1, 0, 10 }); + } + } +} diff --git a/test/NPoco.Tests/FluentTests/QueryTests/FetchAndQueryFluentTest.cs b/test/NPoco.Tests/FluentTests/QueryTests/FetchAndQueryFluentTest.cs index 3aba14ee..902e14dc 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/FetchAndQueryFluentTest.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/FetchAndQueryFluentTest.cs @@ -123,5 +123,26 @@ public void EmptyInQueryWithGuids() var data = Database.Fetch("select * from users where uniqueid in (@1) and userid = @0 and name = @2", 1, list, "name"); Assert.AreEqual(0, data.Count); } + + [Test] + public void FallbackUser() + { + var data = Database.Fetch("select 'thename' myname"); + Assert.AreEqual(1, data.Count); + Assert.AreEqual("thename", data[0].Name); + } + + [Test] + public void FallbackUserNested() + { + var data = Database.Fetch("select null npoco_User, 'thename' myname"); + Assert.AreEqual(1, data.Count); + Assert.AreEqual("thename", data[0].User.Name); + } + + public class FallbackDto + { + public UserFallback User { get; set; } + } } } diff --git a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs index 82851c68..ee752c2b 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs @@ -587,6 +587,30 @@ public void QueryWithDateTimeNullable() Assert.AreEqual(15, users2.Count); } + [Test] + public void QueryWithHints() + { + var users = Database.Query().Hint("(nolock)"); + var sql = ((INeedSql)users).GetSql(); + + Assert.True(sql.SQL.Contains("(nolock)")); + Assert.AreEqual(15, users.ToList().Count); + } + + [Test] + public void QueryWithHintsOnJoin() + { + var users = Database.Query() + .Hint("(nolock)") + .Include(x => x.House, joinTableHint: "with (nolock)"); + + var sql = ((INeedSql)users).GetSql(); + + Assert.True(sql.SQL.Contains("(nolock)")); + Assert.True(sql.SQL.Contains("with (nolock)")); + Assert.AreEqual(15, users.ToList().Count); + } + //[Test] //public void QueryWithInheritedTypesAliasCorrectlyWithJoin() //{ @@ -611,21 +635,4 @@ public class Usersss { public int UserId { get; set; } } -} - -//VB Tests for query provider -//Imports NPoco -//Imports NPoco.Expressions - -//Module Module1 -// Sub Main() -// Dim Db = New Database("asdf", "Microsoft.Data.SqlClient") -// Dim exp = New DefaultSqlExpression (Of User)(Db) -// Dim whered = exp.Where(Function(item) (item.Name = "Test")) -// Console.WriteLine(whered.Context.ToSelectStatement()) -// End Sub -//End Module - -//Public Class User -// Public Property Name As String -//End Class +} \ No newline at end of file diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index 29523c38..666beb22 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using NPoco.Expressions; using NPoco.fastJSON; using NPoco.Linq; @@ -643,6 +644,47 @@ public class Nest3 } } + [Test] + public void Test28A() + { + Database.Mappers.Add(new TestMapper()); + var data = Database.Fetch("select null npoco_NestMe3, 'something' as Y").Single(); + Assert.AreEqual("something", data.NestMe3.Y.X); + } + + public class TestMapper : DefaultMapper + { + public override Func GetFromDbConverter(MemberInfo destType, Type sourceType) + { + if (destType.GetMemberInfoType() == typeof(Test28AClass.Nest4) && sourceType == typeof(string)) + { + return x => + { + return new Test28AClass.Nest4 { X = (string)x }; + }; + } + + return base.GetFromDbConverter(destType, sourceType); + } + } + + public class Test28AClass + { + public Nest3 NestMe3 { get; set; } + + public class Nest3 + { + public string Z { get; set; } + public Nest4 Y { get; set; } + } + + [ComplexMapping(ComplexMapping = false)] + public class Nest4 + { + public string X { get; set; } + } + } + [Test] public void Test29() {