Skip to content

Commit 0ac85fa

Browse files
committed
Adding support for custom converters
1 parent b939bef commit 0ac85fa

File tree

6 files changed

+162
-20
lines changed

6 files changed

+162
-20
lines changed

src/DatatablesParser/DatatablesParser.cs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class Parser<T> where T : class
2020
private int _skip;
2121
private bool _sortDisabled = false;
2222

23+
private Dictionary<string,Expression> _converters = new Dictionary<string, Expression>();
2324

2425
private Type[] _convertable =
2526
{
@@ -63,7 +64,12 @@ where Regex.IsMatch(param.Key,Constants.COLUMN_PROPERTY_PATTERN)
6364
}
6465
}).Distinct().ToDictionary(k => k.index, v => v.map);
6566

66-
67+
68+
if(_propertyMap.Count == 0 )
69+
{
70+
throw new Exception("No properties were found in request. Please map datatable field names to properties in T");
71+
}
72+
6773
if(_config.ContainsKey(Constants.DISPLAY_START))
6874
{
6975
int.TryParse(_config[Constants.DISPLAY_START], out _skip);
@@ -137,6 +143,28 @@ public Results<T> Parse()
137143
return list;
138144
}
139145

146+
///<summary>
147+
/// SetConverter accepts a custom expression for converting a property in T to string.
148+
/// This will be used during filtering.
149+
///</summary>
150+
/// <param name="property">A lambda expression with a member expression as the body</param>
151+
/// <param name="tostring">A lambda given T returns a string by performing a sql translatable operation on property</param>
152+
public Parser<T> SetConverter(Expression<Func<T,object>> property, Expression<Func<T,string>> tostring)
153+
{
154+
Console.WriteLine(property.Body.NodeType);
155+
156+
var memberExp = ((UnaryExpression)property.Body).Operand as MemberExpression;
157+
158+
if(memberExp == null)
159+
{
160+
throw new ArgumentException("Body in property must be a member expression");
161+
}
162+
163+
_converters[memberExp.Member.Name] = tostring.Body;
164+
165+
return this;
166+
}
167+
140168
private void ApplySort()
141169
{
142170
var sorted = false;
@@ -240,19 +268,26 @@ private Expression<Func<T, bool>> GenerateEntityFilter()
240268
var searchExpression = Expression.Constant(search.ToLower());
241269
var paramExpression = Expression.Parameter(_type, "val");
242270
List<MethodCallExpression> searchProps = new List<MethodCallExpression>();
271+
var modifier = new ModifyParam(paramExpression);
243272

244273
foreach (var propMap in _propertyMap)
245274
{
246275
var prop = propMap.Value.Property;
247276
var isString = prop.PropertyType == typeof(string);
248-
if (!prop.CanWrite || !propMap.Value.Searchable || (!_convertable.Any(t => t == prop.PropertyType) && !isString ) )
277+
var hasCustom = _converters.ContainsKey(prop.Name);
278+
279+
if ((!prop.CanWrite || !propMap.Value.Searchable || (!_convertable.Any(t => t == prop.PropertyType) && !isString )) && !hasCustom )
249280
{
250281
continue;
251282
}
252283

253284
Expression propExp = Expression.Property(paramExpression, prop);
254285

255-
if (!isString)
286+
if(hasCustom)
287+
{
288+
propExp = modifier.Visit( _converters[prop.Name]);
289+
}
290+
else if (!isString)
256291
{
257292
var toString = prop.PropertyType.GetMethod("ToString", Type.EmptyTypes);
258293

@@ -267,8 +302,9 @@ private Expression<Func<T, bool>> GenerateEntityFilter()
267302
}
268303

269304
var propertyQuery = searchProps.ToArray();
270-
// we now need to compound the expression by starting with the first
271-
// expression and build through the iterator
305+
306+
//This will all be converted to a giant WHERE clause if translated to sql
307+
//Add the first expression
272308
Expression compoundExpression = propertyQuery[0];
273309

274310
// add the other expressions
@@ -284,6 +320,22 @@ private Expression<Func<T, bool>> GenerateEntityFilter()
284320

285321

286322

323+
public class ModifyParam : ExpressionVisitor
324+
{
325+
private ParameterExpression _replace;
326+
327+
public ModifyParam(ParameterExpression p)
328+
{
329+
_replace = p;
330+
}
331+
332+
protected override Expression VisitParameter(ParameterExpression node)
333+
{
334+
return _replace;
335+
}
336+
337+
}
338+
287339
private class PropertyMap
288340
{
289341
public PropertyInfo Property { get; set; }

test/DatatablesParser.Tests/MssqlEntityTests.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void TotalRecordsTest()
1717

1818
var p = TestHelper.CreateParams();
1919

20-
var parser = new Parser<Person>(p, context.People.AsQueryable());
20+
var parser = new Parser<Person>(p, context.People);
2121

2222
Console.WriteLine("Mssql - Total People TotalRecordsTest: {0}",context.People.Count());
2323

@@ -37,7 +37,7 @@ public void TotalResultsTest()
3737
//override display length
3838
p[Constants.DISPLAY_LENGTH] = new StringValues(Convert.ToString(resultLength));
3939

40-
var parser = new Parser<Person>(p, context.People.AsQueryable());
40+
var parser = new Parser<Person>(p, context.People);
4141

4242
Console.WriteLine("Mssql - Total People TotalResultsTest: {0}",context.People.Count());
4343

@@ -56,14 +56,35 @@ public void TotalDisplayTest()
5656
//Set filter parameter
5757
p[Constants.SEARCH_KEY] = new StringValues("Cromie");
5858

59-
var parser = new Parser<Person>(p, context.People.AsQueryable());
59+
var parser = new Parser<Person>(p, context.People);
6060

6161
Console.WriteLine("Mssql - Total People TotalDisplayTest: {0}",context.People.Count());
6262

6363
Assert.Equal(displayLength, parser.Parse().recordsFiltered);
6464

6565
}
6666

67+
[Fact]
68+
public void TotalDisplayCustomFormatTest()
69+
{
70+
var context = TestHelper.GetMssqlContext();
71+
var p = TestHelper.CreateParams();
72+
var displayLength = 1;
73+
74+
75+
//Set filter parameter
76+
p[Constants.SEARCH_KEY] = new StringValues("9/03/1953");
77+
78+
var parser = new Parser<Person>(p, context.People)
79+
.SetConverter(x => x.BirthDate, x => PersonContext.Format(x.BirthDate,"M/dd/yyyy"));
80+
81+
82+
Console.WriteLine("Mssql - Total People TotalDisplayCustomFormatTest: {0}",context.People.Count());
83+
84+
Assert.Equal(displayLength, parser.Parse().recordsFiltered);
85+
86+
}
87+
6788

6889
}
6990
}

test/DatatablesParser.Tests/MysqlEntityTests.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void TotalRecordsTest()
1717

1818
var p = TestHelper.CreateParams();
1919

20-
var parser = new Parser<Person>(p, context.People.AsQueryable());
20+
var parser = new Parser<Person>(p, context.People);
2121

2222
Console.WriteLine("Mysql - Total People TotalRecordsTest: {0}",context.People.Count());
2323

@@ -37,7 +37,7 @@ public void TotalResultsTest()
3737
//override display length
3838
p[Constants.DISPLAY_LENGTH] = new StringValues(Convert.ToString(resultLength));
3939

40-
var parser = new Parser<Person>(p, context.People.AsQueryable());
40+
var parser = new Parser<Person>(p, context.People);
4141

4242
Console.WriteLine("Mysql - Total People TotalResultsTest: {0}",context.People.Count());
4343

@@ -56,14 +56,35 @@ public void TotalDisplayTest()
5656
//Set filter parameter
5757
p[Constants.SEARCH_KEY] = new StringValues("Cromie");
5858

59-
var parser = new Parser<Person>(p, context.People.AsQueryable());
59+
var parser = new Parser<Person>(p, context.People);
6060

6161
Console.WriteLine("Mysql - Total People TotalDisplayTest: {0}",context.People.Count());
6262

6363
Assert.Equal(displayLength, parser.Parse().recordsFiltered);
6464

6565
}
6666

67+
[Fact]
68+
public void TotalDisplayCustomFormatTest()
69+
{
70+
var context = TestHelper.GetMysqlContext();
71+
var p = TestHelper.CreateParams();
72+
var displayLength = 1;
73+
74+
75+
//Set filter parameter
76+
p[Constants.SEARCH_KEY] = new StringValues("09/03/1953");
77+
78+
var parser = new Parser<Person>(p, context.People)
79+
.SetConverter(x => x.BirthDate, x => PersonContext.Date_Format(x.BirthDate,"%m/%d/%Y"));
80+
81+
82+
Console.WriteLine("MySql - Total People TotalDisplayCustomFormatTest: {0}",context.People.Count());
83+
84+
Assert.Equal(displayLength, parser.Parse().recordsFiltered);
85+
86+
}
87+
6788

6889
}
6990
}

test/DatatablesParser.Tests/PersonContext.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.EntityFrameworkCore;
2-
2+
using System;
3+
34
namespace DataTablesParser.Tests
45
{
56
public class PersonContext : DbContext
@@ -9,6 +10,28 @@ public PersonContext(){ }
910
public PersonContext(DbContextOptions<PersonContext> options)
1011
: base(options){ }
1112

13+
//Sql Server >= 2012
14+
[DbFunction]
15+
public static string Format(DateTime data,string format)
16+
{
17+
throw new Exception();
18+
}
19+
20+
//MySql
21+
[DbFunction]
22+
public static string Date_Format(DateTime data,string format)
23+
{
24+
throw new Exception();
25+
}
26+
27+
//Postgres
28+
[DbFunction]
29+
public static string To_Char(DateTime data,string format)
30+
{
31+
throw new Exception();
32+
}
33+
34+
1235
public DbSet<Person> People { get; set; }
1336
}
1437
}

test/DatatablesParser.Tests/PgsqlEntityTests.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void TotalRecordsTest()
1717

1818
var p = TestHelper.CreateParams();
1919

20-
var parser = new Parser<Person>(p, context.People.AsQueryable());
20+
var parser = new Parser<Person>(p, context.People);
2121

2222
Console.WriteLine("Pgsql - Total People TotalRecordsTest: {0}",context.People.Count());
2323

@@ -37,7 +37,7 @@ public void TotalResultsTest()
3737
//override display length
3838
p[Constants.DISPLAY_LENGTH] = new StringValues(Convert.ToString(resultLength));
3939

40-
var parser = new Parser<Person>(p, context.People.AsQueryable());
40+
var parser = new Parser<Person>(p, context.People);
4141

4242
Console.WriteLine("Pgsql - Total People TotalResultsTest: {0}",context.People.Count());
4343

@@ -56,14 +56,35 @@ public void TotalDisplayTest()
5656
//Set filter parameter
5757
p[Constants.SEARCH_KEY] = new StringValues("Cromie");
5858

59-
var parser = new Parser<Person>(p, context.People.AsQueryable());
59+
var parser = new Parser<Person>(p, context.People);
6060

6161
Console.WriteLine("Pgsql - Total People TotalDisplayTest: {0}",context.People.Count());
6262

6363
Assert.Equal(displayLength, parser.Parse().recordsFiltered);
6464

6565
}
6666

67+
[Fact]
68+
public void TotalDisplayCustomFormatTest()
69+
{
70+
var context = TestHelper.GetPgsqlContext();
71+
var p = TestHelper.CreateParams();
72+
var displayLength = 1;
73+
74+
75+
//Set filter parameter
76+
p[Constants.SEARCH_KEY] = new StringValues("09/03/1953");
77+
78+
var parser = new Parser<Person>(p, context.People)
79+
.SetConverter(x => x.BirthDate, x => PersonContext.To_Char(x.BirthDate,"MM/DD/YYYY"));
80+
81+
82+
Console.WriteLine("Pgsql - Total People TotalDisplayCustomFormatTest: {0}",context.People.Count());
83+
84+
Assert.Equal(displayLength, parser.Parse().recordsFiltered);
85+
86+
}
87+
6788

6889
}
6990
}

test/DatatablesParser.Tests/TestHelper.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Pomelo.EntityFrameworkCore.MySql;
99
using Npgsql.EntityFrameworkCore.PostgreSQL;
1010
using Microsoft.Extensions.Logging;
11+
using Microsoft.EntityFrameworkCore.Diagnostics;
1112

1213
namespace DataTablesParser.Tests
1314
{
@@ -175,10 +176,11 @@ public static PersonContext GetMysqlContext()
175176

176177
var lf = serviceProvider.GetService<ILoggerFactory>();
177178
lf.AddProvider(new EFlogger());
178-
179+
179180
var builder = new DbContextOptionsBuilder<PersonContext>();
180181
builder.UseMySql(@"server=mysql;database=dotnettest;user=tester;password=Rea11ytrong_3")
181-
.UseInternalServiceProvider(serviceProvider);
182+
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
183+
.UseInternalServiceProvider(serviceProvider);
182184

183185
var context = new PersonContext(builder.Options);
184186

@@ -204,10 +206,11 @@ public static PersonContext GetPgsqlContext()
204206

205207
var builder = new DbContextOptionsBuilder<PersonContext>();
206208
builder.UseNpgsql(@"Host=pgsql;Database=dotnettest;User ID=tester;Password=Rea11ytrong_3")
209+
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
207210
.UseInternalServiceProvider(serviceProvider);
208-
211+
209212
var context = new PersonContext(builder.Options);
210-
213+
211214
context.Database.EnsureCreated();
212215
context.Database.ExecuteSqlCommand("truncate table public.\"People\";");
213216

@@ -231,7 +234,8 @@ public static PersonContext GetMssqlContext()
231234

232235
var builder = new DbContextOptionsBuilder<PersonContext>();
233236
builder.UseSqlServer(@"Data Source=mssql;Initial Catalog=TestNetCoreEF;user id=sa;password=Rea11ytrong_3")
234-
.UseInternalServiceProvider(serviceProvider);
237+
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
238+
.UseInternalServiceProvider(serviceProvider);
235239

236240
var context = new PersonContext(builder.Options);
237241
context.Database.EnsureCreated();

0 commit comments

Comments
 (0)