jonwagner / Insight.Database

Fast, lightweight .NET micro-ORM
Other
861 stars 145 forks source link

Using ColumnMapping.Tables.XYZ<TClass>() methods break unrelated type #388

Closed DSANTIMORE closed 4 years ago

DSANTIMORE commented 5 years ago

When using type-constraint helper methods (e.g., ColumnMapping.Tables.RemoveString("_")), I end up with ArgumentNullExceptions in TransformDatabaseName() on unrelated types.

As far as I can tell, when using ColumnMapping.Tables.RemoveStrings("_"), this is having a magical effect when attempting to use Query< TypeB >(). When I comment out all of the ColumnMapping... methods, the problem goes away (but of course, TypeA is not mapped correctly anymore).

Specifically, I get a runtime ArgumentNullException in TransformDatabaseName indicating databaseName is null.

Steps to reproduce (if applicable)

Jaxelr commented 5 years ago

Could you post a repro?

DSANTIMORE commented 5 years ago

I can't post a full working repro, but here's the anonymized code. Note that "StoredProcA" and "StoredProcB" apparently select against the same DB table; unsure if that is relevant here. However, the documentation and syntax suggests we're limiting the mapping to a specified C# type.

`public class MyRepo { private readonly string _connectionString;

    public MyRepo(string connectionString)
    {
        if(String.IsNullOrWhiteSpace(connectionString))
            throw new ArgumentNullException(nameof(connectionString));

         _connectionString = connectionString;

        //Make sure that the correct provider is registered. This goes wrong at times in multi-assembly scenarios.
        SqlInsightDbProvider.RegisterProvider();
        ColumnMapping.Tables.RemoveStrings<TypeA>("_");
    }

    public TypeB GetTypeB(typeBId) 
    {
        //ArgumentNull exception is thrown here.
        //No mapping is required on TypeB.
        //Stack trace:
        /*
               at Insight.Database.RegexReplaceTransform`1.TransformDatabaseName(Type type, String databaseName)
               at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
               at Insight.Database.ColumnMapping.MapColumn(Type type, IDataReader reader, Int32 column, IColumnMapper overrideMapping)
               at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
               at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
               at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
               at Insight.Database.ColumnMapping.MapColumns(Type type, IDataReader reader, Int32 startColumn, Nullable`1 columnCount, IColumnMapper overrideMapping)
               at Insight.Database.CodeGenerator.ClassDeserializerGenerator.MapColumns(Type type, IDataReader reader, Int32 startColumn, Int32 columnCount, IRecordStructure structure, Boolean allowBindChild)
               at Insight.Database.CodeGenerator.ClassDeserializerGenerator.CreateClassDeserializerDynamicMethod(Type type, IDataReader reader, IRecordStructure structure, Int32 startColumn, Int32 columnCount, Boolean createNewObject, Boolean isRootObject, Boolean allowBindChild)
               at Insight.Database.CodeGenerator.ClassDeserializerGenerator.CreateClassDeserializer(Type type, IDataReader reader, IRecordStructure structure, Int32 startColumn, Int32 columnCount, Boolean createNewObject)
               at Insight.Database.CodeGenerator.ClassDeserializerGenerator.CreateDeserializer(IDataReader reader, Type type, IRecordStructure structure, SchemaMappingType mappingType)
               at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
               at Insight.Database.CodeGenerator.DbReaderDeserializer.GetDeserializer(IDataReader reader, Type type, IRecordStructure structure, SchemaMappingType mappingType)
               at Insight.Database.CodeGenerator.DbReaderDeserializer.GetDeserializer[T](IDataReader reader, IRecordStructure structure)
               at Insight.Database.DBReaderExtensions.Single[T](IDataReader reader, IRecordReader`1 recordReader)
               at Insight.Database.Structure.SingleReader`1.Read(IDbCommand command, IDataReader reader)
               at Insight.Database.DBConnectionExtensions.<>c__DisplayClass168_0`1.<Query>b__1(IDbCommand cmd, IDataReader r)
               at Insight.Database.DBConnectionExtensions.ExecuteAndAutoClose[T](IDbConnection connection, Func`2 getCommand, Func`3 translate, CommandBehavior commandBehavior)
               at Insight.Database.DBConnectionExtensions.Query[T](IDbConnection connection, String sql, Object parameters, IQueryReader`1 returns, CommandType commandType, CommandBehavior commandBehavior, Nullable`1 commandTimeout, IDbTransaction transaction, Object outputParameters)
               at Insight.Database.DBConnectionExtensions.Single[T1](IDbConnection connection, String sql, Object parameters, CommandType commandType, CommandBehavior commandBehavior, Nullable`1 commandTimeout, IDbTransaction transaction, Object outputParameters)
               at Blah.Blah.Blah.GetTypeB(Int32 typeBId) in Blah
        */
        return new SqlConnection(_connectionString).Single<TypeB>("StoredProcB", id:typeBId);
    }

    public TypeA GetTypeA(typeAId) 
    {
        return new SqlConnection(_connectionString).Single<TypeA>("StoredProcA", id:typeAId);
    }
}`
DSANTIMORE commented 5 years ago

I've confirmed that the problem persists even if the "StoredProcA" selects against a completely different table.

It seems that having any ColumnMapping.Tables registered sends the code down a different path that expects something regardless of the c# type being materialized.

jonwagner commented 5 years ago

Yeah it's possible that it's going down another code path. I won't have time to dive in for the next few weeks, but if someone dives in and makes a fix, I can do a build.

DSANTIMORE commented 5 years ago

I will attempt to look into it this week.

jonwagner commented 4 years ago

I put a potential fix for this in 6.2.11