LINQ with Option Strict Error in VB.Net - vb.net

I'm facing a trick issue with LINQ. I generate the above code:
... 'Returning an Object
Dim lReturn = (From tb_hb In lObjLNQContext.tb_hbs _
Where tb_hb.id_process = codigoProcessamento _
Order By tb_hb.dth_hb Ascending _
Select tb_hb.id_process, tb_hb.dth_hb).AsEnumerable
Return lReturn
When I check the lReturn DataType is Linq.DataQuery.
I used the code above to access the data:
For Each row In lResult
Console.WriteLine(row.dth_hb)
Everything is running well if I turn off Option Explicit. When I turn it on, compiler is showing me a meessage: Expression is of type 'Object', which is not a collection type. Referencing to lResult variable.
I realy don't know how to solve it.
Thanks for any help.

Your LINQ expression uses anonymous types, which are only available in one method.
Define a class to hold the two values id_process, dth_hb and change your select to create instances of the class. Then you can declare the function as returning List Of the new class
Example here

Related

Option Strict On and SQL stored procedure output parameter of nullable integer

There are several topics in this forum that come tantalisingly close to providing an answer to my question, but not quite what I need.
I am writing in VB.Net, retrieving data via TableAdapters and stored procedures. The stored procedures can return one or more Output parameters in the form of nullable integers.
This is some old code that I am revisiting and tidying up, including the addition of Option Strict On, and the Stored Procedure returns a zero where previously it returned a correct value. I can circumvent the problem but I would like to understand what "best practice" dictates for this circumstance.
This is the code before Option Strict was applied, and returns the correct value in the two Output parameters: RetVal (the return code, defined as an Enum) and UnspecifiedCategoryID (defined as an integer type).
Using oCategoriesTableAdapter As New YachtManagementDataSetTableAdapters.tvf_CategoriesTableAdapter
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=SQLReturn,
UnspecifiedCategoryID:=oCP.GviUnspecifiedCategorySubCategory,
UnspecifiedCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameCategory).UnspecifiedEntityID,
UnspecifiedSubCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameSubCategory).UnspecifiedEntityID,
VesselID:=oCP.GvoActiveVessel.ID
)
End Using
With Option Strict On, if I simply cast both to an integer, using CInt or use CType in order to remove the compiler error ("Option Strict On disallows narrowing from type 'Integer?' to type 'Integer'"), then I will always have a zero returned:
Using oCategoriesTableAdapter As New YachtManagementDataSetTableAdapters.tvf_CategoriesTableAdapter
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=CType(SQLReturn, Integer),
UnspecifiedCategoryID:=CType(oCP.GviUnspecifiedCategorySubCategory, Integer),
UnspecifiedCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameCategory).UnspecifiedEntityID,
UnspecifiedSubCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameSubCategory).UnspecifiedEntityID,
VesselID:=oCP.GvoActiveVessel.ID
)
End Using
I can circumvent the problem using this code:
Using oCategoriesTableAdapter As New YachtManagementDataSetTableAdapters.tvf_CategoriesTableAdapter
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=CType(SQLReturn, Integer),
UnspecifiedCategoryID:=TestNullableInteger,
UnspecifiedCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameCategory).UnspecifiedEntityID,
UnspecifiedSubCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameSubCategory).UnspecifiedEntityID,
VesselID:=oCP.GvoActiveVessel.ID
)
End Using
If TestNullableInteger.HasValue Then
oCP.GviUnspecifiedCategorySubCategory = TestNullableInteger.Value
Else
oCP.GviUnspecifiedCategorySubCategory = 0
End If
Another alternative is to change the data type of GviUnspecifiedCategorySubCategory itself to that of a nullable integer and check there as to whether a value has been returned.
Friend Property GviUnspecifiedCategorySubCategory As Integer?
Get
Return _lviUnspecifiedCategorySubCategory
End Get
Set(Value As Integer?)
If Value.HasValue Then
_lviUnspecifiedCategorySubCategory = Value
Else
_lviUnspecifiedCategorySubCategory = 0
End If
End Set
End Property
However, if the stored procedure has three or four Output parameters then, using this approach, the recoding starts to become onerous. The GetValueOrDefault method shows promise but, using a TableAdapter, I cannot see how this would work.
There's always the chance that I've stopped seeing the wood for the trees.
Any suggestions would be much appreciated.
From the comments to the question:
Is the FULL error message "Error BC32029 Option Strict On disallows Narrowing from type 'Integer?' to type 'Integer' in copying the value of 'ByRef' parameter 'RetVal' back to the matching argument."? – TnTinMn
Yes #TnTinMn, that is the correct full message. – Neil Miller
You are too focused on purpose of the code (database interaction) while ignoring the code syntax. The following code produces the same error message.
Sub DemoIssue()
Dim SQLReturn As Integer
SomeMethod(SQLReturn)
End Sub
Sub SomeMethod(ByRef RetVal As Integer?)
RetVal = 1
End Sub
Note that SQLReturn is a Integer type being passed as an argument to a method that takes a reference to a nullable integer.
If you would have clicked on the BC32029 in the error window to search for help on the error, you likely would have found Option Strict On disallows narrowing from type 'typename1' to type 'typename2' in copying the value of ByRef parameter 'parametername' back to the matching argument that explains:
A procedure call supplies a ByRef argument with a data type that widens to the argument's declared type, and Option Strict is On. The widening conversion is allowed when the argument is passed to the procedure, but when the procedure modifies the contents of the variable argument in the calling code, the reverse conversion is narrowing. Narrowing conversions are not allowed with Option Strict On.
To correct this error
Supply each ByRef argument in the procedure call with the same data type as the declared type, or turn Option Strict Off.
So all you need to do is define SQLReturn as Integer?
Regarding:
Ah! #TnTinMn - are you about to suggest changing the AllowDbNull property to False for the Output parameters in the TableAdapter itself? Yes, I've tested that and it works - that's a much better approach, I think. – Neil Miller
That is another option, but you need to understand the reason why it works is that TableAdapter code for sp_UpdateUnspecifiedCategory is rewritten to expect an Integer type for SQLReturn so there is no issue with ``SQLReturndefined as anInteger`
Edit To address comment:
SQLReturn is defined as an Enum with datatype Integer. I cannot define SQLReturnEnum as type Integer? How would you handle this case if you wanted to take advantage of Intellisense when using the Enum? If I coerce SQLReturn using CInt or CType I will only get a zero returned, even if I were to set the Output parameter property to AllowDbNull to False.
I believe that you are still trying to perform the type conversion on the argument sent to the method.
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=CType(SQLReturn, Integer),...
The problem with this is that CType(SQLReturn, Integer) is a function that returns a new value. As the argument is passed by reference (ByRef) it is this new value that can be modified in the method; such modification is not propagated back to SQLReturn.
Assuming SQLReturnEnum is defined like:
Public Enum SQLReturnEnum As Integer
[Default]
A
B
End Enum
Then an example of passing by reference a nullable Integer and retrieving its value as a SQLReturnEnum would be:
Sub CopyBackExample()
Dim byRefSqlReturn As Integer? ' declare a temp variable to be passed ByRef
SomeMethod(byRefSqlReturn)
' cast the temp variable's value to type SQLReturnEnum
Dim SQLReturn As SQLReturnEnum = If(byRefSqlReturn.HasValue, CType(byRefSqlReturn, SQLReturnEnum), SQLReturnEnum.Default)
End Sub

VBA: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to a late-bound functions

Compile Error:
Compile Error: Only user-defined types defined in public object
modules can be coerced to or from a variant or passed to a late-bound
functions.
I'm new to VBA and I was tasked with debugging some code for a custom screen in Dynamics SL. The first thing I did was to see if it compiled and I got the above message.
When I read the built-in help reference I found the following for the above error:
You attempted to use a public user defined type as a parameter or
return type for a public procedure of a class module, or as a field of
a public user defined type. Only public user defined types that are
defined in a public object module can be used in this manner.
I also went through these similar questions:
How to put user defined datatype into a Dictionary
Only user-defined type defined in public object modules can be coerced when trying to call an external VBA function
They have the same error but I don't see a collection object that the above two questions focused on.
If you may have any idea what may be causing this error please don't hesitate to suggest it.
Code:
Private Sub cpjt_entity_Chk(ChkStrg As String, retval As Integer)
Dim ldDate As Sdate
Dim xStrDailyPost As Sdate
ldDate.val = GetObjectValue("cpe_date")
'xStrDailyPost = DateToStr(ldDate)
'Call MsgBox("Daily Post Date: " & xStrDailyPost, vbOKOnly, "TEST")
serr1 = SetObjectValue("cld_id08", xStrDailyPost) <- Error highlights "xStrDailyPost"
End Sub
Definition for SetObjectValue:
Declare Function SetObjectValue Lib "swimapi.dll" Alias "VBA_SetObjectValue" (ByVal ctlname$, newval As Variant) As Integer
Thank you in advance!
You are probably working with code that was originally written with the Dynamics SL (actually it was Solomon IV at the time) Basic Script Language (BSL) macro language instead of VBA.
Regardless... the fix is, pass results of the "val" method of your xStrDailyPost instance of SDate. SO the code should look like:
serr1 = SetObjectValue("cld_id08", xStrDailyPost.val)
I've not actually tested this but I'm pretty sure this will address your issue.
If you want a little more background, "Sdate" is really just a very thin wrapper of an integer (actually I think it's a short, but I've never found I really needed to know for sure). the "Val" method returns the underlying integer in the SDate variable.

type var is not defined vb.net

I found an example in C# and from my understanding there is no alternative to 'var' in VB.NET. I am trying to create a datatable that will populate depending on a LINQ command further down in my code that calls this function. I have searched for a solution, but unable to find anything that works. Any assistance on what I should use would be appreciated. Note that I do have both Option Strict and Option Infer on as well.
Private Shared Function ToDataTable(rows As List(Of DataRow)) As DataTable
Dim table As New DataTable()
table.Columns.Add("Title")
table.Columns.Add("Console")
table.Columns.Add("Year")
table.Columns.Add("ESRB")
table.Columns.Add("Score")
table.Columns.Add("Publisher")
table.Columns.Add("Developer")
table.Columns.Add("Genre")
table.Columns.Add("Date")
For Each row As var In rows
table.Rows.Add(row.ItemArray)
Next
Return table
End Function
C# uses 'var' for implicit typing - VB uses Option Infer On combined with omitting the type.
The VB equivalent is:
Option Infer On
...
For Each row In rows
table.Rows.Add(row.ItemArray)
Next row
.NET already has .CopyToDataTable extension for that:
Dim table As DataTable = rows.CopyToDataTable
The VB equivalent is simply Dim, without any strong typing.
Dim sName = "John Henry"
In this example, the compiler infers type String (when Option Infer is set to On).
In your example, you may omit the As var portion. The compiler will infer type DataRow.
Tag your questions well, in this case there is no C# issue. Your problem is your are not writing an actual type on the foreach statement. This will fix it:
For Each row As DataRow In rows
table.Rows.Add(row.ItemArray)
Next

Linq to Datarow, Select multiple columns as distinct?

basically i'm trying to reproduce the following mssql query as LINQ
SELECT DISTINCT [TABLENAME], [COLUMNNAME] FROM [DATATABLE]
the closest i've got is
Dim query = (From row As DataRow In ds.Tables("DATATABLE").Rows _
Select row("COLUMNNAME") ,row("TABLENAME").Distinct
when i do the above i get the error
Range variable name can be inferred
only from a simple or qualified name
with no arguments.
i was sort of expecting it to return a collection that i could then iterate through and perform actions for each entry.
maybe a datarow collection?
As a complete LINQ newb, i'm not sure what i'm missing.
i've tried variations on
Select new with { row("COLUMNNAME") ,row("TABLENAME")}
and get:
Anonymous type member name can be
inferred only from a simple or
qualified name with no arguments.
to get around this i've tried
Dim query = From r In ds.Tables("DATATABLE").AsEnumerable _
Select New String(1) {r("TABLENAME"), r("COLUMNNAME")} Distinct
however it doesn't seem to be doing the distinct thing properly.
Also, does anyone know of any good books/resources to get fluent?
You start using LINQ on your datatable objects, you run the query against dt.AsEnumberable, which returns an IEnumerable collection of DataRow objects.
Dim query = From row As DataRow In ds.Tables("DATATABLE").AsEnumerable _
Select row("COLUMNNAME") ,row("TABLENAME")
You might want to say row("COLUMNNAME").ToString(), etc. Query will end up being an IEnumerable of an anonymous type with 2 string properties; is that what you're after? You might need to specify the names of the properties; I don't think the compiler will infer them.
Dim query = From row As DataRow In ds.Tables("DATATABLE").AsEnumerable _
Select .ColumnName = row("COLUMNNAME"), .TableName = row("TABLENAME")
This assumes that in your original sql query, for which you used ADO to get this dataset, you made sure your results were distinct.
Common cause of confusion:
One key is that Linq-to-SQL and (the Linq-to-object activity commonly called) LINQ-to-Dataset are two very different things. In both you'll see LINQ being used, so it often causes confusion.
LINQ-to-Dataset
is:
1 getting your datatable the same old way you always have, with data adapters and connections etc., ending up with the traditional datatable object. And then instead of iterating through the rows as you did before, you're:
2 running linq queries against dt.AsEnumerable, which is an IEnumerable of datarow objects.
Linq-to-dataset is choosing to (A) NOT use Linq-to-SQL but instead use traditional ADO.NET, but then (B) once you have your datatable, using LINQ(-to-object) to retrieve/arrange/filter the data in your datatables, rather than how we've been doing it for 6 years. I do this a lot. I love my regular ado sql (with the tools I've developed), but LINQ is great
LINQ-to-SQL
is a different beast, with vastly different things happening under the hood. In LINQ-To-SQL, you:
1 define a schema that matches your db, using the tools in in Visual Studio, which gives you simple entity objects matching your schema.
2 You write linq queries using the db Context, and get these entities returned as results.
Under the hood, at runtime .NET translates these LINQ queries to SQL and sends them to the DB, and then translates the data return to your entity objects that you defined in your schema.
Other resources:
Well, that's quite a truncated summary. To further understand these two very separate things, check out:
LINQ-to-SQL
LINQ-to-Dataset
A fantastic book on LINQ is LINQ in Action, my Fabrice Marguerie, Steve Eichert and Jim Wooley (Manning). Go get it! Just what you're after. Very good. LINQ is not a flash in the pan, and worth getting a book about. In .NET there's way to much to learn, but time spent mastering LINQ is time well spent.
I think i've figured it out.
Thanks for your help.
Maybe there's an easier way though?
What i've done is
Dim comp As StringArrayComparer = New StringArrayComparer
Dim query = (From r In ds.Tables("DATATABLE").AsEnumerable _
Select New String(1) {r("TABLENAME"), r("COLUMNNAME")}).Distinct(comp)
this returns a new string array (2 elements) running a custom comparer
Public Class StringArrayComparer
Implements IEqualityComparer(Of String())
Public Shadows Function Equals(ByVal x() As String, ByVal y() As String) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of String()).Equals
Dim retVal As Boolean = True
For i As Integer = 0 To x.Length - 1
If x(i) = y(i) And retVal Then
retVal = True
Else
retVal = False
End If
Next
Return retVal
End Function
Public Shadows Function GetHashCode(ByVal obj() As String) As Integer Implements System.Collections.Generic.IEqualityComparer(Of String()).GetHashCode
End Function
End Class
Check out the linq to sql samples:
http://msdn.microsoft.com/en-us/vbasic/bb688085.aspx
Pretty useful to learn SQL. And if you want to practice then use LinqPad
HTH
I had the same question and from various bits I'm learning about LINQ and IEnumerables, the following worked for me:
Dim query = (From row As DataRow In ds.Tables("DATATABLE").Rows _
Select row!COLUMNNAME, row!TABLENAME).Distinct
Strangely using the old VB bang (!) syntax got rid of the "Range variable name..." error BUT the key difference is using the .Distinct method on the query result (IEnumerable) object rather than trying to use the Distinct keyword within the query.
This LINQ query then returns an IEnumerable collection of anonymous type with properties matching the selected columns from the DataRow, so the following code is then accessible:
For Each result In query
Msgbox(result.TABLENAME & "." & result.COLUMNNAME)
Next
Hoping this helps somebody else stumbling across this question...

What does the "New ... With" syntax do in VB Linq?

What (if any) is the difference between the results of the following two versions of this VB Linq query?
' assume we have an XElement containing employee details defined somewhere else
Dim ee = From e In someXML.<Employee> _
Select New With {.Surname = e.<Surname>, .Forename = e.<Forename>}
and
Dim ee = From e In someXML.<Employee> _
Select Surname = .Surname = e.<Surname>, .Forename = e.<Forename>
ie what is the point of the New ... With syntax?
I suspect that this has a simple answer, but I can't find it - any links to suitable tutorials or Microsoft documentation would be appreciated.
The difference is that the 1st explicitly creates an anonymous type. The 2nd is a query expression, and may use an existing type rather than creating an anonymous type. From the documentation linked by Cameron MacFarland:
Query expressions do not always require the creation of anonymous types. When possible, they use an existing type to hold the column data. This occurs when the query returns either whole records from the data source, or only one field from each record.
My understanding is that there is no difference.
New With is aimed to out-of-query usage like
Dim X = New With { .Surname = "A", .Forename = "B" }
Specifically for Linq queries, you can skip New With, but it is still useful for other situations. I am not sure, however, since I do not know VB 9 :)
There is no functional difference between the two pieces of code you listed. Under the hood both pieces code will use an anonymous type to return the data from the query.
The first piece of code merely makes the use of an anonymous type explicit. The reason this syntax is allowed is that it's possible to return any type from a Select clause. But the type must be used explicitly.
Dim x = From it in SomeCollection Select New Student With { .Name = it.Name }
Joel is incorrect in his statement that the second query may use an existing type. Without an explicit type, a select clause which uses an explicit property name will always return an anonymous type.
They're called Anonymous Types.
The main reason for their use is to keep the data from a query in a single object, so the iterators can continue to iterate over a list of objects.
They tend to work as temporary types for storage in the middle of a large or multi-part LINQ query.
There is no difference. The compiler will infer the anonymous type.
You most likely want to return the Value of the elements as in e.<Surname>.Value, which returns a String instead of an XElement.
Your 2nd example could be simplified as
Dim ee = From e In someXML.<Employee> _
Select e.<Surname>.Value, e.<Forename>.Value
because the compiler will also infer the names of the members of the anonymous type.
However, if you have the following class
Class Employee
Private _surname As String
Public Property Surname() As String
Get
Return _surname
End Get
Set(ByVal value As String)
_surname = value
End Set
End Property
Private _forename As String
Public Property Forename() As String
Get
Return _forename
End Get
Set(ByVal value As String)
_forename = value
End Set
End Property
End Class
Then you could change the 1st query to produce an IQueryable(Of Employee) instead of the anonymous type by using New ... With like so:
Dim ee = From e In someXML.<Employee> _
Select New Employee With {.Surname = e.<Surname>.Value, _
.Forename = e.<Forename>.Value}
One difference is that Anonymous types aren't serializable.