VB.NET 2010 and MySql - handling DB NULLs for lazy people - mysql

I want to initialize a class with data coming from a MySql db. Some fields can be null:
Dim dr As MySqlDataReader = ...
Dim item As New Item(dr.GetInt16(0), dr.GetString(1), dr.GetString(2))
Suppose the last two fields could be NULL In the db, so that calling GetString on that field causes an exception.
I could certainly write code to test for NULLs before I get each field:
dim field1 as String
if ( dr.IsDbNull(1) )
field1 = Nothing ' or even ""
else
field1 = dr.GetString(1)
But if you have many fields this is an "ifs" nightmare.
To this purpose I rewrote the IIf VB function to make it more typed, thus to avoid casts:
Namespace Util
Public Shared Function IIf(Of T)(ByVal condition As Boolean, ByVal iftrue As T, ByVal iffalse As T) As T
If condition Then Return iftrue Else Return iffalse
End Function
So that I could write something like:
Dim item As New Item(
dr.GetInt16(0),
Util.IIf(dr.IsDbNull(1), "", dr.GetString(1),
Util.IIf(dr.IsDbNull(2), "", dr.GetString(2))
The typed IIf works well in other cases, but unfortunately it doesn't in this instance, because being it a normal function and not a language keyword, each inpout parameter is evaluated during the call, and when the field is NULL the exception is raised.
Can you think of an elegant if-less solution?

First off, I'd recommend you to use an ORM mapper - there are very few cases nowadays when you have to do manual "mapping".
If this is one of these cases, I'd recommend you to use field names instead of indexes while accessing Data Reader.
And to answer your original question: try extension methods. Sorry for C#, but VB.NET syntax drives me nuts:
public static class DbDataReaderExtensions
{
public static T GetField<T>(this DbDataReader dbDataReader, string fieldName,
T defaultValue)
{
if(dbDataReader.IsDBNull(fieldName))
return defaultValue;
return (T)dbDataReader[fieldName];
}
}

Thanks.
I have looked at many ORMs and I don't like them for one reason or another, so I decided to call plain stored procedures to get data. Can you advice something powerful yet simple?
You're right about to use field names, it is safer, even if a little bit slower.
I had just arrived to the same conclusion with the method, but what I still don't like is the type conversion:
Public Shared Function IfNull(Of T)(ByVal dr As MySqlDataReader, ByVal index As Integer, ByVal _default As T) As T
If dr.IsDBNull(index) Then
Return _default
Else
Return CType(dr.GetValue(index), T)
End If
End Function
I would like to do something more elegant to get the "real" data type from the reader.

Related

SSIS Convert Blank or other values to Zeros

After applying the unpivot procedure, I have an Amount column that has blanks and other characters ( like "-"). I would like to convert those non-numberic values to zero. I use replace procedure but it only converts one at the time.
Also, I tried to use the following script
/**
Public Overrides Sub Input()_ProcessInputRows(ByVal Row As Input()Buffer)
If Row.ColumnName_IsNull = False Or Row.ColumnName = "" Then
Dim pattern As String = String.Empty
Dim r As Regex = Nothing
pattern = "[^0-9]"
r = New Regex(pattern, RegexOptions.Compiled)
Row.ColumnName = Regex.Replace(Row.ColumnName, pattern, "")
End If
End Sub
**/
but i'm getting error.I don't much about script so maybe I placed in the wrong place. The bottom line is that I need to convert those non-numberic values.
Thank you in advance for your help.
I generally look at regular expressions as a great way to introduce another problem into an existing one.
What I did to simulate your problem was to write a select statement that added 5 rows. 2 with valid numbers, the rest were an empty string, string with spaces and one with a hyphen.
I then wired it up to a Script Component and set the column as read/write
The script I used is as follows. I verified there was a value there and if so, I attempted to convert the value to an integer. If that failed, then I assigned it zero. VB is not my strong suit so if this could have been done more elegantly, please edit my script.
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer)
' Ensure we have data to work with
If Not Row.ColumnName_IsNull Then
' Test whether it's a number or not
' TryCast doesn't work with value types so I'm going the lazy route
Try
' Cast to an integer and then back to string because
' my vb is weak
Row.ColumnName = CStr(CType(Row.ColumnName, Integer))
Catch ex As Exception
Row.ColumnName = 0
End Try
End If
End Sub

SQL Reporting 2008; Check if an Array Contains a String

In SQL Reporting 2008 how can I determine if an Array Contains a String?
Example, I wish the following to return "1":
IIf(Split("a,b,c", ",").CONTAINS("a"), "1", "0")
What may be used in replace of the above CONTAINS function? Is it impossible? This value'd be the FilterExpression for my table. Its purpose is to decide what to show and what to hide.
If you are looking for an answer only in an expression, I am not positive. However, you can write .Net methods and call them just like expressions from a custom dll or a "code" section of the report. If you use built-in code, you can do something like the following:
http://www.vbforums.com/showthread.php?t=558440
Creating inline code or referencing an assembly in SSRS:
http://bryantlikes.com/pages/824.aspx
UPDATE:
Example to get your delimited values from your concatenated string:
http://www.dotnetperls.com/split-vbnet
UPDATE:
Here is a function you can use. You put it in the code section of the report:
Public Function Contains(ByVal ItemToCheck As String, ByVal CommaValuesList As String, ByVal delimeter As Char) As Boolean
Dim commaValues() As String = Split(CommaValuesList, delimeter, -1, CompareMethod.Text)
For Each commavalue As String In commaValues
If ItemToCheck.ToLower.Trim = commavalue.ToLower.Trim Then
Return True
End If
Next
Return False
End Function
Use the following syntax to reference it:
=code.Contains(param1,param2,param3)
Let's use MyLettersParameter as a multiselect parameter. To determine if it contains "a" use:
=Array.IndexOf(Parameters!MyLettersParameter.Value, "a") > -1
The above code returns true or false. To return "1" use:
=IIf(Array.IndexOf(Parameters!MyLettersParameter.Value, "a") > -1, "1", "0")

VBA function call

Is there a way to call a function, where the call is stored in a table
**Record 1 task Function call**
124567 Email customer Call function emailcus(a,b,c,d)
434535 AddCost Call function addcost(a,b,c,d)
Cheers
Graham
Yes, you can use the Eval() function for that.
Syntax:
Dim ReturnValue As String
ReturnValue = Eval("MyFunction(1, 2)")
Note that you have to provide the exact function call including parameters.
I'm pointing this out because I'm not sure if the parameters a, b, c, d in your example are only dummy values for your example, or if you expect VBA to fill in the values of some variables named a, b, c, d automatically.
The Eval function does not do this, so if you need variable values as parameters, you would have to do something like this:
Dim ReturnValue As String
Dim EvalString As String
EvalString = "MyFunction(" & Variable1 & ", " & Variable2 & ")"
ReturnValue = Eval(EvalString )
This is a variation on the answer already given by haarrrgh, so if you find it useful be sure to upvote that one as well.
There's another way to deal with placeholders in your DB-stored function calls. First, change your data thusly:
**Record 1 task Function call**
124567 Email customer Call function emailcus([TokenA],[TokenB])
434535 AddCost Call function addcost([TokenA],[TokenB])
Note that the [SquareBrackets] are not actually required syntax in this example, just something that I tend to use in this situation. The important part is to make the parameter tokens something that doesn't appear elsewhere in the string value (including other tokens). You can use as many parameters as you need, just make sure that the calling code knows about how many are expected by each function-call string (I cut it down to shorten my following code).
Then when it's time to call your function, do this:
Dim ReturnValue As String 'or as appropriate for individual the function's return
Dim EvalString As String
EvalString = 'code to fetch from table
EvalString = Replace(EvalString, "[TokenA]", strValueA) 'strValueA passed in?
EvalString = Replace(EvalString, "[TokenB]", strValueB) 'strValueB passed in?
ReturnValue = Eval(EvalString)
In VB6, at least (so I assume it's true in VBA), Replace is faster than concatenation. I also find this more readable, but that may be because I'm used to it from using a similar technique to build SQL commands in code (using Const declarations rather than DB storage, but that would work too).
EDIT
As I reread my "finished" post just after submitting it, I realized that there's a gotcha lurking in there. Because you're doing substitution before submitting the string to Eval, these are actual values that are being put into the string, not variables. The code I presented above works fine if your parameters are numeric, but if they're String type you have to include the quotes, either in your data or in your Replace call. I prefer the former, so change your data to this:
**Record 1 task Function call**
124567 Email customer Call function emailcus('[TokenA]','[TokenB]')
434535 AddCost Call function addcost('[TokenA]','[TokenB]')
This works as tested with a Const. Stored in a DB record, you might need this instead:
**Record 1 task Function call**
124567 Email customer Call function emailcus(""[TokenA]"",""[TokenB]"")
434535 AddCost Call function addcost(""[TokenA]"",""[TokenB]"")
(which also works with a Const...).
The alternative is to leave the data as it is in my first part, & change the Replace calls:
EvalString = Replace(EvalString, "[TokenA]", """" & strValueA & """") 'strValueA passed in?
'or maybe
EvalString = Replace(EvalString, "[TokenB]", "'" & strValueB & "'") 'strValueA passed in?
A couple of other potential gotchas: These must be Functions, not Subs, and they must be declared Public in a module, not in a Form's code.

How to do a simple entity copy in Linq-to-SQL?

When using a Linq-to-SQL class, how can I make a simple copy of an entity and save it?
My entity has a guid for a unique ID that gets automatically generated in the SQL Server.
I don't require a "deep clone".
I tried to use some clone methods that are out there but I couldn't figure out how to get everything serialized that needed to be serialized (got stuck on the DataContext not being serializable).
Can I just get an entity, detach it from the DataContext, null out the unique ID and InsertOnSubmit in a new DataContext? If so, how would I do this?
VB.net code preferred but not required.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UPDATE:
Public Shared Function ReIssue(RequestID As Guid) As Guid
Dim req As Request
Dim new_req As Request
Using dc1 As New MBDataContext()
req = (From r In dc1.Requests Where r.ID = RequestID).Single()
End Using
new_req = req
new_req.ID = Guid.Empty
new_req.CreateDate = Nothing
Using dc2 As New MBDataContext()
dc2.Requests.InsertOnSubmit(new_req)
dc2.SubmitChanges()
End Using
End Function
I get an error: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.
on this line: dc2.Requests.InsertOnSubmit(new_req)
Nulling out the unique id and then calling InsertOnSubmit is the right way to go. Some things you have to consider though:
What is the type of the id? Is it an int? A Guid? Is it nullable? If it is nullable, make sure to set it to null, if it is an int, then to 0, or a Guid, then to Guid.Empty.
Does the type have a timestamp of some kind? If so, then you have to reset/set it to null as well, depending on the type.
Once you've done that, you can call InsertOnSubmit and then SubmitChanges and the change should take place.
Note, if you are doing this for a large number of records, you are better off writing a stored procedure which will perform the insert into the table using a select from the other table. It will be much faster that way (you won't be loading the data from the database into memory then pushing it back, inserting the records one at a time).
This method seems to have worked perfectly.
Making the final code look like this:
Public Shared Function ReIssue(RequestID As Guid) As Guid
Using dc As New MBDataContext()
Dim req As Request
req = (From r In dc.Requests Where r.ID = RequestID).Single()
Dim new_req As Request = DirectCast(Entity.Copy(req, New Request()), Request)
dc.Requests.InsertOnSubmit(new_req)
dc.SubmitChanges()
req.ActiveRequestParentID = new_req.ID
dc.SubmitChanges()
Return new_req.ID
End Using
End Function
Public NotInheritable Class Entity
Private Sub New()
End Sub
Public Shared Function Copy(source As Object, destination As Object) As Object
Dim sourceProps As System.Reflection.PropertyInfo() = source.[GetType]().GetProperties()
Dim destinationProps As System.Reflection.PropertyInfo() = destination.[GetType]().GetProperties()
For Each sourceProp As System.Reflection.PropertyInfo In sourceProps
Dim column As ColumnAttribute = TryCast(Attribute.GetCustomAttribute(sourceProp, GetType(ColumnAttribute)), ColumnAttribute)
If column IsNot Nothing AndAlso Not column.IsPrimaryKey Then
For Each destinationProp As System.Reflection.PropertyInfo In destinationProps
If sourceProp.Name = destinationProp.Name AndAlso destinationProp.CanWrite Then
destinationProp.SetValue(destination, sourceProp.GetValue(source, Nothing), Nothing)
Exit For
End If
Next
End If
Next
Return destination
End Function
End Class

Tuple style object in VBA

I'm using VBA in an Access application and I would like to have a n-tuple object that contains values of different data types. Then I would like a collection of those objects.
If I was doing this in javascript it would look like:
var myStructure = {
name: "blah"
age: 33
moreStuff : "test"
};
And then I would need a collection of myStructure. How can I best accomplish this in VBA?
You can define your own variable type with code such as:
Public Type T_Person
name as string
dateOfBirth as date
....
email() as string (*)
....
End type
You can then declare a T_person type in your code with:
Dim currentPerson as T_Person
currentPerson.name = myName
currentPerson.dateOfBirth = myDate
currentPerson.email(1) = myFirstEmail
....
(*) I do not remember the details for declaring arrays in such circumstances. You might have to determine array's length when defining the variable. Please check help.
The same result can also be reached by declaring a class module named, for example, "Person". In this class module, you'll be not only able to follow the objet properties (such as name, dateOfBirth, etc), but also object events (initialisation and deletion). You'll be also able to create methods on this object. You code would then look like:
Dim myPerson as Person
set myPerson = New Person
myPerson.name = myName
myPerson.dateOfBirth = myDate
if myPerson.age > 18 then (*)
'the guy is an adult'
myPerson.createAccount
Else
'the guy is not ...'
Endif
(*) Here, age is a calculated proerty of your object, available when dateOfBirth is not null. Please google "VBA class module" to find different examples for the implementation of a class module in VBA.
Now, if you want to manage a collection of similar "objects" (here, Persons), you will have to go through the creation of a class module for your objects collection (a "Persons" class module for example) and make use of the "Collection" objet available in VBA. You will then end with 2 different class modules: Person (will hold each person's detail), and Persons (will hold the collection of Persons). You'll then be able to have code like this:
Public myPersons as Persons 'at the app level, 1 main collection'
myPersons.add .... 'adding a new person to your collection'
myPersons.count ... 'counting number of persons in myPersons'
Please google on "VBA collection object" for examples on Collection management in VBA. Check my code proposal, as this was written on the fly, and without VBA help file.
The "Class" solution is clearly more powerfull, but more complex than the "Type". Go for it if you need it. It is definitely worth the pain!
PS: I am not very happy with my namings here, as this can lead to very confusing code when dealing with the myPersons collection and myPerson instance of o Person object. I'd advise you to find a more obvious one, like "PersonCollection" and "Person", or even "Individual"
You can use Variant multidimensional arrays to store your collections. Variants can store any data type making it very versatile.
Const Name as Integer = 0
Const Age as Integer = 1
Const moreStuff as Integer = 2
Dim myStructure as Variant
Redim myStructure(0 to 2, 0 to n)
myStructure(Name, 0) = "Blah"
myStructure(Age, 0) = 33
myStructure(moreStuff, 0) = "test"
Note: You can only expand the last dimension of a multidimensional array in VBA and preserve the values, so make sure that it is for the dimension you want to scale.
That is the basic data structure and you can develop a class or functions to wrap everything up to suit your needs.
You may also want to look into the Scripting.Dictionary object, though I have read that it is considered unsafe. It is more dynamic than using Type definitions, and unlike Collection, it gives you access to the keys.
Here's a sample:
Public Function TestDictionary()
Dim d As New Scripting.Dictionary 'need reference to Microsoft Scripting Runtime
'Dim d As Object
'Set d = CreateObject("Scripting.Dictionary")
Dim k As Variant
d.Add "Banana", "Yellow"
d.Add "Apple", "Red"
d.Add "Grape", "Green"
For Each k In d.Keys
Debug.Print k; ": "; d.Item(k)
Next k
End Function
The Collection class is an option.
Dim col As New Collection
col.Add("blah", "name")
col.Add(33, "age")
col.Add("test", "moreStuff")
This gives you the most flexibility. However it isn't very efficient and the Collection class has no way to get a list of keys.