How to Select one element from a linq multiple join table? - c#

var queryInfo = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
p.CREATION,
q.USERNAME,
q_email = q.EMAIL,
q_fullname = q.FULL_NAME,
b_email = b.EMAIL,
p.ORIGINAL_USER,
b_fullname = b.FULL_NAME
});
Name = queryInfo.ToList().ElementAt(0).ToString();
ID = queryInfo.ToList().ElementAt(1).ToString();
exchange = queryInfo.ToList().ElementAt(2).ToString();
Creation = queryInfo.ToList().ElementAt(3).ToString();
AUsername = queryInfo.ToList().ElementAt(4).ToString();
AEmail = queryInfo.ToList().ElementAt(5).ToString();
AFullName = queryInfo.ToList().ElementAt(6).ToString();
EEmail = queryInfo.ToList().ElementAt(7).ToString();
EUsername = queryInfo.ToList().ElementAt(8).ToString();
EFullName = queryInfo.ToList().ElementAt(9).ToString();
The query is correct and working, I'm having problem trying to select and assign one to each declared variable.
I tried
queryInfo.ToList().ElementAt(0).ToString();
but this is not working. What is the proper syntax?

Create a custom class so you can map your resut into
class:
public class User
{
public string Name { get; set; }
public string ID { get; set; }
public string exchange { get; set; }
public string Creation { get; set; }
public string AUsername { get; set; }
public string AEmail { get; set; }
public string AFullName { get; set; }
public string EEmail { get; set; }
public string EUsername { get; set; }
public string EFullName { get; set; }
}
mapping:
User result = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new User()
{
Name = p.NAME,
ID = p.ID,
exchange = p.EXCHANGE,
Creation = p.CREATION,
AUsername = q.USERNAME,
AEmail = q.EMAIL,
AFullName = q.FULL_NAME,
EEmail = b.EMAIL,
EUsername = p.ORIGINAL_USER,
EFullName = b.FULL_NAME
}).FirtstOrDefault();

Materialize query via FirstOrDefault() and retrieve property values:
var queryInfo =
(from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
p.CREATION,
q.USERNAME,
q_email = q.EMAIL,
q_fullname = q.FULL_NAME,
b_email = b.EMAIL,
p.ORIGINAL_USER,
b_fullname = b.FULL_NAME
})
.FirstOrDefault();
Name = queryInfo?.NAME;
ID = queryInfo?.ID.ToString();
exchange = queryInfo?.EXCHANGE;
Creation = queryInfo?.CREATION.ToString();
AUsername = queryInfo?.USERNAME;
AEmail = queryInfo?.q_email;
AFullName = queryInfo?.q_fullname;
EEmail = queryInfo?.b_email;
EUsername = queryInfo?.ORIGINAL_USER;
EFullName = queryInfo?.b_fullname;

First some background information
Your code is very inefficient.
queryInfo is an IQueryable<...>, meaning that it holds a query: the potential to fetch some data. It does not hold the data itself.
For this, the IQueryable holds an Expression and a Provider. The Expression represents the query in some generic format. The Provider knows who should execute this query (usually a database management system) and what language is used to communicate with this DBMS.
As long as you concatenate LINQ methods that return IQueryable<TResult>, only the Expression changes. The query is not executed, there is no communication with the DBMS. Concatenating this kind of LINQ methods is efficient.
IQueryable also implements IEnumerable. This means, that to execute the query and to enumerate the fetched sequence, at its lowest level you use GetEnumerator() to get the enumerator, and repeatedly call MoveNext() / Current to access the enumerated element:
IQueryable<TResult> query = dbContext.Students.Where(...).OrderBy(...);
// execute the query:
using (IEnumerator<TResult> enumerator = query.GetEnumerator())
{
// and enumerate the fetched data:
while (enumerator.MoveNext())
{
// There is another element, process it:
TResult fetchedElement = enumerator.Current;
ProcessFetchedElement(fetchedElement);
}
}
Well, this is a lot of code. Usually we use high level methods, which deep inside will call GetEnumerator() / MoveNext() / Current:
// execute the query and process the fetched data:
foreach (TResult fetchedElement in query)
{
ProcessFetchedElement(fetchedElement);
}
All LINQ methods that return IQueryable<...> will not execute the query. The other LINQ methods (= the ones that return List<TResult>, TResult, Boolean, etc, anything not IQueryable) will call foreach or deep inside GetEnumerator / MoveNext / Current. These other methods will contact the database to execute the query.
What does this have to do with my question?
Let's look at your code:
var queryInfo = ...
// queryInfo is an IQueryable. The query is not executed yet!
Name = queryInfo.ToList().ElementAt(0).ToString();
// ToList will execute the query and put all data in a List,
// from this List you take the first element and call ToString()
ID = queryInfo.ToList().ElementAt(1).ToString();
// ToList will execute the query again and put all data in a second List,
// from this List you take the second element and call ToString()
etc. You execute the query 10 times. You join the three tables 10 times, you keep only the one with p.Name equal to IdVal and send the remaining data to your process. You do this 10 times.
It would be much more efficient to do this only once:
// execute the query once and put all fetched data in a List
var fetchedData = queryInfo.ToList();
// access the fetched data. Since it is a List, we can use indexes
Name = fetchedData[0].ToString();
Id = fetchedData[1].ToString();
exchange = fetchedData[2].ToString();
Creation = fetchedData[3].ToString();
I don't think that is what you want.
If I look closer at your query, then I see that you join three tables:
var queryInfo = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
From the joined table (=sequence of rows), you keep only those rows that have p.Name == idVal:
where p.NAME == IdVal
There might be one such element, there might be more, or maybe none.
From each remaining row, you make one new object:
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
...
});
As said before: the result is a query. The query represents the potential to fetch a sequence of objects with properties Name, Id, Exchange, ....
I think, that you want the Name / Id / Exchange / etc from the first element of the sequence
If that is the case, we don't have to fetch all elements, we only need to fetch the first element (if there is one)
var queryInfo = ...
// execute the query, and ask only for the first element.
// In SQL this is something like SELECT TOP 1 ... FROM ...
var fetchedElement = queryInfo.FirstOrDefault();
Now if your query yields one or more elements, you will have only the first one. However, if your query results in an empty sequence, fetchedElement will be null
if (fetchedElement != null)
{
// There is an element with Name == IdVal
Name = fetchedElement.Name;
Id = fetchedElement.Id;
...
}
else
{
// There is no such element; TODO: report to operator?
}
Be aware, that it if the query yields more than one element, it is not guaranteed what the first element of your sequence might be. Therefore, if you expect that in some cases there might be more than one element, consider to order the sequence, so the first element is defined. For instance order by ascending creation date. Sometimes the DBMS does not accept a FirstOrDefault of an unordered sequence.

Related

entity framwork core generates weird sql

I have an existing LINQ query that I am trying to optimize. I have the following entity Types (simplified)
public class Account
{
public int Id { get; set; }
public IEnumerable<OpportunityInfo> Opportunities { get; set; }
}
public class Opportunity
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public IEnumerable<Quote> Quotes { get; set; }
}
public class Quote
{
}
It is a standard hierarchy of Account to Opportunity to Quote. Nothing Special. I have the following query that I am using on an ASP.NET Core controller index method. I am starting from Quote and working backwards because there is dynamic query logic between the query and opportunityQuotes that must be Quote based. Otherwise I would start from the top direction.
var query = from o in Quotes select o;
additional query logic (filtering and sorting)
var opportunityQuotes = from o in query
group o by new
{
accountId = o.Opportunity.AccountId,
accountName = o.Opportunity.Account.Name,
active = o.Opportunity.Account.Active,
}
into p
select new
{
Id = p.Key.accountId,
Name = p.Key.accountName,
Active = p.Key.active,
Opportunities =
(from q in p
group q by new
{
Id = q.Opportunity.Id,
Name = q.Opportunity.Name,
Active = q.Opportunity.Active
}
into r
select new
{
Name = r.Key.Name,
Id = r.Key.Id,
Active = r.Key.Active,
Quotes = r
})
};
opportunityQuotes.Dump();
This query generates the following SQL.
SELECT [o].[Id], [o].[ARRValue], [o].[AccountId], [o].[AdjustedArr], ...
FROM [Quotes] AS [o]
LEFT JOIN [Opportunities] AS [o.Opportunity] ON [o].[OpportunityId] = [o.Opportunity].[Id]
INNER JOIN [Accounts] AS [o.Account] ON [o].[AccountId] = [o.Account].[Id]
ORDER BY [o].[AccountId], [o.Account].[Name], [o.Account].[Active]
GO
SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO
SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO
SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO
In reality it generates on query for each opportunity, but I left that out for brevity sake. In my opinion EF should not generate a separate query for each quote. In fact if I comment out the .Name and .Active key parameters in the query as shown below:
group q by new
{
Id = q.Opportunity.Id,
// Name = q.Opportunity.Name,
// Active = q.Opportunity.Active
}
and comment out the correspond variables in the select clause it generates much cleaner sql.
SELECT [o].[Id], [o].[ARRValue], [o].[AccountId], ...
FROM [Quotes] AS [o]
LEFT JOIN [Opportunities] AS [o.Opportunity] ON [o].[OpportunityId] = [o.Opportunity].[Id]
INNER JOIN [Accounts] AS [o.Account] ON [o].[AccountId] = [o.Account].[Id]
ORDER BY [o].[AccountId], [o.Account].[Name], [o.Account].[Active]
GO
The reason I am confused is that .Name and .Active are in the exact same object, they are grouped in the key in the same way as the .Id field, and therefore I don't see why EF would change its behavior just by adding additional group values. Can someone explain the behavior?
Let's take a step back and look at it from a different perspective: If you were to write the SQL query manually, and wanted to fetch all the data required in one query, you would get a lot of duplicate data for the opportunities and account. You could also do this here:
var query = from o in Quotes select o;
var oppQuotes = from o in query
select new
{
AccountId = o.Opportunity.Account.Id,
AccountName = o.Opportunity.Account.Name,
// ... and so on, with all the fields you expect to use.
OpportunityId = o.Opportunity.Id,
OpportunityName = o.Opportunity.Name,
// ... and so on, with all the fields you expect to use.
QuoteId = o.Id,
QuoteName = o.Name,
// ... and again, you get the point.
};
Then, just do an .AsEnumerable() on it, and perform the grouping in your C# code. The database won't be able to optimize anything anyways.
var opportunityQuotes = from q in oppQuotes.AsEnumerable()
group q by new { q.AccountId, q.AccountName }
into accounts
// ... and so on.
For your question, why EF is creating the strange query, I'm at a loss.
In any case, it is always good to be thinking about how YOU would create the sql code to get the data you want most efficiently and not rely on EF to "do the right thing". In many cases it will, in others it will completely blow up in your face. When you want a query, think of the SQL and then translate that to EF code. If you tell it specifically, what you want, you will get it.

C# Linq or querying IEnumerable

I have an Employee table which also has Department Manager information. I need to populate two dropdowns - one with Employees and other with Managers. Instead of using two queries to pull employees and another query to pull managers, I am querying table once and storing all info in cache in an IEnumerable EmployeeList.
I need some query to pull managers from that query - either using LINQ or loop within C# code. I have written loop but it is very inefficient.
Here is the SQL query to populate HCache:
SELECT [Dept_Mgr_ID] As MgrId,
EmployeeId,
EmpLastName,
EmpFirstName
FROM Employee_tbl
Here I am trying to loop through the cache and join EmployeeId and MgrId
List<DTO.Employee> Mgrs = new List<DTO.Employee>(0);
for (int i = 0; i < HCache.EmployeeList.Count(); i++)
{
foreach(var e in HCache.EmployeeList)
{
if (HCache.EmployeeList.ElementAt(i).EmployeeId == e.MgrId)
{
Mgrs.Add(new DTO.Employee() { MgrID = e.MgrId,
ManagerLastName = e.EmpLastName,
ManagerFirstName = e.EmpFirstName
});
}
}
}
I am not using this query, however, this is how I can get the results using 2nd query to get managers:
WITH CTE_Manager_ID
AS
(
SELECT DISTINCT [Dept_Mgr_ID]
FROM Employee_tbl
)
SELECT EmployeeId,
EmpLastName,
EmpFirstName
FROM Employee_tbl Emp
INNER JOIN CTE_Manager_ID cteMgr
ON cteMgr.Dept_Mgr_ID = Emp.EmployeeId
I'd say you should use your second SQL query to get the managers, but I'll try to speed up your code.
Problems:
Assuming EmployeeList is an IEnumerable, EmployeeList.ElementAt(i) is an O(n) operation, i.e. slow. It's a nested loop behind the scenes.
EmployeeList.Count() is an O(n) operation, i.e. slow.
The resulting complexity of your code is O(n^3), i.e. very slow.
How to improve:
Do one pass to build a map from EmployeeId to Employee (or whatever you store in HCache.EmployeeList). This will enable you to find them quickly by id (in O(1)).
Do another pass through EmployeeList to collect the managers.
The overall complexity is O(n), i.e. proportional to the size of the EmployeeList collection.
Here is some code to illustrate the idea:
class Emp {
public int EmployeeId {get;set;}
public int MgrId {get;set;}
public string EmpLastName {get;set;}
}
IEnumerable<Emp> EmployeeList = new List<Emp> {
new Emp { EmployeeId = 1, MgrId = 0, EmpLastName = "boss" },
new Emp { EmployeeId = 2, MgrId = 1, EmpLastName = "dude" } };
IDictionary<int, Emp> dict = EmployeeList.ToDictionary(e => e.EmployeeId);
var managers = EmployeeList
.Select(e => dict.TryGetValue(e.MgrId, out Emp mgr) ? mgr : null)
.OfType<Emp>()
.ToList()
// List<Emp>(1) { Emp { EmpLastName="boss", EmployeeId=1, MgrId=0 } }
Note that this code potentially produces duplicates in the managers list, which may or may not be what you want, but your code behaves this way so I preserved the behavior.

C# LINQ statement with joins, group by and having then mapped into list object

I have a model called ElectricityBillSiteExceeding that looks like this:
public class ElectricityBillSiteExceeding
{
public string GroupInvoiceNumber { get; set; }
public int ElectricityBillMainId { get; set; }
public string SiteNo { get; set; }
public decimal BillSiteTotal { get; set; }
public decimal MaximumAmount { get; set; }
}
I want to create a list of this type and use it to feed a grid on one of my pages, the purpose is to show which site has bills that exceed the max amount allowed.
I have written the SQL which will give me this dataset, it looks like this:
SELECT SUM(ElectricityBillSiteTotal),
ebs.ElectricityBillMainId,
SiteNo,
ebm.GroupInvoiceNumber,
es.MaximumAmount
FROM dbo.ElectricityBillSites ebs
LEFT JOIN dbo.ElectricityBillMains ebm
ON ebs.ElectricityBillMainId = ebm.ElectricityBillMainId
LEFT JOIN dbo.ElectricitySites es
ON ebs.SiteNo = es.SiteNumber
GROUP BY ebs.ElectricityBillMainId, SiteNo, ebm.GroupInvoiceNumber, es.MaximumAmount
HAVING SUM(ElectricityBillSiteTotal) <> 0 AND SUM(ElectricityBillSiteTotal) > es.MaximumAmount
I'm now in my repository trying to write the method which will go to the database and fetch this dataset so that I can power my grid for the user to see.
This is where I'm struggling. I have written a basic LINQ statement to select from a couple of tables, however I'm unsure how I can incorporate the group by and having clause from my SQL and also how I can then turn this IQueryable object into my List<ElectricityBillSiteExceeding> object.
What I have so far
public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
var groupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals
billMains.ElectricityBillMainId
join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
//TODO: group by total, mainId, siteNo, GroupInv, MaxAmt and Having no total = 0 and total > max
select new
{
groupInv = billMains.GroupInvoiceNumber,
mainId = billMains.ElectricityBillMainId,
siteNo = billSites.SiteNo,
total = billSites.ElectricityBillSiteTotal,
max = sites.MaximumAmount
};
//TODO: Map the result set of the linq to my model and return
throw new NotImplementedException();
}
Can anyone point me in the right direction here?
The correct Linq query for your sql is the following. See Left Join to understand the DefaultIfEmpty and also the notes there about the use of ?. in the following group by.
(About the having - in linq you just provide a where after the group by)
var result = from ebs in ElectricityBillSites
join ebm in ElectricityBillMains on ebs.ElectricityBillMainId equals ebm.ElectricityBillMainId into ebmj
from ebm in ebmj.DefaultIfEmpty()
join es in ElectricitySites on ebs.SiteNo equals es.SiteNumber into esj
from es in esj.DefaultIfEmpty()
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, ebm?.GroupInvoiceNumber, es?.MaximumAmount } into grouping
let sum = grouping.Sum(item => item.ebs.ElectricityBillSiteTotal)
where sum > 0 && sum > grouping.Key.MaximumAmount
orderby sum descending
select new ElectricityBillSiteExceeding
{
GroupInvoiceNumber = grouping.Key.GroupInvoiceNumber,
ElectricityBillMainId = grouping.Key.ElectricityBillMainId,
SiteNo = grouping.Key.SiteNo,
BillSiteTotal = sum,
MaximumAmount = grouping.Key.MaximumAmount
};
The error you get:
An expression tree lambda may not contain a null propagating operator
By reading this I conclude that you have an older versino of the provider and thus replace the group by code from the code above with the following:
let GroupInvoiceNumber = ebm == null ? null : ebm.GroupInvoiceNumber
let MaximumAmount = es == null ? 0 : es.MaximumAmount
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, GroupInvoiceNumber, MaximumAmount } into grouping
Before getting into grouping , you need to be aware that the default join in LINQ is always an INNER JOIN. Take a look at the MSDN page How to: Perform Left Outer Joins. However, in the solution I'm presenting below, I'm using INNER JOINs since you are using fields from the other tables in your grouping and having clauses.
For reference on grouping using LINQ, check out How to: Group Query Results on MSDN.
A solution specific to your case is going to look something like:
public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
var qryGroupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals billMains.ElectricityBillMainId
join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
where billSites.ElectricityBillSiteTotal != 0 && billSites.ElectricityBillSiteTotal > sites.MaximumAmount
group new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount }
by new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount } into eGroup
select eGroup.Key;
var inMemGroupedBillSitesThatExceed = qryGroupedBillSitesThatExceed.AsEnumerable();
var finalResult = inMemGroupedBillSitesThatExceed.Select(r => new ElectricityBillSiteExceeding()
{
BillSiteTotal = r.ElectricityBillSiteTotal,
ElectricityBillMainId = r.ElectricityBillMainId,
GroupInvoiceNumber = r.GroupInvoiceNumber,
MaximumAmount = r.MaximumAmount,
SiteNo = r.SiteNo,
});
return finalResult.ToList();
}
This probably will be enough. You could use AutoMapper. It will trivialize mapping to classes.
var resultList = groupedBillSitesThatExceed
.AsEnumerable() //Query will be completed here and loaded from sql to memory
// From here we could use any of our class or methods
.Select(x => new ElectricityBillSiteExceeding
{
//Map your properties here
})
.ToList(); //Only if you want List instead IEnumerable
return resultList;

Take() takes different values than Skip() skips

I have written two different queries, the first one is supposed to get the first 5 objects, and then the next one is supposed to get the next 5 objects ordered by the purchased value. The problem is that the two values in the middle are the same and when I take the first five objects, and then skip the first five objects and take the next five objects the last object of the first set is the same as the first value of the second set and the object with the same purchased value as this object is never shown. My queries are below.
var query = (from v in db.VideoGames
where v.gamesystem == "PC"
orderby v.purchased descending
select v).Take(5);
var query2 = (from v in db.VideoGames
where v.gamesystem == "PC"
orderby v.purchased descending
select v).Skip(5).Take(5);
I would like to know if there is something that I can do differently to keep this from happening.
Edit: I feel that my explanation may be a little confusing so I am going to add an example given 10 VideoGame objects in a database and there purchased value.
VideoGame1.purchased = 1,
VideoGame2.purchased = 2,
VideoGame3.purchased = 3,
VideoGame4.purchased = 4,
VideoGame5.purchased = 5,
VideoGame6.purchased = 5,
VideoGame7.purchased = 7,
VideoGame8.purchased = 8,
VideoGame9.purchased = 9,
VideoGame10.purchased = 10
Here is what I am receiving
query: VideoGame10, VideoGame9, VideoGame8, VideoGame7, VideoGame5
query2: VideoGame5, VideoGame4, VideoGame3, VideoGame2, VideoGame1
Here is what I want
query: VideoGame10, VideoGame9, VideoGame8, VideoGame7, VideoGame6
query2: VideoGame5, VideoGame4, VideoGame3, VideoGame2, VideoGame1
I do not care if I get VideoGame5 in the first query, just as long as I get both the VideoGame5 object and the VideoGame6 object.
the object with the same purchased value as this object is never shown
It did not click till I read this sentence a few times. Assuming you mean you have data like the following
class Videogames
{
public string Name { get; set; }
public int purchased { get; set; }
public string gamesystem { get; set; }
public Videogames(string name, int purchased)
{
Name = name;
this.purchased = purchased;
gamesystem = "PC";
}
}
static void Main(string[] args)
{
var VideoGames = new List<Videogames>();
VideoGames.Add(new Videogames("A", 1));
VideoGames.Add(new Videogames("B", 2));
VideoGames.Add(new Videogames("C", 2));
VideoGames.Add(new Videogames("D", 3));
var query = (from v in VideoGames
where v.gamesystem == "PC"
orderby v.purchased descending
select v).Take(2);
var query2 = (from v in VideoGames
where v.gamesystem == "PC"
orderby v.purchased descending
select v).Skip(2).Take(2);
}
You are getting results like A, B and B, D...
The problem is you do not have deterministic sorting, when there is a tie it is up to whatever underlying system is performing the orderby (likely SQL server, and this is EF or Similar).
To fix this you must make your sorting system more specific so there is no ambiguous ties for the sorting engine to decide for you.
Changing your queires to
var query = (from v in VideoGames
where v.gamesystem == "PC"
orderby v.purchased descending, v.Name ascending
select v).Take(2);
var query2 = (from v in VideoGames
where v.gamesystem == "PC"
orderby v.purchased descending, v.Name ascending
select v).Skip(2).Take(2);
would fix it. You did not show your model so I had to make up a field Name. In database situations you usualy have some kind of primary key ID field, just make your queries sort by the primary key as the last sorting parameter and you should be fine.

Combining Tables With Different Data Using Linq in MVC?

I have Two classes Named OfflineOrderLineItem.cs and OnlineOrderLineItem.cs both have diff Order table named offline and Online
In that i want to Combine the two tables data to search and Display the Fields from both tables
How to do that using linq in mvc4 ??? any idea.....
public virtual IPagedList<OnlineOrderLineItem> SearchOrderLineItems(string PoNumber)
{
var query1 = (from ol in _offlineOrderLineItemRepository.Table
select new
{
ol.Name
}).ToList();
var query2 = (from opv in _onlineOrderLineItemRepository.Table
select new
{
opv.Name
}).ToList();
var finalquery = query1.Union(query2);
if (!String.IsNullOrWhiteSpace(Name))
finalquery = finalquery.Where(c => c.Name == Name);
var orderlineitems = finalquery.ToList(); //its not working it throw a error
return new PagedList<OnlineOrderLineItem>(orderlineitems);//error
}
Error
cannot convert from 'System.Collections.Generic.List<AnonymousType#1>'
to 'System.Linq.IQueryable<Nop.Core.Domain.Management.OnlineOrderLineItem>'
to 'System.Linq.IQueryable<Nop.Core.Domain.Management.OnlineOrderLineItem>'
query1 and query2 are lists of an anonymous type with a single property of type string. (I assmume the ol.Name and opv.Name are strings.) Hence finalQuery and orderlineitems are collections of this anonymous as well. By specifying PagedList<T> you require that the collection passed into the constructor is an enumeration of type T. T is OnlineOrderLineItem, but the enumeration passed into the constructor is the anonymous type which is a different type. Result: compiler error.
To solve the problem I suggest that you define a named helper type that you can use to union the two different types OfflineOrderLineItem and OnlineOrderLineItem:
public class OrderLineItemViewModel
{
public int Id { get; set; }
public string PoNumber { get; set; }
public string Name { get; set; }
// maybe more common properties of `OfflineOrderLineItem`
// and `OnlineOrderLineItem`
}
Then your SearchOrderLineItems method should return a paged list of that helper type:
public virtual IPagedList<OrderLineItemViewModel> SearchOrderLineItems(
string PoNumber)
{
var query1 = from ol in _offlineOrderLineItemRepository.Table
select new OrderLineItemViewModel
{
Id = ol.Id,
PoNumber = ol.PoNumber,
Name = ol.Name,
// maybe more properties
};
// don't use ToList here, so that the later Union and filter
// can be executed in the database
var query2 = from opv in _onlineOrderLineItemRepository.Table
select new OrderLineItemViewModel
{
Id = opv.Id,
PoNumber = opv.PoNumber,
Name = opv.Name,
// maybe more properties
};
// don't use ToList here, so that the later Union and filter
// can be executed in the database
var finalquery = query1.Union(query2);
// again no ToList here
if (!string.IsNullOrWhiteSpace(PoNumber))
finalquery = finalquery.Where(c => c.PoNumber == PoNumber);
var orderlineitems = finalquery.ToList(); // DB query runs here
return new PagedList<OrderLineItemViewModel>(orderlineitems);
}
It is important to use ToList only at the very end of the query. Otherwise you would load the whole tables of all OnlineOrderLineItems and all OfflineOrderLineItems into memory and then filter out the items with the given PoNumber in memory which would be a big overhead and performance desaster.
Instead of
var orderlineitems = finalquery.ToList();
Try
var orderlineitems = finalquery.AsQueryable();
From https://github.com/TroyGoode/PagedList/blob/master/src/PagedList/PagedList.cs, PagedList takes a IQueryable<T>
Queryable.AsQueryable<TElement> Method

Categories