Custom Type Handler works correctly according to position of columns in the SELECT statment
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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