Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 42c7066

Browse files
Add some fall-back guesses when mapping between datareader and object to better support legacy databases
This is very useful when querying legacy databases that cannot be changed (views, stored procedures, ...) that contains fields with underscores and/or special characters and/or prefixes that either wouldn't be possible to be declared in C#, or wouldn't comply with standard naming conventions Examples of mappings that are now possible with this change: [Database] [C#] customer_id => public int CustomerId { get; set; } quantity_% => public decimal Quantity { get; set; } t040_name => public string Name { get; set; }
1 parent bb9ce5c commit 42c7066

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

src/ServiceStack.OrmLite/OrmLiteWriteExtensions.cs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Data;
1616
using System.Diagnostics;
1717
using System.Linq;
18+
using System.Text.RegularExpressions;
1819
using ServiceStack.Common.Utils;
1920
using ServiceStack.Logging;
2021

@@ -235,18 +236,27 @@ public static T PopulateWithSqlReader<T>(this T objWithProperties, IDataReader d
235236
{
236237
foreach (var fieldDef in fieldDefs)
237238
{
238-
int index = NotFound;
239+
int index;
239240
if (indexCache != null)
240241
{
241242
if (!indexCache.TryGetValue(fieldDef.Name, out index))
242243
{
243244
index = dataReader.GetColumnIndex(fieldDef.FieldName);
245+
if (index == NotFound)
246+
{
247+
index = TryGuessColumnIndex(fieldDef.FieldName, dataReader);
248+
}
249+
244250
indexCache.Add(fieldDef.Name, index);
245251
}
246252
}
247253
else
248254
{
249255
index = dataReader.GetColumnIndex(fieldDef.FieldName);
256+
if (index == NotFound)
257+
{
258+
index = TryGuessColumnIndex(fieldDef.FieldName, dataReader);
259+
}
250260
}
251261

252262
if (index == NotFound) continue;
@@ -261,6 +271,71 @@ public static T PopulateWithSqlReader<T>(this T objWithProperties, IDataReader d
261271
return objWithProperties;
262272
}
263273

274+
private static readonly Regex AllowedPropertyCharsRegex = new Regex(@"[^0-9a-zA-Z_]",
275+
RegexOptions.Compiled | RegexOptions.CultureInvariant);
276+
277+
private static int TryGuessColumnIndex(string fieldName, IDataReader dataReader)
278+
{
279+
var fieldCount = dataReader.FieldCount;
280+
for (var i = 0; i < fieldCount; i++)
281+
{
282+
var dbFieldName = dataReader.GetName(i);
283+
284+
// First guess: Maybe the DB field has underscores? (most common)
285+
// e.g. CustomerId (C#) vs customer_id (DB)
286+
var dbFieldNameWithNoUnderscores = dbFieldName.Replace("_", "");
287+
if (string.Compare(fieldName, dbFieldNameWithNoUnderscores, StringComparison.InvariantCultureIgnoreCase) == 0)
288+
{
289+
return i;
290+
}
291+
292+
// Next guess: Maybe the DB field has special characters?
293+
// e.g. Quantity (C#) vs quantity% (DB)
294+
var dbFieldNameSanitized = AllowedPropertyCharsRegex.Replace(dbFieldName, string.Empty);
295+
if (string.Compare(fieldName, dbFieldNameSanitized, StringComparison.InvariantCultureIgnoreCase) == 0)
296+
{
297+
return i;
298+
}
299+
300+
// Next guess: Maybe the DB field has special characters *and* has underscores?
301+
// e.g. Quantity (C#) vs quantity_% (DB)
302+
if (string.Compare(fieldName, dbFieldNameSanitized.Replace("_", string.Empty), StringComparison.InvariantCultureIgnoreCase) == 0)
303+
{
304+
return i;
305+
}
306+
307+
// Next guess: Maybe the DB field has some prefix that we don't have in our C# field?
308+
// e.g. CustomerId (C#) vs t130CustomerId (DB)
309+
if (dbFieldName.EndsWith(fieldName, StringComparison.InvariantCultureIgnoreCase))
310+
{
311+
return i;
312+
}
313+
314+
// Next guess: Maybe the DB field has some prefix that we don't have in our C# field *and* has underscores?
315+
// e.g. CustomerId (C#) vs t130_CustomerId (DB)
316+
if (dbFieldNameWithNoUnderscores.EndsWith(fieldName, StringComparison.InvariantCultureIgnoreCase))
317+
{
318+
return i;
319+
}
320+
321+
// Next guess: Maybe the DB field has some prefix that we don't have in our C# field *and* has special characters?
322+
// e.g. CustomerId (C#) vs t130#CustomerId (DB)
323+
if (dbFieldNameSanitized.EndsWith(fieldName, StringComparison.InvariantCultureIgnoreCase))
324+
{
325+
return i;
326+
}
327+
328+
// Next guess: Maybe the DB field has some prefix that we don't have in our C# field *and* has underscores *and* has special characters?
329+
// e.g. CustomerId (C#) vs t130#Customer_I#d (DB)
330+
if (dbFieldNameSanitized.Replace("_", "").EndsWith(fieldName, StringComparison.InvariantCultureIgnoreCase))
331+
{
332+
return i;
333+
}
334+
}
335+
336+
return NotFound;
337+
}
338+
264339
internal static void Update<T>(this IDbCommand dbCmd, params T[] objs)
265340
{
266341
foreach (var obj in objs)

tests/ServiceStack.OrmLite.Tests/OrmLiteQueryTests.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,44 @@ FROM Note
218218
Assert.That(notes[0].NoteText, Is.EqualTo("Hello world 5"));
219219
}
220220
}
221-
}
221+
222+
class CustomerDto
223+
{
224+
public int CustomerId { get; set; }
225+
public string @CustomerName { get; set; }
226+
public DateTime Customer_Birth_Date { get; set; }
227+
}
228+
229+
[Test]
230+
[TestCase("customer_id", "customer_name", "customer_birth_date")]
231+
[TestCase("customerid%", "@customername", "customer_b^irth_date")]
232+
[TestCase("customerid_%", "@customer_name", "customer$_birth_#date")]
233+
[TestCase("c!u@s#t$o%m^e&r*i(d_%", "__cus_tomer__nam_e__", "~cus`tomer$_birth_#date")]
234+
[TestCase("t030CustomerId", "t030CustomerName", "t030Customer_birth_date")]
235+
[TestCase("t030_customer_id", "t030_customer_name", "t130_customer_birth_date")]
236+
[TestCase("t030#Customer_I#d", "t030CustomerNa$^me", "t030Cust^omer_birth_date")]
237+
public void Can_query_CustomerDto_and_map_db_fields_not_identical_by_guessing_the_mapping(string field1Name, string field2Name, string field3Name)
238+
{
239+
using (var db = ConnectionString.OpenDbConnection())
240+
{
241+
var sql = string.Format(@"
242+
SELECT 1 AS [{0}], 'John' AS [{1}], '1970-01-01' AS [{2}]
243+
UNION ALL
244+
SELECT 2 AS [{0}], 'Jane' AS [{1}], '1980-01-01' AS [{2}]",
245+
field1Name, field2Name, field3Name);
246+
247+
var customers = db.Query<CustomerDto>(sql);
248+
249+
Assert.That(customers.Count, Is.EqualTo(2));
250+
251+
Assert.That(customers[0].CustomerId, Is.EqualTo(1));
252+
Assert.That(customers[0].CustomerName, Is.EqualTo("John"));
253+
Assert.That(customers[0].Customer_Birth_Date, Is.EqualTo(new DateTime(1970, 01, 01)));
254+
255+
Assert.That(customers[1].CustomerId, Is.EqualTo(2));
256+
Assert.That(customers[1].CustomerName, Is.EqualTo("Jane"));
257+
Assert.That(customers[1].Customer_Birth_Date, Is.EqualTo(new DateTime(1980, 01, 01)));
258+
}
259+
}
260+
}
222261
}

0 commit comments

Comments
 (0)