linq to xml enumerating over descendants - vb.net

Hi trying to write a simple linq query from a tutorial I read. But i cannot seem to get it to work. I am trying to display both the address in the attached xml document, but can only display the first one. Can someone help me figure out why both aren't being printed. Thank you very much
<?xml version="1.0" encoding="utf-8" ?>
<Emails>
<Email group="FooBar">
<Subject>Test subject</Subject>
<Content>Test Content</Content>
<EmailTo>
<Address>foo#bar.com</Address>
<Address>bar#foo.com</Address>
</EmailTo>
</Email>
</Emails>
Dim steve = (From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar")) _
Select content = email.Element("EmailTo").Descendants("Address")).ToList()
If Not steve Is Nothing Then
For Each addr In steve
Console.WriteLine(addr.Value)
Next
Console.ReadLine()
End If

Your current query returns a List<IEnumerable<XElement>>. That means you need two nested foreach loops: one to loop over the list, and another to loop over the content of the IEnumerable<XElement>.
Instead, you could update your LINQ query to use the Enumerable.SelectMany method and get to the addresses directly. In query format a SelectMany is represented by using a second from clause to indicate a subquery. This would resemble the following:
Dim query = (From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar")) _
From addr In email.Element("EmailTo").Descendants("Address") _
Select addr.Value).ToList()
If query.Any() Then
For Each addr In query
Console.WriteLine(addr)
Next
End If
Also, the ToList isn't needed if you only want to iterate over the results and don't intend to use the result as a list for other purposes.
EDIT: to explain how this query works let's break it down in 2 parts:
First:
From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar")) _
This queries for all <Email> nodes and only matches the ones that have a group attribute value of "FooBar".
Second:
From addr In email.Element("EmailTo").Descendants("Address") _
Select addr.Value
This is a subquery that continues where the first part (above) ended. It essentially is a way to further query the results of the original query. Here we query for all <Address> nodes and, finally, select their Value for the inner text of the nodes. The reason we need to do this is because Descendants("Address") returns a IEnumerable<XElement> containing all "Address" elements. We need to perform an additional query (or foreach) to iterate over those values and extract their values.
Another way to illustrate this is by breaking it down in 2 queries:
Dim query1 = From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar"))
Select email.Element("EmailTo").Descendants("Address")
Dim query2 = query1.SelectMany(Function(addr) addr.Select(Function(a) a.Value))
Notice the use of SelectMany in query2. The Select in query2 is that additional effort to loop over the IEnumerable that I mentioned earlier. The original query is clearer than query1/query2, but I wrote them just to clarify the point.

Related

MS Access select query inside another select query for selecting another field value

I don't think of any better way.
What I am trying to do is grab some data from table where the field childitem="NL" and the complex part in selecting fields to show is that, I need to make new field Consumption but this field value will be same childqty but when the field processname first 2 character is SC, I need to make the Consumption value to a childqty of another field: current father (4-SCF-329...[id:30]) is child to another father (4-FCM-3290...[id:17]) which is indeed child to another father (4-MCS-329....[id:21])
My Query:
SELECT tblBom.processname AS Process,
tblBom.child AS [SAP Code],
tblBom.childname AS Material,
iif(LEFT(tblBom.processname,2)="SC",
(SELECT childqty FROM tblBom WHERE tblBom.child like (SELECT father FROM tblBom WHERE child Like tblBom.father)),
tblBom.childqty
) AS consumption,
tblBom.childrate AS [Landed Rate],
tblBom.childrate * tblBom.childqty As RATE
FROM tblBom
WHERE tblBom.childitem Like "NL";
I want the tblBom.father last in the 5th line of code to be the main Queries father. Is there a way to make it as variable somewhere or any way. If I change it to "4-SCF*" that will work.
My Table
Excel Detailed View:
Orange: Condition checking for word "SC"
Yellow: The value that need to be replaced
Green: The value that will be placed on the yellow
ReddishPink: The path to Green value for query
Desired OutPut
My solution requires two calculated fields and two queries. The calculated fields are UltimateFather and consumption. we require two queries because the problem doesn't guarantee that a child's father has a value of childitem like "NL". Hence we can't throw out any data before calculating each child's UltimateFather. Ultimate Father is based on adding the public function GetUltimateFather to a code module. Getting the UltimateFather is a recursive problem so see here for commentary on recursive functions in queries:
Is it possible to create a recursive query in Access?
'calculated fields
UltimateFather: GetUltimateFather([child])
consumption: IIf([processname] Like ("SC*"),DLookUp("childqty","tblBom","father = '" & [UltimateFather] & "'"),[childqty])
Public Function GetUltimateFather(childid As String) As String
Dim currentchild, hasfather As String
currentchild = childid
'string variables make sure to escape and delimit strings properly'
hasfather = Nz(DLookup("father", "tblBom", "child = '" & currentchild & "'"), "")
If hasfather = "" Then
GetUltimateFather = currentchild
Else
GetUltimateFather = GetUltimateFather(hasfather) 'get next father in chain
End If
End Function

How NOT filter LINQ results if criteria list is empty?

When filter criteria is passed to my LINQ query, no problem:
Dim statesSelected As String = {‘GA’,’FL’}
Dim results As IEnumerable(Of Person) = _
From p As Person in dc.Persons _
Where statesSelected.Contains(p.StateCode)
HOWEVER, if no filter criteria are selected, then I want ALL states returned (instead of none which is what the above would do). How can I do this, please? I realize I could have an IF statement around the query, but in reality I’ll have many filters (and will want to handle them all within the one query).
Thanks for any help.
I am not sure if this would translate to SQL, but you can try this approach:
Dim results As IEnumerable(Of Person) = _
From p As Person in dc.Persons _
Where statesSelected Is Nothing OrElse statesSelected.Contains(p.StateCode)
In this case if your variable statesSelected is nothing then only the first part of query would be executed, otherwise first part would be true and only second condition would matter
Try this out:
Dim results As IEnumerable(Of Person) = _
From p As Person In Persons
Where If(statesSelected.Length < 1, p.StateCode <> "", statesSelected.Contains(p.StateCode))
What it's doing is checking to make sure statesSelected has elements. If not, it simply brings back all elements. If there values in statesSelected, it brings back the ones that contain that state.
The magic is happening in the ternary If() : https://msdn.microsoft.com/en-us/library/bb513985.aspx?f=255&MSPPError=-2147217396

vb.net DAL Specify columns returned

I have a Data Access Layer class that has a method (GetPeople) that will retrieve records from a SQL Server table (people). This table has more than 20 fields, including varbinary type.
Right now, SQL query is something like
SELECT * FROM people
From my BLL class, I will call DAL.GetPeople(), which will return all columns.
What would be the best way to specify which columns to return, so I could improve performance? For example, sometimes I would like to return all fields, other times, just one or two.
UPDATE
To explain it better:
In DAL I have a method GetPeople() which calls a SQL Server function GetPeople.
In BLL I have a method GetPeople() which calls DAL.GetPeople(), after doing some business logic.
In my presentation layer, I call BLL.GetPeople().
This is working, but on SQL function, I have "SELECT * FROM people". Sometimes I would like to retrieve only one column (eg. name) from table, but in this case all columns are returned, which I think is affects performance.
So, I would like to have a kind of dynamic SELECT query on this SQL Server function, whose columns returned would depend on how I call the function...
I think you are after something like this where you can pass in a comma-seperated list of column names
Private Function GenerateQuery(ByVal columnNames As String) As String
' columnNames in the following format 'column1,column2,column3'
Dim lstColumnNames As String() = Split(columnNames, ",")
Dim strSQL As New StringBuilder
strSQL.Append("SELECT ")
For intColNumber As Integer = 0 To lstColumnNames.GetUpperBound(0)
strSQL.Append("[")
strSQL.Append(lstColumnNames(intColNumber))
strSQL.Append("]")
If intColNumber < lstColumnNames.GetUpperBound(0) Then
strSQL.Append(", ")
End If
Next
strSQL.Append(" FROM People ")
Return strSQL.ToString
End Function
You can use it like this: SqlCommand.CommandText = GenerateQuery("column1,column2,column3")
The column names are wrapped in [] symbols so you don't have to worry about reserved words causing the database to error.
Change your SQL-query to something like
SELECT column1, column2, column3 FROM people;
EDIT:
What you are going to need to do is create function that will put your SQL string together for you. When i did this before, I had all of the available fields in a checked-list control, and if i wanted them pulled, I checked them. The checked items were then put through the function to assemble the string. It should be pretty simple since there are not any joins going on.

how to replace text in a multifield value column in access

I've got a tablea such as below, I know its bad design having multifield value column but I'm really looking for a hack right now.
Student | Age | Classes
--------|------|----------
foo | 23 | classone, classtwo, classthree, classfour
bar | 24 | classtwo, classfive, classeight
When I run a simple select query as below, I want the results such a way that even occurrence of classtwo is displayed as class2
select student, classes from tablea;
I tried the replace() function but it doesnt work on multivalued fields >_<
You are in a tough situation and I can't think of a SQL solution for you. I think your best option would be to write a VB function that will take the string of data, parse it out (replacing) the returning you the updated string that you can update your data with.
I can cook up quite a few ways to solve this.
You can explode the mv by using Classes.Value in your query. This will cause one row to appear for each value in the query and thus you now can use replace on that. However, this will result in one separate row for each class.
So use this:
Select student, classes.Value from tablea
Or, for this example:
Select student, replace(classes.Value,"classtwo","class2") as myclass
from tablea
If you want one line, AND ALSO the multi value classes are NOT from another table (else they will be returning ID not text), then then you can use the following trick
Select student, dlookup("Classes","tablea","id = " & [id]) as sclasses
from tablea
The above will return the classes separated by a space as a string if you use dlookup(). So just add replace to the above SQL. I suppose if you want, you could also do replace on the space back to a "," for display.
Last but not least, if this those classes are coming from another table, then the dlookup() idea above will not work. So just simply create a VBA function.
You query becomes:
Select student, strClass([id]) as sclasses from tablea
And in a standard code module you create a public function like this:
Public Function strClass(id As Variant) As String
Dim rst As DAO.Recordset
If IsNull(id) = False Then
Set rst = CurrentDb.OpenRecordset("select Classes.Value from tableA where id = " & id)
Do While rst.EOF = False
If strClass <> "" Then strClass = strClass & ","
strClass = strClass & Replace(rst(0), "classtwo", "class2")
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
End If
End Function
Also, if you sending this out to a report, then you can DUMP ALL of the above ideas, and simply bind the above to a text box on the report and put the ONE replace command around that in the text box control. It is quite likely you going to send this out to a report, but you did ask how to do this in a query, and it might be the wrong question since you can "fix" this issue in the report writer and not modify the data at the query level. I also think the replace() command used in the report writer would likely perform the best. However, the above query can be exported, so it really depends on the final goal here.
So lots of easy ways to do this.

LINQ / VB.NET select distinct from dataset

I have a single columned datatable inside a single tabled dataset.
I just want to convert this dataset to distinct rows. Here is my code, it gives compile error '.' expected. What am I doing wrong? (I tried adding the ., still same error). I know this is something stupidly obvious. PLZ Save me! ;)
Thanks much in advance!
Dim query = _
From email In ds.Tables(0) _
Select email.Field<string>("Email").Distinct()
EDIT: DOH! MIXING VB/C# SYNTAX HERE! I changed to (Of String) and it works... BUT NOW 'query' is an ienumerable collection of characters... not a datatable... so how do I convert back easily without manually doing a loop?? Plz advise!
You are applying the Distinct method to each string, not the result of the query. As strings are collection of character, you can apply extension methods to them too.
Put parentheses around the query:
Dim query = _
(From email In ds.Tables(0) _
Select email.Field(Of String)("Email")).Distinct()
You can use the distinct values after getting the values into datatabale by:
dtFiltered = dtNotFiltered.DefaultView.ToTable(True, "RAMI_ISSA") 'Get distinct values (by stting the first parameter to True)