jonwagner / Insight.Database

Fast, lightweight .NET micro-ORM

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Type Conversion Exceptions with Get-Only Properties

jdupont opened this issue · comments

Describe the bug

We're seeing type conversion exceptions between database columns and get-only properties, even though Insight doesn't actually populate get-only properties with database values. Specifically, if a get-only property maps to a column in table, and the column's type isn't compatible with the property's type, InsightDb will throw an exception. But, if the types are compatible, InsightDb won't populate the get-only property with the database values. It seems like the mapping shouldn't be throwing an exception since there won't be any assignment happening.

We've been upgrading from v4.1.4 to v6.3.6, and v4.1.4 didn't throw these exceptions for get-only properties (not that it really narrows down the range of changes much). This issue isn't blocking our update, but it seems potentially surprising since the mapping exceptions should be consistent with property assignment.

Also -- Jon's quick fix on this previous issue really helped us out on this upgrade, so thanks again for that!

Steps to reproduce

Unit test reproducing the exception is in this commit.

The specific incompatible types in the unit test aren't really relevant -- this seems to happen for any pairs of incompatible types.

Expected behavior

No type-mapping exception for get-only properties, even if property type is incompatible with column type. Insight won't assign values from the column to the get-only property, so the incompatibility doesn't seem like it should cause an exception.

  • Dotnet version: dotnet48
  • Database: SQL Server
  • Library version: 6.3.6

I might be missing something, but it doesn't seem like Insight is populating get-only properties? For example, this test will pass:

[Test]
public void GetOnlyProperty()
{
    try
    {
        Connection().ExecuteSql(
	        @"CREATE TABLE [ItemTable](
				[ItemName] [varchar](50) NOT NULL
			);
			INSERT INTO [ItemTable] ([ItemName]) VALUES ('My Item Name');");
        
        var nullName = Connection().QuerySql<MyItem>("SELECT [ItemName] FROM [ItemTable];");
        Assert.Null(nullName.First().ItemName);
    }
    finally
    {
        Connection().ExecuteSql("DROP TABLE [ItemTable]");
    }
}

private class MyItem
{
    public string ItemName { get; }
}

And then adding a setter to ItemName causes Insight to populate that property with the value My Item Name from the database.

I definitely agree that all of the fixes you suggested mitigate the type mapping exceptions, so this definitely isn't a critical issue. It mostly just feels like the behavior of the mapping is inconsistent with how data is actually being populated.

Alright, I refreshed my memory....

public int X { get; private set; } // creates a private setter which Insight has access to
public int X { get; } // doesn't create a setter. there is a backing field called __k_backing_X or something (Insight does not use)

Either way, the mapping engine is performing the read from the recordset and then attempting type conversion. If there's no setter (as in the second case) this is at least a minor performance loss, as no field is set.

Looking for the right place to fix this.

This will be a little tricky to fix. The best place is in ClassDeserializerGenerator.CreateClassDeserializerDynamicMethod.

Columns can map to either class fields/properties or constructor arguments.

Currently, it's implemented as:

  1. Get all mappable columns and convert them to target types. Store in local variables.
  2. Stack any local variables matching constructor arguments. Call the constructor.
  3. Loop through all settable properties and store them to the correct property/field.

Since the column maps to the get property it is eligible for constructor setting. We'll have to do some refactoring in that method. But since it's the core IL generator it will take some careful work.

Ah OK. And we can't filter out the get-only properties during the mappable column step? Like in ClassPropInfo.SearchForMatchingField?

This is fixed in 6.3.7