diff --git a/Directory.Build.props b/Directory.Build.props
index 44c947a2..7cee1f75 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,7 +4,7 @@
- 1.0.9.2
+ 1.0.10
diff --git a/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs b/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs
index d7b07419..5dbd40ea 100644
--- a/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs
+++ b/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs
@@ -11,6 +11,16 @@ namespace ConsoleAppEF2
{
class Program
{
+ class User
+ {
+ public string Name { get; set; }
+
+ public string GetDisplayName(bool a, bool b, bool c)
+ {
+ return Name + "GetDisplayName";
+ }
+ }
+
class C : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider
{
public HashSet GetCustomTypes()
@@ -69,6 +79,15 @@ static void Main(string[] args)
};
Console.WriteLine("all {0}", JsonConvert.SerializeObject(all, Formatting.Indented));
+ var projects = new[]
+ {
+ new { UserShares = new [] { new User { Name = "John" } } }
+ }.AsQueryable();
+
+ var filter = "UserShares.Any(GetDisplayName(true,true,false).Contains(\"John\"))";
+ var filtered = projects.Where(filter);
+ Console.WriteLine("filtered {0}", JsonConvert.SerializeObject(filtered, Formatting.Indented));
+
var config = new ParsingConfig
{
CustomTypeProvider = new C()
@@ -83,6 +102,9 @@ static void Main(string[] args)
context.Cars.Add(new Car { Brand = "Alfa", Color = "Black", Vin = "a%bc", Year = "1979", DateLastModified = dateLastModified.AddDays(3) });
context.SaveChanges();
+ var methodTest = context.Cars.Select("it.X(true, \"tst\").Contains(\"Blue\")");
+ Console.WriteLine("methodTest {0}", JsonConvert.SerializeObject(methodTest, Formatting.Indented));
+
var carSingleOrDefault = context.Cars.SingleOrDefault(config, "Brand = \"Ford\"");
Console.WriteLine("carSingleOrDefault {0}", JsonConvert.SerializeObject(carSingleOrDefault, Formatting.Indented));
diff --git a/src-console/ConsoleAppEF2.0/Database/Car.cs b/src-console/ConsoleAppEF2.0/Database/Car.cs
index 64dc218b..34599543 100644
--- a/src-console/ConsoleAppEF2.0/Database/Car.cs
+++ b/src-console/ConsoleAppEF2.0/Database/Car.cs
@@ -23,5 +23,10 @@ public class Car
[Required]
public DateTime DateLastModified { get; set; }
+
+ public string X(bool b, string s)
+ {
+ return b + s + Color;
+ }
}
}
diff --git a/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs b/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs
index 18dad64c..63b4dd91 100644
--- a/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs
+++ b/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs
@@ -7,7 +7,6 @@
using System.Reflection;
using ConsoleAppEF2.Database;
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
namespace ConsoleAppEF2
{
@@ -18,11 +17,17 @@ public class NestedDto
public string Name { get; set; }
public NestedDto2 NestedDto2 { get; set; }
+ }
+ public class NestedDto2
+ {
+ public string Name2 { get; set; }
+ public int Id { get; set; }
+ public NestedDto3 NestedDto3 { get; set; }
}
- public class NestedDto2
+ public class NestedDto3
{
public string Name2 { get; set; }
@@ -50,129 +55,18 @@ public Type ResolveType(string typeName)
}
}
- private static object GetObj()
- {
- return new
- {
- Id = 5,
- Value = 400
- };
- }
-
- class X : DynamicClass
- {
-
- }
-
- private static IQueryable GetQueryable()
- {
- var random = new Random((int)DateTime.Now.Ticks);
-
- var jt = typeof(JToken);
-
- var em = jt.GetTypeInfo().GetDeclaredMethods("op_Explicit");
- var im = jt.GetTypeInfo().GetDeclaredMethods("op_Explicit");
-
- var j = new JObject
- {
- { "Id", new JValue(9) },
- { "Name", new JValue("Test") }
- };
-
- //(j["Id"] as JValue).Value
-
- IQueryable jarray = new[] { j }.AsQueryable();
- var jresult = jarray.Select("new (int(Id) as Id, string(Name) as Name)");
-
- var an = jresult.Any("Id > 4");
-
-
- var dx = new X();
- dx["Id"] = 5;
-
- IQueryable srcDX = new[] { dx }.AsQueryable();
- var b = srcDX.Select("new (Id.ToString() as Id)");
- var anyDX = b.Any("int.Parse(Id) > 4");
-
- var x = Enumerable.Range(0, 10).Select(i => new
- {
- Id = i,
- Value = random.Next()
- }).AsQueryable();
-
- //var any = x.Any("Id > 4");
-
- //var obj = new
- //{
- // Id = 5,
- // Value = random.Next()
- //};
- //var x2 = Enumerable.Range(0, 1).Select(_ => obj).AsQueryable();
- //var any2 = x.Any("Id > 4");
-
- //var o = GetObj();
- //var t = o.GetType();
- //IQueryable source = new[] { o }.AsQueryable();
- //// source.ElementType = t;
-
- //var x2b = new[] { o }.AsQueryable();
- //var any2function = x2b.Any(null, "Id > 4", t);
-
- //var any2b = x2b.Any("Id > 4");
-
- //var x3 = new[] { obj }.AsQueryable();
- //var any3 = x3.Any("Id > 4");
-
- return x.Select("new (it as Id, @0 as Value)", random.Next());
- // return x.AsQueryable(); //x.AsQueryable().Select("new (Id, Value)");
- }
-
- public static IQueryable Transform(this IQueryable source, Type resultType)
+ static void Main(string[] args)
{
- var resultProperties = resultType.GetProperties().Where(p => p.CanWrite);
+ var q = new[] { new NestedDto(), new NestedDto { NestedDto2 = new NestedDto2 { NestedDto3 = new NestedDto3 { Id = 42 } } } }.AsQueryable();
- ParameterExpression s = Expression.Parameter(source.ElementType, "s");
+ var np1 = q.Select("np(it.NestedDto2.NestedDto3.Id, 0)");
+ var npResult1 = np1.ToDynamicList();
+ Console.WriteLine("npResult1 {0}", JsonConvert.SerializeObject(npResult1, Formatting.Indented));
- var memberBindings =
- resultProperties.Select(p =>
- Expression.Bind(resultType.GetMember(p.Name)[0], Expression.Property(s, p.Name))).OfType();
+ var np2 = q.Select("np(it.NestedDto2.NestedDto3.Id)");
+ var npResult2 = np2.ToDynamicList();
+ Console.WriteLine("npResult2 {0}", JsonConvert.SerializeObject(npResult2, Formatting.Indented));
- Expression memberInit = Expression.MemberInit(
- Expression.New(resultType),
- memberBindings
- );
-
- var memberInitLambda = Expression.Lambda(memberInit, s);
-
- var typeArgs = new[]
- {
- source.ElementType,
- memberInit.Type
- };
-
- var mc = Expression.Call(typeof(Queryable), "Select", typeArgs, source.Expression, memberInitLambda);
-
- var query = source.Provider.CreateQuery(mc);
-
- return query;
- }
-
- public static IQueryable EmptyQueryByExample(this T _) => Enumerable.Empty().AsQueryable();
-
-
- private static TResult Execute(MethodInfo operatorMethodInfo, IQueryable source, Expression expression, Type t = null)
- {
- operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2
- ? operatorMethodInfo.MakeGenericMethod(t == null ? source.ElementType : t, typeof(TResult))
- : operatorMethodInfo.MakeGenericMethod(t == null ? source.ElementType : t);
-
- var optimized = Expression.Call(null, operatorMethodInfo, source.Expression, expression);
- return source.Provider.Execute(optimized);
- }
-
- static void Main(string[] args)
- {
- var q = new[] { new NestedDto(), new NestedDto { NestedDto2 = new NestedDto2 { Id = 42 } } }.AsQueryable();
var r1 = q.Select("it != null && it.NestedDto2 != null ? it.NestedDto2.Id : null");
var list1 = r1.ToDynamicList();
@@ -191,18 +85,6 @@ static void Main(string[] args)
Console.WriteLine(projectedData.First().Name);
Console.WriteLine(projectedData.Last().Name);
- IQueryable qry = GetQueryable();
-
- var result = qry.Select("it").OrderBy("Value");
- try
- {
- Console.WriteLine("result {0}", JsonConvert.SerializeObject(result, Formatting.Indented));
- }
- catch (Exception)
- {
- // Console.WriteLine(e);
- }
-
var all = new
{
test1 = new List { 1, 2, 3 }.ToDynamicList(typeof(int)),
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
index 529c67af..949aad14 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
@@ -1,4 +1,5 @@
using JetBrains.Annotations;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq.Dynamic.Core.Validation;
using System.Linq.Expressions;
@@ -234,5 +235,79 @@ private void WrapConstantExpressions(ref Expression left, ref Expression right)
_constantExpressionWrapper.Wrap(ref right);
}
}
+
+ public Expression GenerateAndAlsoNotNullExpression(Expression sourceExpression)
+ {
+ var expresssions = CollectExpressions(sourceExpression);
+ if (!expresssions.Any())
+ {
+ return null;
+ }
+
+ // Reverse the list
+ expresssions.Reverse();
+
+ // Convert all expressions into '!= null'
+ var binaryExpressions = expresssions.Select(expression => Expression.NotEqual(expression, Constants.NullLiteral)).ToArray();
+
+ // Convert all binary expressions into `AndAlso(...)`
+ var andAlsoExpression = binaryExpressions[0];
+ for (int i = 1; i < binaryExpressions.Length; i++)
+ {
+ andAlsoExpression = Expression.AndAlso(andAlsoExpression, binaryExpressions[i]);
+ }
+
+ return andAlsoExpression;
+ }
+
+ private static Expression GetMemberExpression(Expression expression)
+ {
+ if (expression is ParameterExpression parameterExpression)
+ {
+ return parameterExpression;
+ }
+
+ if (expression is MemberExpression memberExpression)
+ {
+ return memberExpression;
+ }
+
+ if (expression is LambdaExpression lambdaExpression)
+ {
+ if (lambdaExpression.Body is MemberExpression bodyAsMemberExpression)
+ {
+ return bodyAsMemberExpression;
+ }
+
+ if (lambdaExpression.Body is UnaryExpression bodyAsunaryExpression)
+ {
+ return bodyAsunaryExpression.Operand;
+ }
+ }
+
+ return null;
+ }
+
+ private static List CollectExpressions(Expression sourceExpression)
+ {
+ var list = new List();
+ Expression expression = GetMemberExpression(sourceExpression);
+
+ while (expression is MemberExpression memberExpression)
+ {
+ expression = GetMemberExpression(memberExpression.Expression);
+ if (expression is MemberExpression)
+ {
+ list.Add(expression);
+ }
+ }
+
+ if (expression is ParameterExpression)
+ {
+ list.Add(expression);
+ }
+
+ return list;
+ }
}
}
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
index 66b0280b..2217fb8d 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
@@ -210,23 +210,6 @@ Expression ParseConditionalOperator()
return expr;
}
- // ?. (null-propagating) operator
- //Expression ParseNullPropagatingOperator()
- //{
- // int errorPos = _textParser.CurrentToken.Pos;
- // Expression expr = ParseNullCoalescingOperator();
- // if (_textParser.CurrentToken.Id == TokenId.Question)
- // {
- // _textParser.NextToken();
- // Expression expr1 = ParseConditionalOperator();
- // _textParser.ValidateToken(TokenId.Dot, Res.DotExpected);
- // _textParser.NextToken();
- // Expression expr2 = ParseConditionalOperator();
- // expr = GenerateConditional(expr, expr1, expr2, errorPos);
- // }
- // return expr;
- //}
-
// ?? (null-coalescing) operator
Expression ParseNullCoalescingOperator()
{
@@ -257,8 +240,8 @@ Expression ParseLambdaOperator()
return expr;
}
- // isnull(a,b) operator
- Expression ParseIsNull()
+ // isnull(a,b) function
+ Expression ParseFunctionIsNull()
{
int errorPos = _textParser.CurrentToken.Pos;
_textParser.NextToken();
@@ -964,7 +947,7 @@ Expression ParseIdentifier()
if (_keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out object value))
{
- var typeValue = value as Type;
+ Type typeValue = value as Type;
if (typeValue != null)
{
return ParseTypeAccess(typeValue);
@@ -973,12 +956,15 @@ Expression ParseIdentifier()
if (value == (object)KeywordsHelper.KEYWORD_IT) return ParseIt();
if (value == (object)KeywordsHelper.KEYWORD_PARENT) return ParseParent();
if (value == (object)KeywordsHelper.KEYWORD_ROOT) return ParseRoot();
+
if (value == (object)KeywordsHelper.SYMBOL_IT) return ParseIt();
if (value == (object)KeywordsHelper.SYMBOL_PARENT) return ParseParent();
if (value == (object)KeywordsHelper.SYMBOL_ROOT) return ParseRoot();
- if (value == (object)KeywordsHelper.KEYWORD_IIF) return ParseIif();
- if (value == (object)KeywordsHelper.KEYWORD_NEW) return ParseNew();
- if (value == (object)KeywordsHelper.KEYWORD_ISNULL) return ParseIsNull();
+
+ if (value == (object)KeywordsHelper.FUNCTION_IIF) return ParseFunctionIif();
+ if (value == (object)KeywordsHelper.FUNCTION_ISNULL) return ParseFunctionIsNull();
+ if (value == (object)KeywordsHelper.FUNCTION_NEW) return ParseNew();
+ if (value == (object)KeywordsHelper.FUNCTION_NULLPROPAGATION) return ParseFunctionNullPropagation();
_textParser.NextToken();
@@ -1046,10 +1032,12 @@ Expression ParseRoot()
return _root;
}
- Expression ParseIif()
+ // iif(test, ifTrue, ifFalse) function
+ Expression ParseFunctionIif()
{
int errorPos = _textParser.CurrentToken.Pos;
_textParser.NextToken();
+
Expression[] args = ParseArgumentList();
if (args.Length != 3)
{
@@ -1059,6 +1047,31 @@ Expression ParseIif()
return GenerateConditional(args[0], args[1], args[2], errorPos);
}
+ // np(...) function
+ Expression ParseFunctionNullPropagation()
+ {
+ int errorPos = _textParser.CurrentToken.Pos;
+ _textParser.NextToken();
+
+ Expression[] args = ParseArgumentList();
+
+ if (args.Length != 1 && args.Length != 2)
+ {
+ throw ParseError(errorPos, Res.NullPropagationRequiresCorrectArgs);
+ }
+
+ if (args[0] is MemberExpression memberExpression)
+ {
+ var expressionTest = _expressionHelper.GenerateAndAlsoNotNullExpression(memberExpression);
+ var expressionIfTrue = memberExpression;
+ var expressionIfFalse = args.Length == 2 ? args[1] : Constants.NullLiteral;
+
+ return GenerateConditional(expressionTest, expressionIfTrue, expressionIfFalse, errorPos);
+ }
+
+ throw ParseError(errorPos, Res.NullPropagationRequiresMemberExpression);
+ }
+
Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos)
{
if (test.Type != typeof(bool))
@@ -1119,6 +1132,7 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp
return Expression.Condition(test, expr1, expr2);
}
+ // new (...) function
Expression ParseNew()
{
_textParser.NextToken();
@@ -1256,7 +1270,7 @@ private Expression CreateNewExpression(List properties, List();
for (int i = 0; i < expressions.Count; i++)
@@ -1275,7 +1289,7 @@ private Expression CreateNewExpression(List properties, List _keywords = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
@@ -34,9 +36,11 @@ public KeywordsHelper(ParsingConfig config)
_keywords.Add(SYMBOL_IT, SYMBOL_IT);
_keywords.Add(SYMBOL_PARENT, SYMBOL_PARENT);
_keywords.Add(SYMBOL_ROOT, SYMBOL_ROOT);
- _keywords.Add(KEYWORD_IIF, KEYWORD_IIF);
- _keywords.Add(KEYWORD_NEW, KEYWORD_NEW);
- _keywords.Add(KEYWORD_ISNULL, KEYWORD_ISNULL);
+
+ _keywords.Add(FUNCTION_IIF, FUNCTION_IIF);
+ _keywords.Add(FUNCTION_ISNULL, FUNCTION_ISNULL);
+ _keywords.Add(FUNCTION_NEW, FUNCTION_NEW);
+ _keywords.Add(FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION);
foreach (Type type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key))
{
diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs
index fd0d475b..cff1a949 100644
--- a/src/System.Linq.Dynamic.Core/Res.cs
+++ b/src/System.Linq.Dynamic.Core/Res.cs
@@ -49,6 +49,8 @@ internal static class Res
public const string NoMatchingConstructor = "No matching constructor in type '{0}'";
public const string NoParentInScope = "No 'parent' is in scope";
public const string NoRootInScope = "No 'root' is in scope";
+ public const string NullPropagationRequiresCorrectArgs = "The 'np' (null-propagation) function requires 1 or 2 arguments";
+ public const string NullPropagationRequiresMemberExpression = "The 'np' (null-propagation) function requires the first argument to be a MemberExpression";
public const string OpenBracketExpected = "'[' expected";
public const string OpenCurlyParenExpected = "'{' expected";
public const string OpenParenExpected = "'(' expected";
diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
index ba94a7a9..7d592219 100644
--- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
@@ -4,7 +4,6 @@
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Tests.Helpers;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
-using System.Text;
using Newtonsoft.Json.Linq;
using Xunit;
using NFluent;
@@ -1257,57 +1256,48 @@ public void ExpressionTests_NullCoalescing()
Assert.Equal(expectedResult3, result3b.ToDynamicArray());
}
- // [Fact]
- public void ExpressionTests_NullPropagating()
+ [Fact]
+ public void ExpressionTests_NullPropagating_Null()
{
// Arrange
- var testModels = User.GenerateSampleModels(1, true);
- testModels[0].Profile = null;
+ var testModels = User.GenerateSampleModels(2, true).ToList();
+ testModels.Add(null); // Add null User
+ testModels[0].Profile = null; // Set the Profile to null for first User
+
+ // Act
+ var result = testModels.AsQueryable().Select(t => t != null && t.Profile != null && t.Profile.UserProfileDetails != null ? (long?)t.Profile.UserProfileDetails.Id : null).ToArray();
+ var resultDynamic = testModels.AsQueryable().Select("np(it.Profile.UserProfileDetails.Id)").ToDynamicArray();
- string q = "Profile?.UserProfileDetails?.Id > 0 || Profile?.UserProfileDetails?.Id2 > 0";
- string t = X(q);
+ // Assert
+ Check.That(resultDynamic).ContainsExactly(result);
+ }
+
+ [Fact]
+ public void ExpressionTests_NullPropagating_Value()
+ {
+ // Arrange
+ var testModels = User.GenerateSampleModels(2, true).ToList();
+ testModels.Add(null); // Add null User
+ testModels[0].Profile = null; // Set the Profile to null for first User
// Act
- // (Profile != null ? (Profile.UserProfileDetails != null ? Profile.UserProfileDetails.Id : null) : null)
- var result = testModels.AsQueryable().Where(t);
+ var result = testModels.AsQueryable().Select(t => t != null && t.Profile != null && t.Profile.UserProfileDetails != null ? t.Profile.UserProfileDetails.Id : 100).ToArray();
+ var resultDynamic = testModels.AsQueryable().Select("np(it.Profile.UserProfileDetails.Id, 100)").ToDynamicArray();
// Assert
- Check.That(result).IsNull();
+ Check.That(resultDynamic).ContainsExactly(result);
}
- private string X(string text)
+ [Fact]
+ public void ExpressionTests_NullPropagating_ThrowsException()
{
- var newText = new List();
- string[] spaceParts = text.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
- foreach (string spacePart in spaceParts)
- {
- string[] parts = spacePart.Split(new[] { "?." }, StringSplitOptions.RemoveEmptyEntries);
- if (parts.Length > 1)
- {
- var list = new List();
- for (int i = 0; i < parts.Length; i++)
- {
- list.Add(string.Join(".", parts.Take(parts.Length - i)));
- }
-
- var stringBuilder = new StringBuilder();
- list.Reverse();
- for (int i = 0; i < list.Count - 1; i++)
- {
- stringBuilder.Append($"({list[i]} != null ? ");
- }
-
- stringBuilder.Append($"({list.Last()}) {string.Concat(Enumerable.Repeat(" : null)", parts.Length - 1))}");
-
- newText.Add(stringBuilder.ToString());
- }
- else
- {
- newText.Add(spacePart);
- }
- }
+ // Arrange
+ var q = User.GenerateSampleModels(1, true).AsQueryable();
- return string.Join(" ", newText);
+ // Act
+ Check.ThatCode(() => q.Select("np()")).Throws();
+ Check.ThatCode(() => q.Select("np(it.Profile.UserProfileDetails.Id, 1, 2)")).Throws();
+ Check.ThatCode(() => q.Select("np(1)")).Throws();
}
[Fact]
diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs
index c29ef4c6..7a95a440 100644
--- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs
@@ -1,5 +1,4 @@
-using System.Linq.Dynamic.Core.Parser;
-using System.Linq.Expressions;
+using System.Linq.Expressions;
using NFluent;
using Xunit;
@@ -40,5 +39,35 @@ public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid_Invalid()
// Assert
Check.That(result).IsNull();
}
+
+ [Fact]
+ public void ExpressionHelper_GenerateAndAlsoNotNullExpression()
+ {
+ // Assign
+ Expression> expression = (x) => x.Relation1.Relation2.Id;
+
+ // Act
+ Expression result = _expressionHelper.GenerateAndAlsoNotNullExpression(expression);
+
+ // Assert
+ Check.That(result.ToString()).IsEqualTo("(((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null))");
+ }
+
+ class Item
+ {
+ public int Id { get; set; }
+ public Relation1 Relation1 { get; set; }
+ }
+
+ class Relation1
+ {
+ public int Id { get; set; }
+ public Relation2 Relation2 { get; set; }
+ }
+
+ class Relation2
+ {
+ public int Id { get; set; }
+ }
}
}