Select several levels of childs - vb.net

I'm using entity Framework 6.
On my database I have these tables :
MasterTable ( Id , name)
Child1 ( ID , name , vl1 , Master_ID)
Child2 ( ID , name , vl2 , MasterID )
Child3 (ID , name , vl3 , Master_ID )
Child3Itm ( ID , name , Child3_ID)
For a given MasterTable item, I want to load with a single Query from database:
All Child1 where vl1 > 5
All Child2 where vl2 > 6
All Child3 where vl3 > 7
And in each Child3 to load all of the Child3Itm content.
I'm using this query:
Dim lst = (From t In context.MasterTable.Where(Function(t1) t1.id = 7)
Select New With {
t,
.chld1 = t.child1s.Where(Function(t21) t21.vl1 >5),
.chld2 = t.child2s.Where(Function(t31) t31.vl2>6 ),
.chld3 = t.child3s.Where(Function(t41) t41.vl3>7).Select(Function(t411) t411.Child3Itms)
}).ToList
The problem is that no Child3 are selected. All others are OK. What can i do? Thanks in advance!

Without seeing your data, it will be tough to diagnose. But since you have access to a debugger, you can do it yourself. With the code below, step over each line and inspect the variables. It should be easy to see where your code is failing
Dim masters = From t In context.MasterTable Where t = 7
Dim child1s = From m In masters Where m.child1s.vl1 > 5
Dim child2s = From m In masters Where m.child2s.vl2 > 6
Dim child3s = From m In masters Where m.child3s.vl3 > 7
Dim child3i = child3s.Child3Itms
Might be a typo in your question. Make sure the condition for the child3s is correct:
t41.vl>7
Should it be this?
t41.vl3 > 7

Dim lst = (From t In context.MasterTable.Where(Function(t1) t1.id = 7)
Select New With {
t,
.chld1 = t.child1s.Where(Function(t21) t21.vl1 >5),
.chld2 = t.child2s.Where(Function(t31) t31.vl2>6 ),
.chld3 = t.child3s.Where(Function(t41) t41.vl3>7),
.chld3itms = t.child3s.Where(Function(t51) t51.vl3>7).Select(Function(t511) t511.Child3Itms)
}).ToList

Dim lst = (From t In context.MasterTable.Where(Function(t1) t1.id = 7)
Select New With {
t,
.chld1 = t.child1s.Where(Function(t21) t21.vl1 >5),
.chld2 = t.child2s.Where(Function(t31) t31.vl2>6 ),
.chld3 = (From t2 in t.child3s.Where(Function(t41) t41.vl3>7)
Select New With {
t2,
.chld3it=t2.Child3Itms
})
}).ToList
This is working too.
If someone can tell me if this answer is better or no compared with the other solution posted by antoni ???

Related

How to query a linked list with T-Sql to find the Root Node

I am trying to find out from my SQL Server 2012 table what the root WorldID is from data that has a parent child relationship (yes, this is for a role playing game system).
The data sample is this:
The query I used to show the the data is:
SELECT
ID
,Name
,WorldID_Orig
,WorldID_From
,(Select Name from REF_World WHERE ID = WorldID_From) as 'WorldFrom_Name'
,WorldID_To
,(Select Name from REF_World WHERE ID = WorldID_To) as 'WorldTo_Name'
,ObjectType
,ObjID_From
,ObjID_To
,(Select Name from REF_Armor WHERE ID = ObjID_To) as 'Obj_Name'
,DateImported
FROM
REF_WorldObjectTrading
WHERE
ID > 3625
AND ObjectType = 'REF_Armor'
AND (Select Name from REF_Armor WHERE ID = ObjID_To) = 'Body: Dimension Shift'
ORDER BY
WorldID_From, ObjID_From
This data is representative of the data I have in my table. What I would like to do is for each row populate the WorldID_Orig with whatever WorldID_From is the first one in the chain. In this case that would be WorldID = 1.
What I'm struggling with is how to find that the root WorldID is 1 from the data if I have the data from, say, the 4th row of results here. I believe it will involve something along these lines ... but I'm not quite getting how to find the root ID.
declare #ParentID int
set #ParentID= 41385 --(select ID
from REF_Armor
where Name = 'Body: Dimension Shift')
;WITH T (Name, WorldFrom, WorldTo, WorldToName, ObjFrom, ObjTo) AS
(
SELECT
Name, WorldID_From, WorldID_To,
(SELECT Name FROM REF_World WHERE ID = WorldID_To) as WorldToName,
ObjID_From, ObjID_To
FROM
REF_WorldObjectTrading
WHERE
ObjID_From = #ParentID
AND ObjectType = 'REF_Armor'
AND ID > 3625
UNION ALL
SELECT
T1.name, T1.WorldID_From, T1.WorldID_To,
(SELECT Name FROM REF_World WHERE ID = WorldID_To) as WorldToName,
t1.ObjID_From, t1.ObjID_To
FROM
REF_WorldObjectTrading T1
INNER JOIN
T ON T1.objID_From = T.ObjFrom
WHERE
T.ObjFrom <> #ParentID
)
select *
from T
order by objTo, WorldFrom
If anyone can help me to nail this down, I'd be very appreciative. I feel I'm close, but I'm just not getting it. Thanks!
The solution to this issue was two fold. First I needed to make sure that when new Things (such as a new Armor in the REF_Armor table) are added that a new row is added to the Tracing table that has the ParentID NULL. That is done as follows:
Dim WorldID_Orig As Integer = MySession.World.ID
Dim Name As String = "Create " & Session("NewItemName")
Dim ObjID_From As Integer = Nothing
Dim ObjectID As Integer = NewItemID
Dim ObjectType As String = "REF_Armor"
Dim WorldID_From As Integer = Nothing
Dim WorldID_To As Integer = Nothing
MySession.EC.WOT_Insert(WorldID_Orig, Name, ObjID_From, ObjectID, ObjectType, WorldID_From, WorldID_To)
This occurs in the following event:
Protected Sub dsDataView_Inserted(sender As Object, e As SqlDataSourceStatusEventArgs) Handles dsDataView.Inserted
The WOT_Insert procedure does the following:
Public Sub WOT_Insert(WorldID_Orig As Integer,
Name As String,
ObjID_From As Integer,
ObjectID As Integer,
ObjectType As String,
WorldID_From As Integer,
WorldID_To As Integer)
Dim wot As New Elthos_ODS.WorldObjectTrading
wot.WorldID_Orig = WorldID_Orig ' -- HERE WE NEED TO QUERY THE DATABASE TO FIND THE ORIGINAL WORLD ID
wot.DateImported = Now
wot.DateModified = Nothing
wot.DateRemoved = Nothing
wot.Name = Name
wot.ObjID_From = ObjID_From
wot.ObjectID = ObjectID
wot.ObjectType = ObjectType
wot.WorldID_From = WorldID_From
wot.WorldID_To = WorldID_To
wot.InsertClassToDB()
End Sub
Lastly the SQL that queries the database is as follows:
ALTER PROCEDURE [dbo].[S_SYS_Admin_WOT_Trace] (
#ObjectID as int,
#ObjectType as varchar(255)
)
AS
;WITH Hierarchy (
WorldID_Orig,
Name,
WorldID_From,
WorldID_To,
ObjID_From,
ObjectID,
Parents)
AS
(
SELECT
WorldID_Orig,
Name,
WorldID_From,
WorldID_To,
ObjID_From,
ObjectID,
CAST('' AS VARCHAR(MAX))
FROM REF_WorldObjectTrading AS FirtGeneration
WHERE
WorldID_From = 0
and WorldID_To = 0
and ObjectType = #ObjectType
and ID > 3625
UNION ALL
SELECT
nextgeneration.WorldID_Orig,
NextGeneration.Name,
NextGeneration.WorldID_From,
NextGeneration.WorldID_To,
nextgeneration.ObjID_From,
NextGeneration.objectID,
CAST(CASE WHEN Parent.Parents = ''
THEN(CAST(NextGeneration.ObjID_From AS VARCHAR(MAX)))
ELSE(Parent.Parents + '.' + CAST(NextGeneration.ObjID_From AS VARCHAR(MAX)))
END AS VARCHAR(MAX))
FROM REF_WorldObjectTrading AS NextGeneration
INNER JOIN Hierarchy AS Parent
ON NextGeneration.objID_From = Parent.ObjectID
WHERE Parent.ObjectID <> #ObjectID
)
SELECT *
FROM Hierarchy
OPTION(MAXRECURSION 32767)
I hope this helps others who may find themselves in a similar situation.

Linq GROUP BY or manually grouping in a loop?

Yesterday I asked this question on stackoverflow. Today I realize that if I do a GROUP BY I also need to create a new type of object.
Let's say I have some data that looks like this:
var1 var2 var3 qty
1 a 1a 50
1 a 1a 25
2 b 2b 10
2 b 2b 15
2 b 2b 10
3 a 3a 25
Here is my working LinQ query
From j In MyTable
Where j.var1 = "xxx"
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .qty = Group.Sum(Function(x) x.qty)}
Actually I use Entity Framework and the code look more like this
Dim foo = (From j In dbContext.MyTable
Where j.var1 = anotherVariable
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .quantity = Group.Sum(Function(x) x.Qty)}).ToArray()
foo is a new type that doesn't exist in my generated Entities. But I have an entity generated by my entity framework that can contains these. It's MyTable itself. I use a GROUP BY only to sum a column of MyTable. I query a MyTable entities and I can put the result in a MyTable entity too.
My question are
1) Can I write something like this
Dim foo = (From j In dbContext.MyTable
Where j.var1 = anotherVariable
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New MyTable With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .qty = Group.Sum(Function(x) x.qty)}).ToArray()
In this case do I need to explicitely write all the mappings ?
2) Should I change my mind. Do a simpler query without GROUP BY and try to group and sum in a VB.NET loop (For Each). Or two queries ? On to get all MyTable with a WHERE clause and another to group ?
Dim foo = dbContext.MyTable.Where(Function(p As MyTable) p.var1 = anotherVariable).ToArray()
For Each bar In foo
'Code to group and sum or another query
Next
You won't be able to instantiate MyTable in an LINQ to Entities query but you can simply enumerate the results of the projection with ToArray and then construct the entities with another Select call.

Put multiple row values in object in VB.NET

I have a stored procedure and often that procedure will return multiple rows, for example:
ID Type Price
1234 A 2.260
1234 B 2.690
1234 C 2.990
1234 D 2.690
1234 D 2.790
1234 D 2.650
1234 D 2.680
And I want to output the latest value for each type. In my data reader I have:
While dr.Read
result.price.TypeA= dr("price")
result.price.TypeB= dr("price")
result.price.TypeC= dr("price")
result.price.TypeD= dr("price")
End While
and my query looks like:
select sm_id,
type,
price
from ** WITH (NOLOCK)
where id = #id
order by id desc
I'm not sure how to store all of my results into my object so I can access them in my front end.
This is may be not most performant query but it will do the job as what you described it
select t1.id, t1.t, t1.maxDt, t2.price
from
(select id, [TYPE] t, MAX(ud) maxDt
from dbo.a it
where id =1
group by id, [type]) t1
left join
dbo.a t2 on t1.id = t2.id and t1.t = t2.[type] and t1.maxDt = t2.ud
Now, storing results in object and displaying it
Public Class MyClass
Public Property Id As Integer
Public Property [Type] As String
Public Property [Date] As DateTime
Public Property Price As Decimal
End Class
. . . . .
Dim myList As New List(Of MyClass)()
While dr.Read()
Dim item As New MyClass()
item.Id = dr("id")
item.[Type] = dr("t")
item.[Date] = dr("maxDt")
item.Price = dr("price")
myList.Add(item)
End While
myDataGrid.DataSource = myList
This is what you, approximately, need

linq to sql The Grid view

I make sales and procurement system and when I work a query, or search the invoice number, the results appear in palm leaves View, but appears only in a row while the bill by no more than one row
And this was used by code
i.m used vb.net and database sql and b yway linq to sql
Try
Dim data = (From d In DBVariable.Data.masterfatoras
From f In DBVariable.Data.fatoras
From na In DBVariable.Data.asnafs
From sup In DBVariable.Data.suppliers
Where d.ID = f.mid Where d.num.Contains(txt)
Select d, f, na, sup).FirstOrDefault
TextBoxX1.Text = data.d.num
nammord.Text = (From supp In DBVariable.Data.suppliers Where data.d.idmord = supp.ID Select supp.Name).Single()
txtmord.Text = (From supp In DBVariable.Data.suppliers Where data.d.idmord = supp.ID Select supp.Code).Single()
adrmord.Text = (From supp In DBVariable.Data.suppliers Where data.d.idmord = supp.ID Select supp.Address).Single()
nodaf.Text = data.d.nodfa
' ''نفاصيل الفاتورة
For p As Integer = 0 To gridshraa.Rows.Count - 1
gridshraa.Rows(p).Cells(2).Value = (From asna In DBVariable.Data.asnafs Where data.f.idname = asna.ID Select asna.Name).Single()
gridshraa.Rows(p).Cells(1).Value = (From asna In DBVariable.Data.asnafs Where data.f.idname = asna.ID Select asna.code).Single()
gridshraa.Rows(p).Cells(3).Value = (From asna In DBVariable.Data.asnafs Where data.f.idname = asna.ID Select asna.unit).Single()
gridshraa.Rows(p).Cells(4).Value = data.f.qty
gridshraa.Rows(p).Cells(5).Value = data.f.price
gridshraa.Rows(p).Cells(6).Value = data.f.totprice
Next
Catch
End Try

Entity framework : Using summary functions inside a Projection

I'm trying to run the following query :
Dim lst = (From t In context.MyObj1 where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.AsQueryable.Where(Function(t2) t2.tp=2).Sum(Function(t3) t3.quantity), _
.vlh = t.child1.AsQueryable.Where(Function(t3) t3.tp=2).Sum(Function(t3) t3.value) _
}).ToList
( in this query .quantity and .value have Decimal type.)
but I'm getting this error on runtime :
An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Additional information: The cast to value type 'System.Decimal' failed because the materialized value is null.
Either the result type's genericparameter or the query must use a nullable type.
It's sure that the collection child1 has items that have .tp=2.
What's wrong ?
Thank you !
Updated :
these are the tables on database :
MyObj1:
Id name
2 name1
7 name7
8 name8
Child1:
ID ParentID TP Quantity Value
1 2 2 7 9
2 7 2 20 10
3 7 2 8 11
( ParentID is the forign key for child1 related to ID field on MyObj )
Also , I try the query like this :
Dim lst = (From t In context.MyObj1 where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.AsQueryable.Where(Function(t2) t2.tp=2).Count(Function(t3) t3.quantity), _
.vlh = t.child1.AsQueryable.Where(Function(t3) t3.tp=2).Count(Function(t3) t3.value) _
}).ToList
and has no problem. so I think maybe the problem is the SUM function.
Update :
This is working without errors :
Dim lst = (From t In context.MyObj1 where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.AsQueryable.Where(Function(t2) t2.tp=2).Sum(Function(t3) Ctype(t3.quantity,System.Nullable(of Decimal)), _
.vlh = t.child1.AsQueryable.Where(Function(t3) t3.tp=2).Sum(Function(t3) Ctype(t3.value,System.Nullable(of Decimal)) _
}).ToList
But I have problems because this method doesn't return any value on the Sums for those parent's items that doesn't have any child in Child1 collection , for example For the Item on Myobj1 with id=8 there's no child1's item , but in this case I want to return a 0 as a sum.
What can I do ?
Thank you !
Try this:
Dim lst = (From t In context.MyObj1
Where t.id > 6
Where Not (t.child1 Is Nothing)
Select New With {}).ToList
Hard to tell with just the code you've posted, but it appears something before you get into the LINQ statements is already null (i.e., Nothing).
EDIT
Sorry, just couldn't hack it in VB anymore ... switching to C# - hoping this is what you're looking for (because it's EF, I don't have an actual DB, and don't have time to set up an in-memory data store, it's not tested with your actual data):
(from t in context.MyObj1s
where t.Id > 6
from c in context.Child1s
where c.ParentId == t.Id
where c.Tp == 2
group new { Quantity = c.Quantity, Value = c.Value } by t into g
select new
{
Parent = g.Key,
Sash = g.Sum(x => x.Quantity),
Vlh = g.Sum(x => x.Value),
}).ToList();
This avoids passing the child1 navigation property on MyObj1 into a context where it's trying to convert IQueryables into SQL, which child1 is not (directly).
The cast to nullable decimals is necessary because of the null values.
If you want zeros in stead of null values you have to add DefaultIfEmpty:
Dim lst = (From t In context.MyObj1 _
where t1.id>6 Select New With { _
.Parent = t, _
.sash = t.child1.Where(Function(t2) t2.tp=2) _
.Select(Function(t3) t3.quantity), _
.DefaultIfEmpty().Sum(), _
.vlh = t.child1.Where(Function(t3) t3.tp=2) _
.Select(Function(t3) t3.value) _
.DefaultIfEmpty().Sum() _
}).ToList
This return an IEnumerable with a 0 value when there are no results in the subqueries.