too many fields to specify in result set of join - linq-to-sql

So similar questions have been asked with not much of an answer....
I have a Stats Table related to a Branch table. The Stats records contain pc stats of a particular bank branch.
Branch
+Code
+Name
+Area
...
Stats
+BranchCode
+IP
+UpSpeed
+DownSpeed
...
Here is my linq query...
var stats = from st in store.Stats
join b in store.Branches on st.BranchCode equals b.Brcd
select new
{
st.ID,st.IP,st.Name,b.Brcd,b.Branch_Name..............
};
The issue is st and b have a LOT of fields, for now I guess I will type them all... but isn't there a solution to this? *Prevent the typing of all fields... something like a * wildcard?
Did try intersect however the types need to be the same!
Thanks
Gideon

1
var stats =
from st in store.Stats
join b in store.Branches on st.BranchCode equals b.Brcd
select new
{
Stats = st,
Branch = b
};
Creates anonymous instances with one Stats and one Branch.
2
var stats =
from b in store.Branches
join st in store.Stats
on b.Brcd equals st.BranchCode
into branchstats
select new
{
Branch = b
Stats = branchstats
};
Creates anonymous instances with one Branch and its Stats.
3
var stats =
from b in store.Branches
select new
{
Branch = b
Stats = b.Stats
};
Same as 2, If there's an association between the two types in the designer, then there's a relational property generated on each type.
4
DataLoadOptions dlo = new DataLoadOptions()
dlo.LoadWith<Branch>(b => b.Stats);
store.LoadOptions = dlo;
List<Branch> branches = store.Branches.ToList();
Here, DataLoadOptions are specified that automatically populate the Stats property when any Branch is loaded.

Related

Dapper batch queries instead of a single query executed many times

I'm trying to optimize some queries, and I have this crazy one. The basic idea is I get a bunch of rooms which has some corresponding meetings. I currently run a query to get all the rooms, then foreach room I need to get the meetings, where I do a query for each room. This opens up for a lot of database connections (i.e. 1000 rooms each having to open a connection to pull the meetings), and I'd like to do it as a batch instead. I am using dapper to map my queries to models and I'm trying to use the list parameters described here
SELECT
mm.id,
mm.organizer_name as Organizer,
mm.subject as Subject,
mm.start_time as StartTime,
mm.end_time as EndTime,
(mm.deleted_at IS NOT NULL) as WasCancelled,
(am.interactive = 0 AND am.cancelled_at IS NOT NULL) as WasNoShow,
c.name as name
FROM master_meeting mm
LEFT JOIN master_meeting__exchange mme ON mme.id=mm.id
LEFT JOIN master_meeting__forwarded_exchange mmfe ON mmfe.id=mm.id
LEFT JOIN meeting_instance__exchange mie ON mie.meeting_id=mm.id
LEFT JOIN meeting_instance__forwarded_exchange mife ON mife.meeting_id=mm.id
LEFT JOIN appointment_meta__exchange ame ON mie.item_id=ame.item_id
LEFT JOIN appointment_meta__exchange ame2 ON mife.item_id=ame2.item_id
LEFT JOIN appointment_meta am ON am.id=ame.id
LEFT JOIN appointment_meta am2 ON am2.id=ame2.id
LEFT JOIN calendar c on mie.calendar_id=c.id
WHERE mie.calendar_id = #Id OR mife.calendar_id=#Id
AND mm.start_time BETWEEN #StartTime AND #EndTime
Without going into details of the crazy long join sequence, I currently have to do this query, a lot. It has been written up initially as:
List<Result> resultSet = new List<Result>();
foreach(int id in idList){
resultSet.AddRange(
_queryHandler.Handle(
new MeetingQuery(id, "FixedStartTime", "FixedEndTime")
)
);
}
Which in turn calls this a bunch of times and runs the query:
_connection.Query<Meeting>(sql,
new {
Id = query.id,
StartTime = query.StartTime,
EndTime = query.EndTime
}
);
This obviously requires quite a few database connections, and I'd like to avoid this by having dapper doing multiple queries, but I get the following error if I try to add the parameters as a list which looks like this:
class Parameters {
int Id;
string StartTime;
string EndTime;
}
List<Parameters> parameters = new List<Parameters>();
foreach(int id in idList)
parameters.Add(new Parameters(id, "SameStartTime", "SameEndTime");
Then I would use the list of parameters as this:
_connection.Query<Meeting>(sql,parameters);
The error I get is:
dapper Additional information: An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
Firstly, it's possible to reuse a single connection for multiple queries, so you could retrieve all of your data with multiple Dapper "Query" calls using the same connection.
Something like the following (which isn't the exact same query as you showed since I was testing this on my own computer with a local database; it should be easy enough to see how it could be altered to work with your query, though) -
private static IEnumerable<Record> UnbatchedRetrieval(IEnumerable<Parameters> parameters)
{
var allResults = new List<Record>();
using (var conn = GetConnection())
{
foreach (var parameter in parameters)
{
allResults.AddRange(
conn.Query<Record>(
"SELECT Id, Title FROM Posts WHERE Id = #id",
parameter
)
);
}
}
return allResults;
}
public class Parameters
{
public int Id { get; set; }
}
However, if it really is the number of queries that you want to reduce through batching then there isn't anything in Dapper that makes it very easy to do since each parameter must be uniquely named, which won't be the case if you provide multiple instances of a type as the "parameters" value (since there will be "n" Id values that are all called "Id", for example).
You could do something a bit hacky to produce a single query string that will return results from multiple parameter sets, such as the following -
private static IEnumerable<Record> BatchedRetrieval(IEnumerable<Parameters> parameters)
{
using (var conn = GetConnection)
{
var select = "SELECT Id, Title FROM Posts";
var where = "Id = {0}";
var sqlParameters = new DynamicParameters();
var combinedWheres =
"(" +
string.Join(
") OR (",
parameters.Select((parameter, index) =>
{
sqlParameters.Add("id" + index, parameter.Id);
return string.Format(where, "#id" + index);
})
) +
")";
return conn.Query<Record>(
select + " WHERE " + combinedWheres,
sqlParameters
);
}
}
public class Parameters
{
public int Id { get; set; }
}
.. but this feels a bit dirty. It might be an option to explore, though, if you are absolutely sure that performing those queries one-by-one is a performance bottleneck.
Another thing to consider - when you need the data for 1000 different ids, are the start and end times always the same for each of the 1000 queries? If so, then you could possibly change your query to the following:
private static IEnumerable<Record> EfficientBatchedRetrieval(
IEnumerable<int> ids,
DateTime startTime,
DateTime endTime)
{
using (var conn = GetConnection())
{
return conn.Query<Record>(
#"SELECT
mm.id,
mm.organizer_name as Organizer,
mm.subject as Subject,
mm.start_time as StartTime,
mm.end_time as EndTime,
(mm.deleted_at IS NOT NULL) as WasCancelled,
(am.interactive = 0 AND am.cancelled_at IS NOT NULL) as WasNoShow,
c.name as name
FROM master_meeting mm
LEFT JOIN master_meeting__exchange mme ON mme.id=mm.id
LEFT JOIN master_meeting__forwarded_exchange mmfe ON mmfe.id=mm.id
LEFT JOIN meeting_instance__exchange mie ON mie.meeting_id=mm.id
LEFT JOIN meeting_instance__forwarded_exchange mife ON mife.meeting_id=mm.id
LEFT JOIN appointment_meta__exchange ame ON mie.item_id=ame.item_id
LEFT JOIN appointment_meta__exchange ame2 ON mife.item_id=ame2.item_id
LEFT JOIN appointment_meta am ON am.id=ame.id
LEFT JOIN appointment_meta am2 ON am2.id=ame2.id
LEFT JOIN calendar c on mie.calendar_id=c.id
WHERE mie.calendar_id IN #Ids OR mife.calendar_id IN #Ids
AND mm.start_time BETWEEN #StartTime AND #EndTime",
new { Ids = ids, StartTime = startTime, EndTime = endTime }
);
}
}
There may be a problem with this if you call it with large numbers of ids, though, due to the way that Dapper converts the IN clause - as described in https://stackoverflow.com/a/19938414/3813189 (where someone warns against using it with large sets of values).
If that approach fails then it might be possible to do something similar to the temporary table bulk load suggested here: https://stackoverflow.com/a/9947259/3813189, where you get all of the keys that you want data for into a temporary table and then perform a query that joins on to that table for the keys (and then deletes it again after you have the data).

Using a cartesian product in LINQ and Entity framework to combine 3 tables

before I return "set.select" I would like to include fields from another table but I canot join this table because it has no fields in common with the other two tables. How may I adjust my code below to achieve this? Iam using vs2012 sql and in MVC c#
var set =
(from m in managers
from t in context.tblCompany
join tsc in context.tblStyling on t.ccID equals tsc.ccID
select new { tsc.ccID,LogoIcon = tsc.Icon , tsc.style1, tsc.style2, t.Desc })
.ToList();
return set.Select(c => new Settings(c.ccID, c.style1, c.style2, c.Desc, c.LogoIcon , m.firstName , m.lastName));
Okay, it looks like you want to join context.tblCompany and context.tblStyle and get the cross product of the resulting set and managers. If that is correct, then you are already there. You just need to include the fields from manager that you want in your select statement:
var set =
(from m in managers
from t in context.tblCompany
join tsc in context.tblStyling on t.ccID equals tsc.ccID
select new
{
tsc.ccID,
LogoIcon = tsc.Icon,
tsc.style1,
tsc.style2,
t.Desc,
m.firstName,
m.lastName })
.ToList();
return set;

LINQ select after grouping

I'm trying to return a result set from a grouped query and I can't get the select right. In LinqPad the cursor jumps to "ItemID" in Grouped.Key.ItemID with the error:
'int' does not contain a definition for 'ItemID' and no extension method 'ItemID' accepting a first argument of type 'int' could be found
Here's the query:
from B in Bids
join I in Items on B.ItemID equals I.ItemID
group new {B, I} by I.ItemID into Grouped
select new {
ItemID = Grouped.Key.ItemID,
ItemName = Grouped.Key.ItemName,
Bids = Grouped.Key.B
}
I would like the return set to have records comprised of the ItemID, ItemName and all of the associated Bid records.
Thanks very much,
BK
The Grouped.Key refers to the field(s) that you specied in the grouped by x clause. As a result in your query, the Key = I.ItemID.
In your example, instead of thinking from the SQL perspective where you have to flatten out heirarchies, embrace the OO nature of LINQ and object graphs. Adapting your example a bit and setting LINQPad to use C# Statements, I think you will end up with more of what you are looking for. Note: The Dump() extension method is specific to LINQPad to output the results and shows the resulting heirarchy.
var bids = new [] { new { ItemID = 1, BidValue = 30 } , new {ItemID=1, BidValue=45}};
var items = new [] { new { ItemID = 1, ItemName = "x" }, new {ItemID = 2, ItemName="y"} };
var query = from i in items
select new
{
i.ItemID,
i.ItemName,
Bids = from b in bids
where b.ItemID == i.ItemID
select b
};
query.Dump();
That being said, your categories indicate LINQ to SQL. If your model is in LINQ to SQL or EF, you may be able to do this even easier by using the mapped associations:
var query = from i in dc.Items
select new
{
i.ItemID,
i.ItemName,
i.Bids
};
query.Dump();
That says exactly what is written. Groupped.Key will contain I.ItemID, but not the whole I. So you can't write Groupped.Key.ItemID.
Consider:
from B in new [] { new { ItemID = 1, BidValue = 30 } }
join I in new [] { new { ItemID = 1, ItemName = "x" } } on B.ItemID equals I.ItemID
group new { B, I } by I into Groupped
select new {
ItemID = Groupped.Key.ItemID,
ItemName = Groupped.Key.ItemName,
Bids = (from g in Groupped select g.B).ToList()
}
Well, assuming you have foreign keys setup in the database from bid -> item there is no need for all this joining an grouping.
Your items will already have a collection of bids in them.
So you can do things like:
var x = db.Items.Single(i=>ItemId == 1); // get one item
foreach (bid b in x.Bids) // iterate through all the bids
{}
If you really want to have them in an anonymous type, this will do:
from i in db.items
select new { i.ItemID, i.ItemName, i.Bids }
That is the beauty of Linq2Sql. Try to let go of writing SQL in Linq but instead use the more object oriented approach.
In this groupby, ItemID is the Key. ItemID does not have a B property.
group new {B, I} by I.ItemID into Grouped
Here's an improved version of your query which accesses the group properly.
from b in Bids
join i in Items on b.ItemID equals i.ItemID
group b by i into groupedBids
select new
{
Item = i,
Bids = groupedBids
};
Here's a version that uses GroupJoin to do the same thing.
from i in Items
join b in Bids on i.ItemID equals b.ItemID into groupedBids
select new
{
Item = i,
Bids = groupedBids
};
Here's a version that does the join in the database and the group locally. You might do something like this since LinqToSql must re-query by the key of a group to get each group's elements (known as the n+1 problem with groupby).
from x in
(
from i in Items
join b in Bids on i.ItemID equals b.ItemID
select new {Item = i, Bid = b}
).ToList()
group x.b by x.i into groupedBids
select new
{
Item = groupedBids.Key,
Bids = groupedBids
};

equivalent LINQ query

I have a history table for Students in SQL Server 2008.
StudentHistoryId, StudentId, Grade, CreatedAt, ModifiedAt, ModifiedBy, Active
I am new to LINQ.
How do I write a LINQ query to get the latest modified row for all the active students and also the equivalent sql query for the same ?
Something like (Assuming LINQ-SQL):
using (YourDataContext db = new YourDataContext())
{
var data = from s in db.Students
select new
{
StudentId = s.StudentId,
LastHistory = s.Histories
.OrderByDescending(s => s.ModifiedAt)
.Where(s => s.Active)
.FirstOrDefault()
};
}
This is assuming that you want all students, regardless of whether they actually have any history. If don't want this, you can start with the History table and group by Student ID.
To view the SQL, you can hover the variable in debugging to see the SQL produced. I'm too lazy to convert the LINQ ;-)
var q =
from h in history
where h.Active
group h by new { h.StudentId, h.Grade } into g
select new
{
StudentId = g.Key.StudentId,
Grade = g.Key.Grade,
LatestModified = g.Max (x => x.ModifiedAt)
}
LINQ
var tempresult = (from student in Students
where Active == true).OrderByDesc(ModifiedAt)
List<Student> results = new List<Student>();
foreach(var result in tempResult)
{
if((results.Where(r => r.StudentId == result.StudentId).FirstOrDefault()) == null)
{
results.Add(result);
}
}
SQL
Select [Rows]
From Students S
Where S.Active = 1
And S.ModifiedAt = (Select Max(ModifiedAt)
From Student S1
Where S1.StudentId = S.StudentId)
The Linq is hacky (and I'm sure there's a better way, but I can't remember it) and I'm only sort-of confident about the SQL syntax (though that should point you in the right direction even if it's not exactly right), but either of those should get: The maximum ModifiedAt for every student that is currently active.
.FirstOrDefault() [LINQ] or Top 1 would only select the single row (only one student) with the most recent ModifiedAt.

Group By and Sum clauses in LINQ

I've written a simple linq query as follows:
var query = from c in context.ViewDeliveryClientActualStatus
join b in context.Booking on c.Booking equals b.Id
join bg in context.BookingGoods on c.Booking equals bg.BookingId
select new { c, b, bg };
I have filtered the previous query with a number of premises and then needed to group by a set of fields and get the sum of some of them, as so:
var rows = from a in query
group a by new {h = a.c.BookingRefex, b = a.c.ClientRefex, c = a.b.PickupCity, d = a.b.PickupPostalCode} into g
select new
{
Booking_refex = g.Key.h,
Client_refex = g.Key.b,
//Local = g.
Sum_Quan = g.Sum(p => p.bg.Quantity),
};
I'd like to get a few values from a which I haven't included in the group by clause. How can I get those values? They're not accessible through g.
The g in your LINQ expression is an IEnumerable containing a's with an extra property Key. If you want to access fields of a that are not part of Key you will have to perform some sort of aggregation or selection. If you know that a particular field is the same for all elements in the group you can pick the value of the field from the first element in the group. In this example I assume that c has a field named Value:
var rows = from a in query
group a by new {
h = a.c.BookingRefex,
b = a.c.ClientRefex,
c = a.b.PickupCity,
d = a.b.PickupPostalCode
} into g
select new {
BookingRefex = g.Key.h,
ClientRefex = g.Key.b,
SumQuantity = g.Sum(p => p.bg.Quantity),
Value = g.First().c.Value
};
However, if c.Value is the same within a group you might as well include it in the grouping and access it using g.Key.cValue.
Just add those field in the
new {h = a.c.BookingRefex, b = a.c.ClientRefex, c = a.b.PickupCity, d = a.b.PickupPostalCode}
they will be accessible in g then.