Imagine this call which uses three fluent API's to make a very simple and readable function (the IQueryable Where and OrderBy, the AutoMapper Project and To, and ToDataSourceResult from Kendo).
Return Json(DataSource.TemplateItems.Where(Function(x) x.TemplateID= id) _
.OrderBy(Function(x) x.SortOrder) _
.Project() _
.To(Of TemplateIdRowViewModel) _
.ToDataSourceResult(req))
For debugging, I sometimes want to examine the current value being passed around in between calls to the fluent API. For example, I might want to know what my IQueryable looks like after Project and To finish with it but before ToDataSourceResult gets it.
I'm using the VS 2010 debugger. Right now I find myself editing the call to include temporary variable for each step so I can examine them, like this:
Dim where = DataSource.TemplateItems.Where(Function(x) x.TemplateID = id)
Dim ordered = where.OrderBy(Function(x) x.SortOrder)
Dim projected = ordered.Project().To(Of TemplateIdRowViewModel)()
Dim result = projected.ToDataSourceResult(req)
Dim jsonified = Json(result)
Return jsonified
Related
Public Function gridloop(MSFG1 As Object) As Long
For i= 0 To MSFG1.rows - 1
A = MSFG1.TextMatrix(i,1)
Next
End Function
The above code is 2 times slower than below
Public Function gridloop(MSFG1 As MSHFlexGrid) As Long
Public Function gridloop(MSFG1 As MSFlexGrid) As Long
Any solution to speed-up?
Not a lot of details in the question, I presume you have two (or more?) different controls where you're trying to essentially overload your gridloop function so it'll work with multiple types of controls?
The following might provide a performance improvement. I have not tested this, not even confirmed that it is free of compile errors. Idea is to determine the control type, then assign it to a variable of a matching type, then the references to the methods and properties might be early bound (thus faster).
Public Function gridloop(MSFG1 as Object) as Long
Dim myMSHFlexGrid As MSHFlexGrid
Dim myMSFlexGrid As MSFlexGrid
Dim i As Integer
Dim A As Long
If TypeOf MSFG1 Is MSHFlexGrid Then
Set myMSHFlexGrid = MSFG1
For i = 0 To myMSHFlexGrid.rows - 1
A = myMSHFlexGrid.TextMatrix(i,1)
Next
ElseIf TypeOf MSFG1 Is MSFlexGrid Then
Set myMSFlexGrid = MSFG1
For i = 0 To myMSFlexGrid.rows - 1
A = myMSFlexGrid.TextMatrix(i,1)
Next
End If
End Function
Alternative is to define two gridloop functions, one for each type. A form of manual overloading.
Public Function gridloop_MSHFlexGrid(MSFG1 As MSHFlexGrid) As Long
Public Function gridloop_MSFlexGrid(MSFG1 As MSFlexGrid) As Long
Advantage to this is that trying to call one of the gridloop functions with an 'incorrect' control will result in a compile error - catching a problem early that could otherwise require spending some significant time performing runtime debugging.
Building on MarkL's answer, you could use actual VB interface overloading to get what you want.
The idea would be to create an interface exposing whatever properties or functions you need on the grids, and then create two classes, each one implementing that interface and internally manipulating the actual grid.
This wrappering substitutes for the fact that the two grid types do not intrinsically share a common interface. (I looked in the IDL using OLEView).
You can then use the interface as the type in every location you currently are using Object to stand in for the actual grid class. If the interface is comprehensive AND its methods / properties are named appropriately then you would not need to make any other code changes.
Sample (pseudo)code...
Interface:
'In file for interface IGridWrapper
Function Rows
End Function
Function TextMatrix(i as Integer, j as Integer)
End Function
'Any others...
Wrapper class 1:
' In file for class "MSFlexGridWrapper"
Implements IGridWrapper
Private m_grid as MSFlexGrid
Sub Init(MSFlexGrid grid)
Set m_grid = grid
End Sub
Function IGridWrapper_Rows
IGridWrapper_RowCount = m_grid.Count
End Function
Function IGridWrapper_Textmatrix(i as Integer, j as Integer)
'etc.
End Function
'Any others...
Wrapper class 2:
' In file for class "MSHFlexGridWrapper"
Implements IGridWrapper
Private m_grid as MSHFlexGrid
Sub Init(MSHFlexGrid grid)
Set m_grid = grid
End Sub
Function IGridWrapper_Rows
IGridWrapper_RowCount = m_grid.Count
End Function
Function IGridWrapper_Textmatrix(i as Integer, j as Integer)
'etc.
End Function
'Any others...
Code using the wrappers:
Public Function gridloop(MSFG1 As IGridWrapper) As Long
(Note - none of this has been put through a compiler for exact syntax checking)
The basic reason that late binding (binds at runtime) is slower than early binding (binds at compile time) is that you have to use the iDispatch interface's GetIDsOfNames and Invoke methods to access properties in the object's interface, rather than accessing them directly from the vtable. For more information, have a look at this.
The reason that DaveInCaz's and MarkL's suggestions will probably speed things up is that they are ways to allow your gridloop function to accept a type that can be bound early rather than an Object type. DaveInCaz's solution is also a fine example of a practical application of polymorphism.
Please have a look at the following code, which I have run in VB6 and .NET:
Private Sub Form_Load()
Dim TestArray3() As String
TestArray3 = TestArrayFunction
End Sub
Private Function TestArrayFunction() As String()
Dim TestArray1(0 To 1) As String
Dim TestArray2() As String
TestArray1(0) = "Monday"
TestArray1(1) = "Tuesday"
TestArray2 = TestArray1
TestArray1(0) = "Wednesday"
End Function
When the program gets to the end of TestArrayFunction in VB6, the value of TestArray2(0) is "Monday", however when run in .NET, it is "Wednesday". I understand in .NET that an Array is an object and has two references pointing to it in TestArrayFunction. Why is this not the case in VB6?
This is to add to Dabblernl's response.
Long story made short, VB6 never really had a general reference type. Being built on COM, the only reference type it has (Dim ... As Object) is one for COM-based (or COM styled) classes. The Variant type was only good at boxing other types.
As to why ByRef works with arrays ...
The evolution of many dialects of BASIC, including VB6, adopted/adapted the paradigm of FORTRAN when it came to subroutines and functions; namely, parameters are passed by address (hence, ByRef is the default), and functions return their results by value. Add to this that BASIC never really had the concept of an address or a reference: VB6 (and earlier versions) would simulate addresses with 32-bit (signed) integers and otherwise would cope (in strange ways) via peculiar rules of the DECLARE SUB/FUNCTION statement and the (shoe-horned) AddressOf operator and VarPtr/StrPtr functions.
Final note: Since VB6 classes are COM-style, VB6 has the SET statement. The reason for this is that without the SET, an l-value = r-value situation is an implied LET statement. COM supports the notion of a default property (with and without parameters); LET objA = objB is interpreted as LET objA.DefaultPropOfA = objB.DefaultPropOfB. SET, on the other hand, makes objA take on the reference to the same object that objB references.
The Visual Basic of .NET is a nice and powerful language, with far fewer shortcomings and warts than VB6. However, it does have big differences from its legacy cousin. These differences have made long time VB6 users grouchy, and I suspect it has made many VB.NET users confused when they're asked to deal with VB6 code.
BTW, a VB6 coder would deal with the array within a class issue in this way:
Public Property Get DaysOfWeek(ByVal index As Integer) As String
DaysOfWeek = m_strDaysOfWeek(index)
End Property
Public Property Let DaysOfWeek(ByVal index As Integer, ByRef value As String)
m_strDaysOfWeek(index) = value
End Property
That would allow syntax of strDay = clsDateTime.DaysOfWeek(1) and clsDateTime.DaysOfWeek(2)="Tuesday" to work as desired.
I am struggling with this nearly daily. While it is perfectly possible to pass an array ByRef to a Function call, the '=' sign will make a shallow copy.
But there is more strange behaviour of arrays in VB6, Suppose you have the following DateTimeClass classmodule in VB6:
Option Explicit
Private m_strDaysOfWeek() As String
Public Property Get DaysOfWeek() As String()
DaysOfWeek = m_strDaysOfWeek()
End Property
Public Property Let DaysOfWeek(strDaysOfWeek() As String)
m_strDaysOfWeek() = strDaysOfWeek
End Property
Private Sub Class_Initialize()
ReDim m_strDaysOfWeek(7)
m_strDaysOfWeek(1) = "Monday"
End Sub
You would expect to be able to write code like:
Dim clsDateTime As New DateTimeClass
Dim strDay As String
strDay = clsDateTime.DaysOfWeek(1)
Or:
clsDateTime.DaysOfWeek(2)="Tuesday"
But you can't. You have to do it like this:
Dim clsDateTime As New DateTimeClass
Dim strDay As String
Dim strDays() As String
strDays = clsDateTime.DaysOfWeek
strDay = strDays(1)
strDays(2) = "Tuesday"
clsDateTime.DaysOfWeek = strDays
What the reasons of the VB6 team were at the time to implement it thus? I don't know...
I have abandoned the VB6 arrays and use Collections and Dictionaries nearly exclusively.
VB6 copies the array, and the last statement of your Function only changes the original copy.
Since you know .Net broke compatibility with VB I'm not sure where the confusion comes from.
I'm trying to take the final solution from Phil Haack here and sort using his killer LINQ query but instead of using data context like he is, I want to query against IEnumerable(Of T) ... not having any luck.
Public Function DynamicGridData(ByVal sidx As String, ByVal sord As String, ByVal page As Integer, ByVal rows As Integer) As ActionResult
Dim list As List(Of User) = UserService.GetUserCollection()
Dim FilteredAndSortedList = list.OrderBy(sidx + " " + sord).Skip(pageIndex * pageSize).Take(pageSize)
Return JSON(jsonData)
End Function
Currently I get the error below my .OrderBy(sidx + " " + sord) line
"Data type(s) of teh type parameter(s) in extension method 'Public Function OrderBy(Of TKey)(keySelector As System.Func(Of User, TKey)) As System.Linq.IOrderedEnumerable(Of User)' defined in 'System.Linq.Enumerable' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error."
EDIT:
I found the issue to be that my "list" in the example above was not of type IQueryable. Simply adding .AsQueryable() did the trick!
Public Function DynamicGridData(ByVal sidx As String, ByVal sord As String, ByVal page As Integer, ByVal rows As Integer) As ActionResult
Dim list As List(Of User) = UserService.GetUserCollection()
Dim FilteredAndSortedList = list.AsQueryable().OrderBy(sidx + " " + sord).Skip(pageIndex * pageSize).Take(pageSize)
Return JSON(jsonData)
End Function
I'm not positive but I think it has to do with the fact that your passing into OrderBy a string as opposed to an Expression(TDelegate) which is what it expects.
Take a look at this article and see if it helps.
http://msdn.microsoft.com/en-us/library/bb549264.aspx
Edit:
I've taken a look at Phil's Code and at some further documentation provided by Scott Gu. You should be able to pass in a string in which to order by. That being the case my next question would be if you take out the order by does your app work? The reason for this is I'm wondering if we have another underlying error that we are missing. Like for instance you are returning JSON(jsondata) but I dont see where you are initializing jsondata. Now I dont think that is your error cause you state you are getting it on the orderby statement. However if you are actually getting the error because the object you are trying to order is empty then you have another underlying issue.
this worked for me
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
been searching for a quick example of sorting a IQueryable (Using Linq To SQL) using a Aggregate value.
I basically need to calculate a few derived values (Percentage difference between two values etc) and sort the results by this.
i.e.
return rows.OrderBy(Function(s) CalcValue(s.Visitors, s.Clicks))
I want to call an external function to calculate the Aggregate. Should this implement IComparer? or IComparable?
thanks
[EDIT]
Have tried to use:
Public Class SortByCPC : Implements IComparer(Of Statistic)
Public Function Compare(ByVal x As Statistic, ByVal y As Statistic) As Integer Implements System.Collections.Generic.IComparer(Of Statistic).Compare
Dim xCPC = x.Earnings / x.Clicks
Dim yCPC = y.Earnings / y.Clicks
Return yCPC - xCPC
End Function
End Class
LINQ to SQL doesn't like me using IComparer
LINQ to SQL is never going to like you using your own methods within a query - it can't see inside them and work out what you want the SQL to look like. It can only see inside expression trees, built up from lambda expressions in the query.
What you want is something like:
Dim stats = From x in db.Statistics
Where (something, if you want filtering)
Order By x.Earnings / x.Clicks;
If you really want to fetch all of the results and then order them, you need to indicate to LINQ that you're "done" with the IQueryable side of things - call AsEnumerable() and then you can do any remaining processing on the client. It's better to get the server to do as much as possible though.
My VB is pretty bad, but I think this is what it should look like. This assumes that CalcValues returns a double and the type of rows is RowClass. This example does not use the IComparer version of the OrderBy extension but relies on the fact the doubles are comparable already and returns the CalcValue (assumed as double) as the key.
Dim keySelector As Func(Of Double, RowClass) = _
Func( s As RowClass) CalcValue( s.Visitors, s.Clicks )
return rows.OrderBy( keySelector )
Here are some links you might find useful.
IQueryable.OrderBy extension method
Lambda expressions for Visual Basic
My solution:
Dim stats = rows.OrderBy(Function(s) If(s.Visitors > 0, s.Clicks / s.Visitors, 0))
This also catches any divide by zero exceptions
I am trying to automatically clone an object without having to instantiate a new one and manually copy every single variable.
I remember back in the day (when I did VB6 everyday) I came up with a method of cloning objects using the PropertyBag, which was pretty cool. But I've lost the code and don't remember how to do it anymore.
Does anyone remember or have another method?
Is this what you were looking for? Article is copied below for posterity.
Serialize Data Using a PropertyBag
You can serialize your data quickly by placing it into a PropertyBag object, then reading the PropertyBags Contents property. This property is really a Byte array that is a serial representation of the data in your PropertyBag object. You can use this byte array for many purposes, including an efficient means of data transmission over DCOM:
Private Function PackData() As String
Dim pbTemp As PropertyBag
'Create a new PropertyBag object
Set pbTemp = New PropertyBag
With pbTemp
'Add your data to the PB giving each item a
'unique string key
Call .WriteProperty("FirstName", "John")
Call .WriteProperty("MiddleInitial", "J")
Call .WriteProperty("LastName", "Doe")
'Place the serialized data into a string
'variable.
Let PackData = .Contents
End With
Set pbTemp = Nothing
End Function
To retrieve the serialized data, simply create a new PropertyBag object and set the serialized string to its Contents property. Convert the string into a byte array before assigning it to the Contents property:
Private Sub UnPackData(sData As String)
Dim pbTemp As PropertyBag
Dim arData() As Byte
'Convert the string representation of the data to
'a Byte array
Let arData() = sData
'Create a new PropertyBag object
Set pbTemp = New PropertyBag
With pbTemp
'Load the PropertyBag with data
Let .Contents = arData()
'Retrieve your data using the unique key
Let m_sFirstName = .ReadProperty("FirstName")
Let m_sMiddleInitial = _
.ReadProperty("MiddleInitial")
Let m_sLastName = .ReadProperty("LastName")
End With
Set pbTemp = Nothing
End Sub
Mike Kurtz, McKees Rocks, Pa.
Method I have used in the past is putting all instance variables on a UDT. As long as you keep the UDT up to date you can copy a class' data with a single method/statement.
Given a "Person" class here's a simple example:
Private Type tPerson
ID As Long
FirstName As String
LastName As String
End Type
Private m_Person As tPerson
Public Sub InitPerson(ID As Long, FirstName As String, LastName As String)
m_Person.ID = ID
m_Person.FirstName = FirstName
m_Person.LastName = LastName
End Sub
Friend Sub SetData(PersonData As tPerson)
m_Person = PersonData
End Sub
Public Function GetClone() As Person
Dim p As New Person
p.SetData m_Person
Set GetClone = p
End Function
Public Property Get FirstName() As String
FirstName = m_Person.FirstName
End Property
To try the code:
Dim p As New Person
p.InitPerson 1, "MyName", "MyLastName"
Dim p2 As Person
Set p2 = p.GetClone
MsgBox p2.FirstName
If you maintain all instance varianbles inside the UDT instead of declaring them seperately you can have simple Clone method that needs very little maintenance.
Another advantage is you can put a UDT to a file handle for quick serialization to disk.
Public Sub Save(filePathName As String)
Dim f As Integer
f = FreeFile()
Open filePathName For Binary Access Write Lock Read Write As #f
Put #f, , m_Person
Close #f
End Sub
A poor men's serialization solution really :-)
Also read: Persisting a Component's Data.
Every object in my application's framework has a Read and Store method. What I do is pass them a stream that writes to a bytearray and store the byte array. While this doesn't eliminate having to handle every property you only have to deal with this once for reading and once for writing.
An alternative is to use a property bag like Darrel Miller says but you still have to deal with each property separately. If you already have read and store then my suggestion should save some time.