I am new to Linq.
My actual code is this
Dim dt As DataTable = GetData()
Dim listOrder = From a In dt _
Group a By key = a("Name") Into g = Group _
Select Id = key, TotPoints = g.Sum(Function(r)r("Points"))
Order By TotPoints Descending
For Each item In listOrder
If item.Id = 1 Then
item.TotPoints = CalculateNewPoints()
End If
Next
The problem is that when trying to update a property receive the message: The property "xxxx" is ReadOnly
Thanks for any help!! and sorry for my bad English.
Yes, that should be. I believe that lista is a typo and it should be item. Here item iterator inside foreach is a readonly object reference and using which you can't perform any write operation like the way you are trying. Rather use a for loop.
For Each item In listOrder
If item.Id = 1 Then
item.TotPoints = CalculateNewPoints()
End If
You can as well use another LINQ query and then update the data like below but this would work only if Id is unique and there is only record with Id 1.
Itemtype item = listOrder.Single(x => x.Id == 1);
item.TotPoints = CalculateNewPoints();
You're creating instances of an anonymous type { Id, TotPoints }. Later on, you try to modify TotPoints. But anonymous types are immutable, or read-only, so you get this exception. The solution is to assign the right values at once:
Dim listOrder = From a In dt _
Group a By key = a("Name") Into g = Group _
Select _
Id = key, _
TotPoints = If (key = 1 _
, CalculateNewPoints() _
, g.Sum(Function(r)r("Points")))
Order By TotPoints Descending
Related
I have this manual adding of data or row in the grid and I would like to ask some idea on how to implement data binding in vb.net.
memberGrid.Rows.Clear()
For Each m As Member In members
Dim row As Object() = {m.MemberId, EntityHelper.FullName(m.Person.FirstName, m.Person.SurName),
WorkoutLogic.GetLastWorkoutDateDisplay(m.MemberId, AppRuntime.Workouts),
LogicService.GetMembershipStatus(m.MembershipHistories),
m.Person.Mobile}
memberGrid.Rows.Add(row)
Next
Try something like
memberGrid.DataSource = (From m In members
Select New With { Id = m.MemberId,
FullName = EntityHelper.FullName(m.Person.FirstName, m.Person.SurName),
LastWorkout = WorkoutLogic.GetLastWorkoutDateDisplay(m.MemberId, AppRuntime.Workouts),
Status = LogicService.GetMembershipStatus(m.MembershipHistories),
Mobile = m.Person.Mobile }).ToList()
Change the names before the ='s to match the names of your columns.
I am trying to grab each key value from a LINQ Query and pull them into my view. The LINQ query looks like this:
Public Property ByVDN As IEnumerable
Get
Dim valQ = (From scr In Var.db.CareSideA.ScriptCrossReferences
Join s In Var.db.CareSideA.Scripts On s.ScriptID Equals scr.ScriptID
Join ms In Var.db.CareSideA.MasterScripts On s.MasterScriptID Equals ms.MasterScriptID
Join svce In Var.db.CareSideA.Services On svce.SkillTargetID Equals scr.ForeignKey
Join p In Var.db.CareSideA.Peripherals On svce.PeripheralID Equals p.PeripheralID
Join sm In Var.db.CareSideA.ServiceMembers On svce.SkillTargetID Equals sm.ServiceSkillTargetID
Join sg In Var.db.CareSideA.SkillGroups On sm.SkillGroupSkillTargetID Equals sg.SkillTargetID
Where s.Version = ms.CurrentVersion And scr.TargetType = 1 And svce.PeripheralNumber = Value
Select New With {Key .Service = svce.PeripheralNumber,
Key .ScriptName = ms.EnterpriseName,
Key .Peripheral = p.EnterpriseName,
Key .SkillMapping = sg.PeripheralNumber,
Key .LatestVersion = s.Version,
Key .Created = s.DateTime,
Key .Author = s.Author}).ToList
Return valQ
End Get
Set(value As IEnumerable)
End Set
End Property
Now this does return results but they look like this:
Ideally I'd like to be able to do this:
<table>
For Each Item In Model.ByVDN
Dim i = Item
<tr>
<td>#Html.DisplayFor(Function(m) i.Service)</td>
<td>#Html.DisplayFor(Function(m) i.ScriptName)</td>
<td>#Html.DisplayFor(Function(m) i.Peripheral)</td>
Next
etc...
You can't pass about anonymous objects. Well, you can, but it is not strongly typed. You'll have to define a class with these properties, create IEnumerable of instances of that class and pass that Enumerable to the view. There is no other way.
UPD: see similar question: passing linq select query to the method
I have a method in a webservice that has parameter with which users can decide how they want to order their results. This is a List(Of String) with the names of the fields in the order they want to sort them.
I know I can normally order on multiple columns by doing the following
Dim test = Bars.OrderBy(Function(x) x.Foo) _
.ThenBy(Function(x) x.Bar) _
.ThenBy(Function(x) x.Test)
However in this case this won't work since I can't chain the ThenBy function because I'm adding the sorting orders in a loop. To use ThenBy I need an IOrderedQueryable collection. This is how I would want it to work
Dim sortColumns = {"Foo", "Bar", "Test"}
Dim query = From b in Bars
For each column in sortColumns
Select Case column
Case "Foo"
query = query.Orderby(Function(x) x.Foo)
Case "Bar"
query = query.Orderby(Function(x) x.Bar)
Case "Test"
query = query.Orderby(Function(x) x.Test)
End Select
Next
Dim result = query.Select(Function(x) x.x).ToList()
Return result
This of course won't work because OrderBy will replace any previous ordering. The only solution I can think of is ordering the list on some other variable first so I already have an IOrderedQueryable collection but this just seems like the wrong approach.
Dim bars As New List(Of Bar)
Dim sortColumns = {"Foo", "Bar", "Test"}
Dim query = bars.Select(Function(x) New With {.Temp = 1, .x = x}) _
.OrderBy(Function(x) x.Temp)
For Each column In sortColumns
Select Case column
Case "Foo"
query = query.ThenBy(Function(x) x.x.Foo)
Case "Bar"
query = query.ThenBy(Function(x) x.x.Bar)
Case "Test"
query = query.ThenBy(Function(x) x.x.Test)
End Select
Next
Dim result = query.Select(Function(x) x.x).ToList()
Return result
You could write your own extension method OrderByOrThenBy which checks whether the value is already an IOrderedQueryable, uses ThenBy if so and OrderBy otherwise. Slightly smelly, but not terribly hard to do.
EDIT: C# sample (untested):
public static class QueryableOrdering
{
public static IOrderedQueryable<TElement> OrderByOrThenBy<TElement, TKey>
(this IQueryable<TElement> source,
Expression<Func<TElement, TKey>> ordering)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (ordering == null)
{
throw new ArgumentNullException("ordering");
}
var ordered = source as IOrderedQueryable<TElement>;
return ordered == null ? source.OrderBy(ordering)
: ordered.ThenBy(ordering);
}
}
I have these objects:
Public Class MakeInfo
Public Property Name As String
Public Property Description As String
Public Property Stock As StockInfo
End Class
Public Class ModelInfo
Public Property Make As String
Public Property Name As String
Public Property Description As String
Public Property Stock As StockInfo
End Class
Public Class StockInfo
Public Property Count As Integer
Public Property MinPrice As Double
End Class
Using LINQ I need to take a List(Of MakeInfo) and a List(Of ModelInfo) and aggregate the StockInfo from ModelInfo into the List(Of MakeInfo).
So for each MakeInfo I will have a total count of all stock where MakeInfo.Name = ModelInfo.Make, and also a minimum price.
I think it's going to be something like this, but I'm having trouble accessing the nested object and not sure if it's going to be possible with a single query. I've tried a few variations, but here's where I'm up to:
newList = From ma In makeData _
Group Join mo In modelData _
On ma.Name Equals mo.Make _
Into Group _
Select New MakeInfo With {.Name = m.Name, _
.Description = m.Description, _
.Stock = ?}
If you are actually trying to modify the original MakeInfo objects, you should stop the query with the group. newList will have each make paired with the group of models of that make. You can iterate through newList, and for each make, aggregate the models into the StockInfo for the make.
Dim newList = From ma In makeData _
Group Join mo In modelData _
On ma.Name Equals mo.Make _
Into Group
For Each g In newList
g.ma.Stock = g.Group.Aggregate(
New StockInfo() With {.MinPrice = Double.NaN},
Function(si, model)
Return New StockInfo With {
.Count = si.Count + 1,
.MinPrice = If(Double.IsNaN(si.MinPrice) OrElse
model.Stock.MinPrice < si.MinPrice,
model.Stock.MinPrice,
si.MinPrice)}
End Function)
Next
If you actually want new instances, just change the loop to create a new item instead of modifying the existing make.
Dim results As New List(Of MakeInfo)()
For Each g In newList
Dim newMake As New MakeInfo()
newMake.Name = g.ma.Name
newMake.Description = g.ma.Description
newMake.Stock = g.Group.Aggregate( same as before )
results.Add(newMake)
Next
I eventually found the right way to do this and it doesn't require loop iteration:
Dim new_makes = From ma In makes
Join mo In models On mo.Make Equals ma.Name
Group By ma.Name, ma.Description
Into TotalCount = Sum(mo.Stock.Count), LowestPrice = Min(mo.Stock.MinPrice)
Select New MakeInfo With {.Name = Name, _
.Description = Description, _
.Stock = New StockInfo With {.Count = TotalCount, .MinPrice = LowestPrice}}
I was certain that it would be possible.
I'm trying to write a linq to object query in vb.net, here is the c# version of what I'm trying to achieve (I'm running this in linqpad):
void Main()
{
var items = GetArray(
new {a="a",b="a",c=1}
, new {a="a",b="a",c=2}
, new {a="a",b="b",c=1}
);
(
from i in items
group i by new {i.a, i.b} into g
let p = new{ k = g, v = g.Sum((i)=>i.c)}
where p.v > 1
select p
).Dump();
}
// because vb.net doesn't support anonymous type array initializer, it will ease the translation
T[] GetArray<T>(params T[] values){
return values;
}
I'm having hard time with either the group by syntax which is not the same (vb require 'identifier = expression' at some places, as well as with the summing functor with 'expression required' )
Thanks so much for your help!
You can create an array of type Object in VB.NET that can contain objects of any type, including anonymous types. The compiler will correctly infer that the same anonymous type is to be used provided you keep the same field name, types, and order of fields.
I ran this code in LinqPad to get the results you are looking
Dim items As Object() = { _
New With {.a="a",.b="a",.c=1}, _
New With {.a="a",.b="a",.c=2}, _
New With {.a="a",.b="b",.c=1} _
}
Dim myQuery = From i In items _
Group By i.a, i.b into g = Group _
let p = New With { .k = g, .v = g.Sum(Function(i) i.c)} _
where p.v > 1 _
select p
myQuery.Dump