LINQ expression to expression tree via API in VB - vb.net

I have a relatively simple LINQ expression which I need to convert into VB expression tree syntax. This is likely an easy task for folks that are familiar, but I am new to the realm of LINQ expression trees.
In my samples, you see a "New Int16() {}" array. That value must be parameterized at run-time with an array of values from another code element.
my query is:
from i in tblInstitutions
let ChildHasCategory = i.tblInstCtgyHistories.Where(Function(CtgyHist) CtgyHist.EndDate is Nothing AND ( (New Int16() {32,35,38,34}).Contains(CtgyHist.InstCtgyCodeFK)))
where ChildHasCategory.Any()
select i
Which can also be represented as:
tblInstitutions
.Select (i => new {
i = i,
ChildHasCategory = (IEnumerable<tblInstCtgyHistory>)(i.tblInstCtgyHistories)
.Where (
CtgyHist =>
((CtgyHist.EndDate == null) &
(IEnumerable<Int16>)(new Int16[] { 32, 35, 38, 34 } ).Contains (CtgyHist.InstCtgyCodeFK)
)
)
}
)
.Where ($VB$It => $VB$It.ChildHasCategory.Any ())
.Select ($VB$It => $VB$It.i)
This is going to be used in the context of a custom filter in an ASP.NET Dynamic Data web application. I'd like to mimic the default approach.
A sample of one of the other dynamic filter code-behind is:
Public Overrides Function GetQueryable(source As IQueryable) As IQueryable
Dim value = TextBox1.Text
If String.IsNullOrWhiteSpace(value) Then
Return source
End If
If DefaultValues IsNot Nothing Then
DefaultValues(Column.Name) = value
End If
Dim parameter = Expression.Parameter(source.ElementType)
Dim columnProperty = Expression.PropertyOrField(parameter, Column.Name)
Dim likeValue = Expression.Constant(value, GetType(String))
Dim condition = Expression.Call(columnProperty, GetType(String).GetMethod("Contains"), likeValue)
Dim where = Expression.Call(GetType(Queryable), "Where", New Type() {source.ElementType}, source.Expression, Expression.Lambda(condition, parameter))
Return source.Provider.CreateQuery(where)
End Function

I'm not sure you really need to worry about expression trees here. First off, we should be able to express your query as follows:
Dim targetCodes = new Int16() {32, 35, 38, 34 } ' This could be data driven as well
return from i in tblInstitutions
where i.tblInstCtgyHistories.Any(Function(ctgyHist) ctgyHist.EndDate is Nothing AndAlso
targetCodes.Contains(ctgyHist.InstCtgyCodeFK))
select i
Given that, under what case do you need the custom expression tree?

Related

How to not add to the list, when the return of a LINQ IEnumerable is empty?

I do have the following VB.NET code:
Dim list = directoryQuery.Select(
Function(d) New With {
.dir = d.FullName,
.acl = GetFileSystemAccessRule(d).Select(
Function(a) New With {.if = a.Reference.ToString()}
)
}
)
End Sub
Sometimes the return of GetFileSystemAccessRule(d).Select is Return Enumerable.Empty(Of FileSystemAccessRule)(). In that case, I would like to neither add .directory nor .acl to that list. I want to skip it.
So I tought about the options to remove afterwards the empty items.
//tried but failed:
list = list.Where(Function(a) a.acl IsNot Enumerable.Empty(list)).ToList()
//tried but failed:
list = list.Where(Function(a) a.acl IsNot Nothing).ToList()
But unfortunately all of them failed. What do I wrong?
I think this would be the way to go:
list = list.Where(Function(a) a.acl.Any())
or something closely resembling that (I'm not very well versed in VB.NET syntax).

Statement in linq lambda expression

I have a list of it, that is my SelectedValue from some ComboBox.
Dim AppsInt = MyApps.CheckedItems.Select(Function(x) Convert.ToInt32(x.Value)).ToList()
And i have this object that is a list( of t)
Dim myObj = New List( Of Item )
Dim FooItem = New item ( 42 )
What I want is to get my list of Int into my object. With Something that would look like this in C#:
AppsInt.foreach( x => myObj .add(new Item(x) ) ) ;
What i have done so far is sending me a "do not produce a result" error:
AppsInt.ForEach( Function(it) myObj.Add(New Item(it)) )
How can i do it ? How to make this linq lambda work?
you should change function(it) to sub(it) .
Or:
Dim myObj = AppsInt.Select( Function(it) New Item(it)).ToArray()
Lambda expression inside you ForEach expression does not returns any result (and compiler said it to you). It means that you have two ways to solve it:
Add return statement into your lamda expression that will return anything:
AppsInt.ForEach(Function(it)
myObj.Add(New Item(it))
Return 1 ' it's not matter what you will return here.
End Function)
Change Function(it) to Sub(it). Sub is not obliged to return any value.
Second option is more preferable.

Calling a function within Entity Framework Select

A property from my object (oJobs) is as follows:
Private _brandlist As List(Of DAL.Brand)
Public Property PostBrandList() As List(Of DAL.Brand)
Get
Return _brandlist
End Get
Set(ByVal value As List(Of DAL.Brand))
_brandlist = value
End Set
End Property
In the database, the brand list is stored as a string separated by comma e.g. the column 'brands' can be a string '3,45,2' where each number represents an id of a brand stored in another table.
my select query is as below:
Dim jobposts As List(Of oJobs) = From j In db.JobPostings
Select New oJobs With { 'hiding all others for code brevity
.PostBrandList = 'problem is here'
}
Since j.BrandList will return a string, I will need to split that string and for each number, run another query to finally return and assign a List(Of DAL.Brand) into .PostBrandList
For those who might ask "what have you tried?",
I have run the query, then did a for each to add the list of brands later - succeeded but not optimal
Coded a function that takes the list as a parameter and returns a separate list of objects - very silly.
Also, I am not allowed to normalize the DB :(
Not tested and might need some tweaking but heres one idea. you will also need to change your property to an IEnumerable rather than List. Because the second linq query is embedded within the first, I believe it should execute it all as one query, but you should check it to make sure.
Dim jobposts As List(Of oJobs) = From j In db.JobPostings
Select New oJobs With { 'hiding all others for code brevity
.PostBrandList = From b In db.Brands Where j.Brands = b.ID Or j.Brands.StartsWith(b.ID & ",") Or j.Brands.EndsWith("," & b.ID) Or j.Brands.Contains("," & b.ID & ",") Select b
}
In c# you can use
.Select(x=>new {x.BrandList})
.ToList() //Materialize first before calling function
.Select(x=> new oJobs{
PostBrandList =
db.Brands.Where(z=>
x.BrandList
.Split(',')
.Select(y=>int.Parse(y.Trim()))
.Contains(z.Id))
.ToList()
});
Note that you must materialize entity first before calling String.Split
I don't know how to translate that to VB.NET.
Of course it will cause SELECT n+1 problem because you can't use join.
If you can't normalize table, my other suggestion is to create indexed view (sql server), so you can use join and improve performance.
Indexed view https://msdn.microsoft.com/en-us/library/ms191432.aspx
You could try it with the Let statement:
Dim jobposts As List(Of oJobs) = From j In db.JobPostings
/* li in the LINQ Statement represents one string value from the BrandList list */
Let postBrandElemsList = j.BrandList.Split(',').Select(Function(li) New DAL.Brand With { ... /* Field initializatione of the Class DAL.Brand here */ }
Select New oJobs With
{
.PostBrandList = postBrandElemsList
}
I'm sorry for the probably bad VB.NET syntax, you should check this when implementing it in your code.
Maybe you would just want to use the Split function on the column brands into an array and iterate through the result, using the Find function to retrieve the brand objects?

Linq to Moq - Nullable Types

I am writing a unit test and in it trying to setup a simple generic list containg mocks of an entity class...
Dim schedules = New List(Of Schedule) From
{
Mock.Of(Of Schedule)(Function(s) s.ActiveFrom = "2010-01-01" AndAlso
s.ActiveUntil = New DateTime?("2110-01-01"))
}
Schedule.ActiveFrom is a Date and Schedule.ActiveUntill is a Nullable(Of Date).
When i run the unit test i get the following error message...
The binary operator AndAlso is not defined for the types 'System.Nullable[System.Boolean] >and System.Boolean
I'm stumped; Where am I going wrong?
This isn't of much help to your VB Unit test, but the C# equivalent works just fine:
var schedules = new List<Schedule>
{
Mock.Of<Schedule>(
s => s.ActiveFrom == new DateTime(2010, 01, 01)
&& s.ActiveUntil == new Nullable<DateTime>(new DateTime(2110, 01, 01)))
};
Assert.IsTrue((schedules.Count == 1) &&
schedules.Single(_ => (_.ActiveFrom == new DateTime(2010, 01, 01))
&& (_.ActiveUntil == new DateTime(2110, 01, 01))) != null);
It also isn't related to the implicit comparison of DateTime to DateTime? either as careful unpacking of the DateTime? also fails:
AndAlso If(Not s.ActiveUntil.HasValue, False,
s.ActiveUntil.Value = New DateTime("2110-01-01")
I believe the issue may relate to Mock.Of's usage of parsed Expressions during construction of the mock from the predicate provided - possibly there is nuance in VB which wasn't considered.
If so, and assuming you don't want to change your unit tests to C#, you may need to either need to build the Expressions by hand to get the Mock.Of(predicate) goodness to work, or revert to old style creation of your Mock / Fake objects:
Dim scheduleMock As New Mock(Of Schedule)
scheduleMock.SetupGet(Function(s) s.ActiveFrom).Returns("2010-01-01")
scheduleMock.SetupGet(Function(s) s.ActiveUntil).Returns(New DateTime?("2110-01-01"))
Dim schedules = New List(Of Schedule) From
{
scheduleMock.Object
}
Assert.IsTrue(schedules.Count = 1)
Assert.IsTrue(schedules.First.ActiveFrom = "2010-01-01"
AndAlso schedules.First.ActiveUntil = "2110-01-01")
The problem is that you actually have two different types. VB can't handle that. So you want to trap the nullable portion and if it is null, return a straight boolean.
One thing you can do is change:
s.ActiveUntil = New DateTime?("2110-01-01")
to
if(s.ActiveUntil is Nothing, FALSE, s.ActiveUntil = New DateTime?("2110-01-01"))
This way, if the field is null, a simple boolean is returned, and if it is not null, you can return the boolean results of the compare.

How can I create a DateTime value in a Linq Query?

I am trying to create a query in Linq to Entities. I want it to return objects that include a DateTime property derived from strings in the table I am querying. The data (in SQL Server) has a string date field (in the database it appears as VARCHAR(8)) called date_occurred. It has a String time field (varchar(6)) called time_occurred.
An example of the contents of date_occurred is "20131007" to represent Oct. 7, 2013. An example of the contents of time_occurred is "145710" to mean 10 seconds after 2:57pm.
I have tried two methods that don't work:
Dim ATQuery = From e In EntityContext.vwSecAppAuditToday
Order By e.date_occurred, e.time_occurred
Select New AuditEntry With {
.EventTime = DateTime.ParseExact(Trim(e.date_occurred) & Trim(e.time_occurred), "yyyyMMddHHmmss", CultureInfo.InvariantCulture),
.ServerName = e.server_name
}
This throws a NotSupportedException with a message stating: "LINQ to Entities does not recognize the method 'System.DateTime ParseExact(System.String, System.String, System.IFormatProvider)' method, and this method cannot be translated into a store expression."
Before that, I tried:
Dim ATQuery = From e In EntityContext.vwSecAppAuditToday
Order By e.date_occurred, e.time_occurred
Select New AuditEntry With {
.EventTime = New DateTime(Integer.Parse(e.date_occurred.Substring(0, 4)),
Integer.Parse(e.date_occurred.Substring(4, 2)),
Integer.Parse(e.date_occurred.Substring(6, 2)),
Integer.Parse(e.time_occurred.Substring(0, 2)),
Integer.Parse(e.time_occurred.Substring(2, 2)),
Integer.Parse(e.time_occurred.Substring(4, 2))),
.ServerName = e.server_name
}
This also throws a NotSupportedException. In this case, the message states: "Only parameterless constructors and initializers are supported in LINQ to Entities."
Is what I am trying to do possible using Linq to Entities?
Edit: Comment Alert
For those who read this post later, Moho and Matt Johnson have made especially helpful comments. I have marked these with +1.
Select an anonymous class that contains the fields of interest (called a projection), then create DateTime struct per item after the IQueryable has been enumerated:
Dim ATQuery = From e In EntityContext.vwSecAppAuditToday
Order By e.date_occurred, e.time_occurred
Select New With {
.DateOccurred = e.date_occurred,
.TimeOccurred = e.time_occurred,
.ServerName = e.server_name
}
Dim q2 = From e In ATQuery.ToArray()
Select New AuditEntry With {
.EventTime = DateTime.ParseExact(Trim(e.DateOccurred) & Trim(e.TimeOccurred), "yyyyMMddHHmmss", CultureInfo.InvariantCulture),
.ServerName = e.ServerName
}
Your New DateTime contains only integer, so it looks like 20131008011300 (for 2013/10/08 01:13:00)
/ between date, : between time and a space between date and time are missed