Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Giving Clarity to LINQ Queries by Extending Exp...

Giving Clarity to LINQ Queries by Extending Expressions

In this session we’ll learn about .Net Expression trees by discovering how they work and applying the knowledge to LINQ using the pipes and filters pattern. LINQ and Entity Framework are both commonly used in the .Net ecosystem, but even well-written applications can have LINQ queries that are difficult to understand. Because LINQ is so flexible, it can be written in ways that fail to communicate the developer’s intent. Well-written LINQ should be so clear as to be self-documenting. To write clear LINQ, it helps to understand the details of a few LINQ components that improve LINQ’s readability.

We’ll be showing how to use a pipe and filter pattern to make LINQ queries easier to comprehend. We will take a deep dive into expression trees to understand how they work, and how to manipulate them for maximum re-usability.

Avatar for edcharbeneau

edcharbeneau

August 01, 2018
Tweet

More Decks by edcharbeneau

Other Decks in Technology

Transcript

  1. Entity Framework & LINQ Expression< Func <TModel , bool >>

    People.Where(p => p.Title == “Developer”)
  2. Telerik Data Grid @(Html.Kendo().Grid<Post> .Columns(columns => { columns.Bound(p => p.Title);

    columns.Bound(p => p.Author); columns.Bound(p => p.Pubished); }) ) @EdCharbeneau #DataGrid
  3. Expressions in C# • Representation of code as data •

    Meta -programming – Analyze , rewrite , and translate code at runtime
  4. Homo- iconicity Same syntax for executable code as data representation

    1 //Executable representation 2 Func<int, int> plusOne = n => n + 1; 3 4 //Data representation 5 Expression<Func<int, int>> plusOne = n => n + 1;
  5. Homo- iconicity Same syntax for executable code as data representation

    1 //Executable representation 2 Func<int, int> plusOne = n => n + 1; 3 4 //Data representation 5 Expression<Func<int, int>> plusOne = n => n + 1;
  6. Expression Factory 1 // new Expression() invalid! 2 3 var

    x = Expression.Constant(1); 1 constant @EdCharbeneau #ExpressionFactory
  7. Expression Factory 1 // new Expression() invalid! 2 3 var

    x = Expression.Constant(1); 4 var y = Expression.Constant(1); 1 constant 1 constant @EdCharbeneau #ExpressionFactory
  8. Expression Factory 1 // new Expression() invalid! 2 3 var

    x = Expression.Constant(1); 4 var y = Expression.Constant(1); 5 var sum = Expression.Add(x,y); + 1 1 Add constant constant @EdCharbeneau #ExpressionFactory
  9. Expression Factory 1 // new Expression() invalid! 2 3 var

    x = Expression.Constant(1); 4 var y = Expression.Constant(1); 5 var sum = Expression.Add(x,y); 6 7 Console.WriteLine(sum); 8 // (1 + 1) 9 10 Console.WriteLine(sum.GetType()); 11 // SimpleBinaryExpression + 1 1 Add constant constant SimpleBinaryExpression @EdCharbeneau #ExpressionFactory
  10. Compile 1 Expression<Func<int, int>> plusOne = n => n +

    1; 2 Console.WriteLine(plusOne.ToString()); 3 // n => (n + 1)
  11. Compile 1 Expression<Func<int, int>> plusOne = n => n +

    1; 2 Console.WriteLine(plusOne.ToString()); 3 // n => (n + 1) 4 5 var x = plusOne.Compile(); 6 Console.WriteLine(x(1)); 7 // 2
  12. Runtime Modification ExpressionVisitor • used to traverse or rewriteexpression trees

    • Abstract class – Inherit and override • .Visit(expression) – Recursively walks the tree – Returns an Expression https://msdn.microsoft.com/en -us/library/bb882521(v=vs.90).aspx
  13. Pipeline Pipes and filters Filter Rule Rule Data Data Data

    Consumer Results Database Filter Rule Rule dbContext.Where (rule).Where(rule)
  14. Refactoring What ’s going on here? 1 postRepository.GetAll() 2 .Where(post

    => post.IsPublished && 3 post.PostedOn <= today && 4 (post.PostedOn >= cutoffDate || 5 post.Author == featuredAuthor && 6 post.PostedOn >= featuredAuthorCutoffDate));
  15. Let’s start simple What ’s going on here? 1 postRepository.GetAll()

    2 .Where(post => post.IsPublished && post.PostedOn <= today);
  16. Distinction The tale of two extension methods IEnumerable.Where (q =>

    q.Value == true) Function delegate Func <T, bool > IQueryable.Where (q => q.Value == true) Expression Expression< Func <T, bool >
  17. LINQ Where Chaining 1 //Both statements produce the same result

    2 3 //Concise 4 var query = query.Where(x => x.Value == 1 && x.Name == ”foo”); 5 6 //Configurable 7 var query = query.Where(x => x.Value == 1) 8 .Where(x => x.Name == ”foo”); SELECT x FROM table WHERE (value = 1 AND name = ‘foo’) @EdCharbeneau #LINQ
  18. LINQ Where Chaining 1 var query = query.Where(x => x.Value

    == 1); SELECT x FROM table WHERE (value = 1) => x == value 1
  19. LINQ Where Chaining 1 var query = query.Where(x => x.Value

    == 1) 2 .Where(x => x.Name == ”foo”); SELECT x FROM table WHERE (value = 1 AND name = ‘foo’) && => x == name foo == value 1
  20. LINQ Where Chaining 1 var query = query.Where(x => x.Value

    == 1); 2 query.Where(x => x.Name == ”foo”); 3 query.Where(x => x.IsDeleted == ”true”); SELECT x FROM table WHERE (value = 1 AND name = ‘foo’ AND IsDeleted = 1) && => x == name foo == value 1 && == isDeleted 1
  21. Custom Filters Extends type IQueryable< MyType > Return type IQueryable<

    MyType > Method .CustomMethodName() public static IQueryable< Post >ArePublished( this IQueryable< Post > posts ) { return posts.Where (post => post.IsPublished ); }
  22. 1 postRepository.GetAll() 2 .Where(post => post.IsPublished && 3 post.PostedOn <=

    today); Readability? Before After What ’s the intent? 1 postRepository.GetAll() 2 .ArePublished() 3 .PostedOnOrBefore(today);
  23. Now what? What ’s the intent? 1 IQueryable<Post> posts =

    postRepository.GetAll() 2 .Where(post => post.IsPublished && post.PostedOn <= today && 3 (post.PostedOn >= cutoffDate || post.Author == featuredAuthor && 4 post.PostedOn >= featuredAuthorCutoffDate));
  24. Lambda as an Expression tree post=>post.PostedOn >= cutoffDate >= cutoffDate

    PostedOn GreaterThanOrEqual => post Lambda parameter parameter constant Body
  25. Lambda as an Expression tree post=>post.PostedOn >= cutoffDate >= cutoffDate

    PostedOn GreaterThanOrEqual => post Lambda parameter parameter constant Body
  26. Lambda as an Expression tree post=>post.PostedOn >=cutoffDate >= cutoffDate PostedOn

    GreaterThanOrEqual => post Lambda parameter parameter constant Body
  27. Combine Like Expressions? >= => post Body == => post

    Body AND / OR? @EdCharbeneau #PredicateExtensions
  28. CombineLambdas >= constant Post.Property => post Body == constant Post.Property

    => post Body AND / OR? CombineLambdas(left, right, ExpressionType.AndAlso)
  29. Get Paramters >= constant Post.Property => post Body == constant

    Post.Property => post Body SubstituteParameterVisitor(left.Parameters[0], right.Parameters[0])
  30. Replace Parameters >= constant Post.Property Body == constant Post.Property post

    Body post Post.Property SubstitutionVisitor.VisitParameter
  31. Combined Expressions => post Lambda parameter Body >= && ==

    Expression.MakeBinary Expression.Lambda<Func<T, bool>>
  32. Readability? Before After 1 postRepository.GetAll() 2 .Where(post => post.IsPublished &&

    post.PostedOn <= today && 3 (post.PostedOn >= cutoffDate || post.Author == featuredAuthor && 4 post.PostedOn >= featuredAuthorCutoffDate)); 1 postRepository.GetAll() 2 .ArePublished() 3 .PostedOnOrBefore(today) 4 .Where(PostedOnOrAfter(cutoffDate) 5 .Or(FeaturedAuthorPostedrOnOrAfter(featuredAuthor,featuredAuthorCutoffDate)));
  33. New Requirements! Now we must support multiple featured authors Dynamic

    LINQ query? No problem, now we ’re equipped for it!
  34. Dynamic Queries Try this without expressions. 1 var rule =

    PredicateExtensions.PredicateExtensions.Begin<Post>(); 2 foreach (var authorName in featuredAuthorNames) 3 { 4 rule = rule.Or(FeaturedAuthorPostedOnOrAfter(authorName, featuredAuthorCutoffDate)); 5 } 6 return rule;
  35. Ed Charbeneau Developer Advocate for Progress <Telerik > Author <T>

    Podcast => “Eat Sleep Code the Official Telerik Podcast ” Twitter .Where (user == @EdCharbeneau )