{"id":1803,"date":"2009-07-10T20:22:00","date_gmt":"2009-07-10T20:22:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vbteam\/2009\/07\/10\/dynamic-searching-using-linq-dataset-joins\/"},"modified":"2024-07-05T12:48:44","modified_gmt":"2024-07-05T19:48:44","slug":"dynamic-searching-using-linq-dataset-joins","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/vbteam\/dynamic-searching-using-linq-dataset-joins\/","title":{"rendered":"Dynamic Searching using LINQ &#8211; Dataset + Joins"},"content":{"rendered":"<p>About two years ago I posted some <a href=\"http:\/\/blogs.msdn.com\/vbteam\/archive\/2007\/08\/29\/implementing-dynamic-searching-using-linq.aspx\">code<\/a> that shows how to <a href=\"http:\/\/blogs.msdn.com\/vbteam\/archive\/2007\/08\/29\/implementing-dynamic-searching-using-linq.aspx\">dynamically construct LINQ queries at runtime<\/a>.&nbsp; On average there&#8217;s a couple questions per month about trying it in some edge case, and usually the support already exists within the API.&nbsp; Recently though someone posted a question that has to do with Datasets and Joins that raised some interesting issues:<\/p>\n<p><em>I have a dataset with two datatables <\/em><em>(One Loaded From A CSV file) <\/em><em>and <\/em><em>(the other loaded from a query) <\/em><em>what I would like to be able to do is <\/em><em>set the criteria (i.e dtCSV.Field1=dtQuery.Field7 AND (dtCSV.Field5 != dtQuery.Field4 or dtCSV.Field5 != dtQuery.Field9)<\/em><\/p>\n<p><em>Returning dtQuery.Field1, dtCSV.Field5<\/em><\/p>\n<p>Previously the API supported conditions of the form &#8220;object.property=value&#8221;, but here we have something of the form &#8220;object1.property = object2.property&#8221;.&nbsp; It&#8217;s still a completely valid expression, but it means the API will need to generate a different expression tree.<\/p>\n<p>Let&#8217;s walk through how you can add this support to the API:<\/p>\n<p>1. At the end of Module DynamicQuery add the following extension method:<\/p>\n<p class=\"MsoNormal\"><span>&#8216;Extension method that can be called off any type that implements IEnumerable(Of T), <\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;which constructs a Condition with T as the element type and compares one property (or field) with another<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>&lt;Extension(), EditorBrowsable(EditorBrowsableState.Always)&gt; _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> <span>Function<\/span> CreateMutliColumnCondition(<span>Of<\/span> T)(<span>ByVal<\/span> dataSource <span>As<\/span> IEnumerable(<span>Of<\/span> T), _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> propName <span>As<\/span> <span>String<\/span>, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>ByVal<\/span> condType <span>As<\/span> condition.Compare, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> propName2 <span>As<\/span> <span>String<\/span>) <span>As<\/span> Condition(<span>Of<\/span> T)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> Condition.CreateMutliColumn(dataSource, propName, condType, propName2)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><span><\/span><\/p>\n<p>2. Add this method to the Condition class:<\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;<\/span><span>&#8216;Constructs a Condition with T as the element type that compares one field\/property with another<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> <span>Shared<\/span> <span>Function<\/span> CreateMutliColumn(<span>Of<\/span> T)(<span>ByVal<\/span> dataSource <span>As<\/span> IEnumerable(<span>Of<\/span> T), _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> propertyName <span>As<\/span> <span>String<\/span>, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>ByVal<\/span> condType <span>As<\/span> Compare, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> propertyName2 <span>As<\/span> <span>String<\/span>) <span>As<\/span> Condition(<span>Of<\/span> T)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> <span>New<\/span> Condition(<span>Of<\/span> T)(propertyName, condType, propertyName2)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><span><\/span><\/p>\n<p><p>3. Add the following code to the Condition(Of T) class:<\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;Returns a MemberExpression for a property or field access.<span>&nbsp; <\/span>Also handles nesting scenarios<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Function<\/span> GetPropertyExpression(<span>ByVal<\/span> propName <span>As<\/span> <span>String<\/span>, <span>ByVal<\/span> paramExpr <span>As<\/span> ParameterExpression) <span>As<\/span> MemberExpression<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;Split the string to handle nested property access<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> s = propName.Split(<span>&#8220;.&#8221;c<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;Get the PropertyInfo instance for propName<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> pInfo = <span>GetType<\/span>(T).GetProperty(s(0))<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> callExpr = Expression.MakeMemberAccess(paramExpr, pInfo)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;For each member specified, construct the additional MemberAccessExpression<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;For example, if the user says &#8220;myCustomer.Order.OrderID = 4&#8221; we need an<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;additional MemberAccessExpression for &#8220;Order.OrderID = 4&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;<\/span><span>For<\/span> i = 1 <span>To<\/span> UBound(s)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>pInfo = pInfo.PropertyType.GetProperty(s(i))<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>callExpr = Expression.MakeMemberAccess(callExpr, pInfo)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Next<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> callExpr<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Friend<\/span> <span>Sub<\/span> <span>New<\/span>(<span>ByVal<\/span> propName <span>As<\/span> <span>String<\/span>, <span>ByVal<\/span> condType <span>As<\/span> Compare, <span>ByVal<\/span> propName2 <span>As<\/span> <span>String<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> paramExpr = GetParamInstance(<span>GetType<\/span>(T))<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> b <span>As<\/span> Expression = CombineExpression(GetPropertyExpression(propName, paramExpr), _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span>condType, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>GetPropertyExpression(propName2, paramExpr))<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>LambdaExpr = Expression.Lambda(<span>Of<\/span> Func(<span>Of<\/span> T, <span>Boolean<\/span>))(b, <span>New<\/span> ParameterExpression() {paramExpr})<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;<\/span><span>&#8216;Compile the lambda expression into a delegate<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>del = <span>DirectCast<\/span>(LambdaExpr.Compile(), Func(<span>Of<\/span> T, <span>Boolean<\/span>))<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><span><\/span><\/p>\n<p>4. (optionally) the old constructor in this method should be refactored to use the helper method we introduced in #3.<\/p>\n<p class=\"MsoNormal\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Friend<\/span><span> <span>Sub<\/span> <span>New<\/span>(<span>ByVal<\/span> propName <span>As<\/span> <span>String<\/span>, <span>ByVal<\/span> condType <span>As<\/span> Compare, <span>ByVal<\/span> value <span>As<\/span> <span>Object<\/span>, <span>ByVal<\/span> valueType <span>As<\/span> Type)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>Dim<\/span> paramExpr = GetParamInstance(<span>GetType<\/span>(T))<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> callExpr = GetPropertyExpression(propName, paramExpr)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;ConstantExpression representing the value on the left side of the operator<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> valueExpr = Expression.Constant(value, valueType)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> b <span>As<\/span> Expression = CombineExpression(callExpr, condType, valueExpr)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>LambdaExpr = Expression.Lambda(<span>Of<\/span> Func(<span>Of<\/span> T, <span>Boolean<\/span>))(b, <span>New<\/span> ParameterExpression() {paramExpr})<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;<\/span><span>&#8216;Compile the lambda expression into a delegate<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>del = <span>DirectCast<\/span>(LambdaExpr.Compile(), Func(<span>Of<\/span> T, <span>Boolean<\/span>))<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><span><\/span><\/p>\n<p><p>&nbsp;<\/p>\n<p>Now that the API supports it, we can do joins against Datasets:<\/p>\n<p class=\"MsoNormal\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8216;The code in this method is equivalent to the following query, but constructed dynamically<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;Dim filteredQuery = From csvRow In dtCSV _<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Join orderRow In dtOrders On csvRow.CustomerID Equals orderRow.CustomerID<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> dtCSV <span>As<\/span> <span>New<\/span> myDataset.dtCSVDataTable<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtCSV.Rows.Add(1, <span>&#8220;Redmond&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtCSV.Rows.Add(2, <span>&#8220;Toronto&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtCSV.Rows.Add(3, <span>&#8220;Seattle&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> dtOrders <span>As<\/span> <span>New<\/span> myDataset.dtOrdersDataTable<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtOrders.Rows.Add(10001, 1)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtOrders.Rows.Add(10002, 3)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtOrders.Rows.Add(10003, 4)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dtOrders.Rows.Add(10004, 2)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> query = <span>From<\/span> csvRow <span>In<\/span> dtCSV _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>From<\/span> orderRow <span>In<\/span> dtOrders<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> c = query.CreateMutliColumnCondition(<span>&#8220;csvRow.CustomerID&#8221;<\/span>, Compare.Equal, <span>&#8220;orderRow.CustomerID&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> filteredQuery = query.Where(c)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>DataGridView1.DataSource = (<span>From<\/span> row <span>In<\/span> filteredQuery _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Select<\/span> row.orderRow.OrderID, row.csvRow.CustomerID, row.csvRow.City).ToList()<\/span><span><\/span><\/p>\n<p><p>&nbsp;<\/p>\n<p>The key line here is this one:<\/p>\n<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> c = query.CreateMutliColumnCondition(<span>&#8220;csvRow.CustomerID&#8221;<\/span>, Compare.Equal, <span>&#8220;orderRow.CustomerID&#8221;<\/span>)<\/span><\/p>\n<p><p>Instead of comparing a column to a value we can now compare one column to another (either in the same table or in a different table).<\/p>\n<p><em>&lt;geeky overload resolution details&gt;<\/em><\/p>\n<p><em>Why did I&nbsp;name the method CreateMultiColumnCondition instead of just adding another overload called CreateCondition?&nbsp;&nbsp;To avoid introducing&nbsp;a breaking change!&nbsp; If you had code that called CreateCondition with the last parameter as a String, the compiler would infer the type parameter at that position to be String.&nbsp; But now with a new overload that also has a parameter of type String in that position, the compiler would now choose the new overload because that method is &#8220;less generic&#8221; (i.e. a closer match).<\/em><\/p>\n<p><em>I don&#8217;t like the idea of having to think through advanced overload resolution details to figure out which method will actually get called, so it seemed easier to just rename the method \ud83d\ude42<\/em><\/p>\n<p><em>&lt;\/geeky&gt;<\/em><\/p>\n<p>A better (and likely more performant) implementation would actually introduce an overload of Join similar to this one for Where:<\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;Filters an IEnumerable(Of T) according to the specified condition<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>&lt;Extension()&gt; _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> <span>Function<\/span> Where(<span>Of<\/span> T)(<span>ByVal<\/span> source <span>As<\/span> IEnumerable(<span>Of<\/span> T), _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> condition <span>As<\/span> Condition(<span>Of<\/span> T)) <span>As<\/span> IEnumerable(<span>Of<\/span> T)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> source.Where(condition.del)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><font face=\"Calibri\">&nbsp;<\/font><\/span><\/p>\n<p class=\"MsoNormal\">This&nbsp;would involve a bit more work in the API though as the Join method takes 3 delegates rather than 1.&nbsp; (so Condition(Of T) would have to generate 3 smaller trees rather than one big one).&nbsp; It&#8217;s already&nbsp;almost 6&nbsp;on a Friday though and I&#8217;m lazy, so this is left as an exercise to the reader \ud83d\ude42<\/p>\n<p class=\"MsoNormal\">&nbsp;<\/p>\n<p class=\"MsoNormal\">Full VS2008 .sln is attached.<\/p>\n<p>Jonathan<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/MSDNBlogsFS\/prod.evol.blogs.msdn.com\/CommunityServer.Components.PostAttachments\/00\/09\/82\/91\/99\/DynamicQueries.zip\">DynamicQueries.zip<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>About two years ago I posted some code that shows how to dynamically construct LINQ queries at runtime.&nbsp; On average there&#8217;s a couple questions per month about trying it in some edge case, and usually the support already exists within the API.&nbsp; Recently though someone posted a question that has to do with Datasets and [&hellip;]<\/p>\n","protected":false},"author":260,"featured_media":8818,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[21,195],"tags":[77,83,94,117,166],"class_list":["post-1803","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-im-a-vb","category-visual-basic","tag-iqueryable","tag-jonathan-aneja","tag-linqvb9","tag-orcas","tag-vb2008"],"acf":[],"blog_post_summary":"<p>About two years ago I posted some code that shows how to dynamically construct LINQ queries at runtime.&nbsp; On average there&#8217;s a couple questions per month about trying it in some edge case, and usually the support already exists within the API.&nbsp; Recently though someone posted a question that has to do with Datasets and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/1803","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/users\/260"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/comments?post=1803"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/1803\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media\/8818"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media?parent=1803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/categories?post=1803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/tags?post=1803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}