Skip to content

Commit 6c48c81

Browse files
committed
isnull (issue #13)
1 parent 880cbe2 commit 6c48c81

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

src/System.Linq.Dynamic.Core/ExpressionParser.cs

+23-7
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,16 @@ interface IEnumerableSignatures
254254
static readonly Expression FalseLiteral = Expression.Constant(false);
255255
static readonly Expression NullLiteral = Expression.Constant(null);
256256

257-
const string KEYWORD_IT = "it";
258-
const string KEYWORD_PARENT = "parent";
259-
const string KEYWORD_ROOT = "root";
260257
const string SYMBOL_IT = "$";
261258
const string SYMBOL_PARENT = "^";
262259
const string SYMBOL_ROOT = "~";
260+
261+
const string KEYWORD_IT = "it";
262+
const string KEYWORD_PARENT = "parent";
263+
const string KEYWORD_ROOT = "root";
263264
const string KEYWORD_IIF = "iif";
264265
const string KEYWORD_NEW = "new";
266+
const string KEYWORD_ISNULL = "isnull";
265267

266268
static Dictionary<string, object> _keywords;
267269

@@ -448,6 +450,18 @@ Expression ParseNullCoalescing()
448450
return expr;
449451
}
450452

453+
// isnull(a,b) operator
454+
Expression ParseIsNull()
455+
{
456+
int errorPos = _token.pos;
457+
NextToken();
458+
Expression[] args = ParseArgumentList();
459+
if (args.Length != 2)
460+
throw ParseError(errorPos, Res.IsNullRequiresTwoArgs);
461+
462+
return Expression.Coalesce(args[0], args[1]);
463+
}
464+
451465
// ||, or operator
452466
Expression ParseConditionalOr()
453467
{
@@ -1034,6 +1048,7 @@ Expression ParseIdentifier()
10341048
if (value == (object)SYMBOL_ROOT) return ParseRoot();
10351049
if (value == (object)KEYWORD_IIF) return ParseIif();
10361050
if (value == (object)KEYWORD_NEW) return ParseNew();
1051+
if (value == (object)KEYWORD_ISNULL) return ParseIsNull();
10371052

10381053
NextToken();
10391054

@@ -1785,11 +1800,7 @@ Expression PromoteExpression(Expression expr, Type type, bool exact)
17851800
{
17861801
if (ce == NullLiteral)
17871802
{
1788-
#if !(NETFX_CORE || DNXCORE50 || DOTNET5_4 || NETSTANDARD)
17891803
if (!type.GetTypeInfo().IsValueType || IsNullableType(type))
1790-
#else
1791-
if (!type.GetTypeInfo().IsValueType || IsNullableType(type))
1792-
#endif
17931804
return Expression.Constant(null, type);
17941805
}
17951806
else
@@ -1807,6 +1818,10 @@ Expression PromoteExpression(Expression expr, Type type, bool exact)
18071818
case TypeCode.Int64:
18081819
case TypeCode.UInt64:
18091820
value = ParseNumber(text, target);
1821+
1822+
// Make sure an enum value stays an enum value
1823+
if (target.IsEnum)
1824+
value = Enum.ToObject(target, value);
18101825
break;
18111826
case TypeCode.Double:
18121827
if (target == typeof(decimal)) value = ParseNumber(text, target);
@@ -2654,6 +2669,7 @@ static Dictionary<string, object> CreateKeywords()
26542669
d.Add(SYMBOL_ROOT, SYMBOL_ROOT);
26552670
d.Add(KEYWORD_IIF, KEYWORD_IIF);
26562671
d.Add(KEYWORD_NEW, KEYWORD_NEW);
2672+
d.Add(KEYWORD_ISNULL, KEYWORD_ISNULL);
26572673

26582674
foreach (Type type in _predefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key))
26592675
{

src/System.Linq.Dynamic.Core/Res.cs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal static class Res
1515
public const string NoParentInScope = "No 'parent' is in scope";
1616
public const string NoRootInScope = "No 'root' is in scope";
1717
public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments";
18+
public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments";
1819
public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'";
1920
public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other";
2021
public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";

test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs

+40
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,46 @@ public void ExpressionTests_NullCoalescing()
208208
Assert.Equal(expectedResult3, result3b.ToDynamicArray<int>());
209209
}
210210

211+
[Fact]
212+
public void ExpressionTests_IsNull_Simple()
213+
{
214+
//Arrange
215+
var baseQuery = new int?[] { 1, 2, null, 3, 4 }.AsQueryable();
216+
var expectedResult = new int[] { 1, 2, 0, 3, 4 };
217+
218+
// Act
219+
var result1 = baseQuery.Select("isnull(it, 0)");
220+
221+
//Assert
222+
Assert.Equal(expectedResult, result1.ToDynamicArray<int>());
223+
}
224+
225+
[Fact]
226+
public void ExpressionTests_IsNull_Complex()
227+
{
228+
//Arrange
229+
var testModels = User.GenerateSampleModels(3, true);
230+
testModels[0].NullableInt = null;
231+
testModels[1].NullableInt = null;
232+
testModels[2].NullableInt = 5;
233+
234+
var expectedResult1 = testModels.AsQueryable().Select(u => new { UserName = u.UserName, X = u.NullableInt ?? (3 * u.Income) }).Cast<object>().ToArray();
235+
var expectedResult2 = testModels.AsQueryable().Where(u => (u.NullableInt ?? 10) == 10).ToArray();
236+
var expectedResult3 = testModels.Select(m => m.NullableInt ?? 10).ToArray();
237+
238+
//Act
239+
var result1 = testModels.AsQueryable().Select("new (UserName, isnull(NullableInt, (3 * Income)) as X)");
240+
var result2 = testModels.AsQueryable().Where("isnull(NullableInt, 10) == 10");
241+
var result3a = testModels.AsQueryable().Select("isnull(NullableInt, @0)", 10);
242+
var result3b = testModels.AsQueryable().Select<int>("isnull(NullableInt, @0)", 10);
243+
244+
//Assert
245+
Assert.Equal(expectedResult1.ToString(), result1.ToDynamicArray().ToString());
246+
Assert.Equal(expectedResult2, result2.ToArray());
247+
Assert.Equal(expectedResult3, result3a.ToDynamicArray<int>());
248+
Assert.Equal(expectedResult3, result3b.ToDynamicArray<int>());
249+
}
250+
211251
[Fact]
212252
public void ExpressionTests_ConditionalOr1()
213253
{

0 commit comments

Comments
 (0)