DapperLib / DapperLib/Dapper

Custom Type Handler works correctly according to position of columns in the SELECT statment

Open
#1,036 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
18.4k
Forks
3.7k
Avg merge
5h 8m
Merged PRs (30d)
1

Description

Hi everyone,
I am new to dapper and I met a special case. I write immediately my code.

I wrote a custom type handler to manage spatial data with nuget package "Microsoft.Spatial", that is sql independent and support odata v4. Then I have registered it. Here the code of the handler:

public class GeometryPointTypeHandler : SqlMapper.TypeHandler<GeometryPoint>
{
    //      POINT(X Y)
    //      POINT(X Y Z M)
    public override GeometryPoint Parse(object value)
    {
        if (value == null)
            return null;

        if (!Regex.IsMatch(value.ToString(), @"^(POINT \()(.+)(\))"))
            throw new Exception("Value is not a Geometry Point");

        //Get values inside the brackets
        string geometryPoints = value.ToString().Split('(', ')')[1];

        //Split values by empty space
        string[] geometryValues = geometryPoints.Split(' ');

        double x = this.ConvertToDouble(geometryValues[0]);
        double y = this.ConvertToDouble(geometryValues[1]);

        double? z = null;
        if (geometryValues.Length >= 3)
            z = this.ConvertToDouble(geometryValues[2]);

        double? m = null;
        if (geometryValues.Length >= 4)
            m = this.ConvertToDouble(geometryValues[3]);

        return GeometryPoint.Create(x, y, z, m);
    }

    public override void SetValue(IDbDataParameter parameter, GeometryPoint value)
    {
        throw new NotImplementedException();
    }

    private double ConvertToDouble(string value)
    {
        return double.Parse(value, CultureInfo.InvariantCulture);
    }
}

I created a custom POCO class where I map the result of the query. The class is the following:

internal class RequestInfo
    {
        public long Id { get; set; }
        public string StatusCode { get; set; }
        
        public DateInfo DepartureDate { get; set; }
        public DateInfo ArrivalDate { get; set; }

        public GeometryPoint DepartureCoordinates { get; set; }
        public GeometryPoint ArrivalCoordinates { get; set; }

        public int NumberOfItems
        {
            get
            {
                if (this.Items != null)
                    return this.Items.Count;

                return 0;
            }
        }

        public List<RequestItemInfo> Items { get; set; }
    }

where RequestItemInfo and DateInfo are custom classes. I let you see just DateInfo because I think the other is not importat for the issue:

internal class DateInfo
    {
        public long DateId { get; set; }
        public DateTime Date { get; set; }
        public bool IsFlexible { get; set; }
    }

Ok, now I let you see the query gives me problems:

string sql =
		 @"SELECT search.Id AS Id, status.Code AS StatusCode,
				  departureAddress.GeometryLocation.STAsText() AS DepartureCoordinates,
				  arrivalAddress.GeometryLocation.STAsText() AS ArrivalCoordinates,
				  departureDate.Id AS DateId, departureDate.Date, departureDate.IsFlexible, 
				  arrivalDate.Id AS DateId, arrivalDate.Date, arrivalDate.IsFlexible,
				  item.Id AS ItemId, item.Quantity AS Quantity,
				  attribute.Id AS AttributeId, attribute.Name AS Name, attribute.Value AS Value
			 FROM RequestSearch search
			 JOIN RequestAdminInfo adminInfo
			   ON search.Id = adminInfo.Id
			 JOIN Status status
			   ON adminInfo.StatusId = status.Id
			 JOIN DateInfo departureDate
			   ON search.DepartureDateInfoId = departureDate.Id
			 JOIN DateInfo arrivalDate
			   ON search.ArrivalDateInfoId = arrivalDate.Id
			 JOIN AddressInfo departureAddress
			   ON search.DepartureAddressInfoId = departureAddress.Id
			 JOIN AddressInfo arrivalAddress
			   ON search.ArrivalAddressInfoId = arrivalAddress.Id
			 JOIN RequestItem item
			   ON search.Id = item.RequestId
			 JOIN RequestItemAttribute attribute
			   ON item.Id = attribute.RequestItemId
			WHERE search.Id = @requestId";

var requestMapped = new Dictionary<long, RequestInfo>();

var requestInfoResultset = await this._connection.QueryAsync<RequestInfo, DateInfo, DateInfo, RequestItemInfo, RequestItemAttributeInfo, RequestInfo>(
	sql,
	(request, departureDate, arrivalDate, item, attribute) =>
	{
		RequestInfo result;
		if (!requestMapped.TryGetValue(request.RequestId, out result))
		{
			result = request;
			result.DepartureDate = departureDate;
			result.ArrivalDate = arrivalDate;

			result.Items = new List<RequestItemInfo>();

			requestMapped.Add(result.RequestId, result);
		}

		if (!result.Items.Exists(i => i.ItemId == item.ItemId))
		{
			item.Attributes = new List<RequestItemAttributeInfo>();

			result.Items.Add(item);
		}

		result.Items.Find(i => i.ItemId == item.ItemId).Attributes.Add(attribute);

		return result;
	},
	new { requestId = requestId },
	splitOn: "DateId, DateId, ItemId, AttributeId"
);

return requestInfoResultset.FirstOrDefault();

The code above works correctly. But when I started to write code my SELECT was:

departureAddress.GeometryLocation.STAsText() AS DepartureCoordinates,
departureDate.Id AS DateId, departureDate.Date, departureDate.IsFlexible,
arrivalAddress.GeometryLocation.STAsText() AS ArrivalCoordinates, 
arrivalDate.Id AS DateId, arrivalDate.Date, arrivalDate.IsFlexible,

instead of (the correct one)

departureAddress.GeometryLocation.STAsText() AS DepartureCoordinates,
arrivalAddress.GeometryLocation.STAsText() AS ArrivalCoordinates, 
departureDate.Id AS DateId, departureDate.Date, departureDate.IsFlexible,
arrivalDate.Id AS DateId, arrivalDate.Date, arrivalDate.IsFlexible,

In the first version, I obtained my custom class correctly populated but ArrivalCoordinates was NULL. I am not expert on DAPPER but I have a feeling the problem can be due to the splitOn option. Perhaps dapper thought ArrivalCoordinates was a property of departureDate.

I hope I have been clear in my exposure.

PS.: I renamed some properties and classes. So if you see some mismatching, it was not on the real code.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the multi-mapping QueryAsync call and its splitOn value, then compare the two SELECT column orders shown in the issue. Reproduce the mapping with the custom GeometryPoint handler and verify which properties receive values; done means explaining or correcting the null ArrivalCoordinates result and covering the behavior with a focused test if the repository provides a suitable test location.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, sql
Domain
backend, databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.