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.
Related
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.
So I have to identify everyone who has a higher income than "JONES".
Here is the Schema:
new Emp{name = "SMITH", income = 800},
new Emp{name = "JONES", income = 600},
new Emp{name = "ADAMS", income = 900},
new Emp{name = "KING", income = 400}
I can't find a way to build this in a Query Syntax...
so let's say you have your data like this. so this should solve your problem. so to explain the code below.
I have a list of data based of the Emp class.
I also have a variable of jones that contains information about jones.
I can then use Linq to query the data list of Emp where the emp income is greater than the matches Jones. then I return then in orderbydescending using Linq.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
//Emp class for
public class Emp
{
public string name { get; set; }
public double income { get; set; }
}
public static void Main()
{
//List of Emp data base off the Emp class object.
var data = new List<Emp>
{
new Emp {name = "SMITH", income = 800},
new Emp {name = "JONES", income = 600},
new Emp {name = "ADAMS", income = 900},
new Emp {name = "KING", income = 400}
};
//Jones data that will be used for querying
var jones = new Emp {name = "JONES", income = 600};
//List of Emp that have income higher than jones.
var higherThanJones = data.Where(item => item.income > jones.income)
.OrderByDescending(i => i.income)
.ToList();
//Foreach loop to show the people with income than jones
foreach (var people in higherThanJones)
{
//printing out the names of the people higher than Jones
Console.WriteLine(people.name);
}
}
}
In query syntax, you can first create a query to find the matching record, or return the default (which will be 0) if there is no match:
var jonesIncome = (from e in emps
where e.name == "JONES"
select e.income).FirstOrDefault();
Then you can use the income query to find the rows desired:
var higherThanJones = from e in emps
where e.income > jonesIncome
select e;
Since queries use deferred execution, the two queries will actually be executed when higherThanJones results are used. If you are querying a database, the two queries will be translated into a single SQL query, depending on the LINQ you are using and the database provider.
You could also use lambda/fluent syntax to combine into a single query (I prefer not to combine query syntax as it doesn't read as well):
var matchName = "JONES";
var higherThanMatch = emps.Where(e => e.income > emps.Where(e2 => e2.name == matchName)
.Select(e2 => e2.income)
.FirstOrDefault());
So I have to identify everyone who has a higher income than "JONES".
Are you certain there is a "Jones"? Is there exactly one "Jones"?
The answer depends on whether you are working IQueryable or IEnumerable.
If you need to do it as Queryable, you need to pack it in one Query:
IQueryable<Employee> employees = ...
var employeesWithHigherIncomes = employees
.Where(employee => employee.Income >
employees.Where(employee => employee.Name == name)
.FirstOrDefault()));
Luckily your database is smart enough not to search Jones again for every Employee.
As Enumerable:
string name = "Jones"
IEnumerable<Employee> employees = ...
var incomeJones = employees.Where(employee => employee.Name == name)
.Select(employee => employee.Income)
.FirstOrDefault();
var employessWithHigherIncome = employees
.Where(employee => employee.Income > incomeJones)
.FirstOrDefault();
You will enumerate your sequence at utmost twice: once (partly) until you found the first "Jones", and once completely to find all higher incomes.
If I had put the query to find the income of Jones in the "Where", like I did in Queryable, then for every Employee I had to enumerate the sequence to find Jones again.
I have an initial query that I want to modify to increase granularity in my results. But Visual Studio tells me my query isn't valid and I can't understand why. Basically I want to group my data according to 2 property (columns) and also group one of the property by the first N characters.
Initial query that works:
List<PostalCode> codes = (from customer in bd.Customers
group customer by customer.postalcode.Substring(0, postalCodeLength) into postalCodes
select new PostalCode
{
Postal = postalCodes.Key,
Count = postalCodes.Count()
}).ToList();
return codes;
Query marked by ** as wrong by VS2010:
List<PostalCode> codes = (from customer in bd.Customers
group customer by new { **customer.postalcode.Substring(0, postalCodeLength)**, customer.CustomerGroupType}
into postalCodes
select new PostalCode
{
Postal = postalCodes.Key.postalcode,
CustomerGroupType = postalCodes.Key.CustomerGroupType,
Count = postalCodes.Count()
}).ToList();
return codes;
The new { } object syntax requires that properties have names - something your original query did not require. It cannot infer a name from your method call. So I'd recommend changing it to something like:
from customer in bd.Customers
group customer by new { TrimmedPostalCode = customer.postalcode.Substring(0, postalCodeLength), customer.CustomerGroupType}
into postalCodes
select new PostalCode
{
Postal = postalCodes.Key.TrimmedPostalCode,
CustomerGroupType = postalCodes.Key.CustomerGroupType,
Count = postalCodes.Count()
}
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
What's the equivalent LINQ instruction for a Datatable of the following SQL query:
SELECT code_direction, count(TP) AS CN
FROM table1
WHERE cod_time = 'A011'
GROUP BY TP,code_direction;
and how to get the result into a new datatable?
I tried to convert it but I there're some errors. Someone could take a look on this:
var query = from t in table1.AsEnumerable()
group t by new { t.TP, t.code_direction }
into grp
select new
{
grp.Key.code_direction,
CN = grp.Count(t.TP)
};
foreach (var x in query)
{
Console.Write(x.code_direction);
Console.Write(x.CN);
}
As far as your first question goes. The LINQ equivalent of the SQL query is:
var query = from t in table1.AsEnumerable()
where t.cod_time == "A011"
group t by new { t.TP, t.code_direction }
into grp
select new
{
grp.Key.code_direction,
CN = grp.Count()
};
Note that you don't have to pass any argument to grp.Count(). (For the obvious reason that in SQL COUNT(TP) is the same as COUNT(*), i.e. just count the number of rows. The story would be different if you'd use COUNT(DISTINCT TP) or similar.)
As far as the second question goes, if your query just returned an IEnumerable<T> where T is DataRow (i.e. a query like table1.AsEnumerable().Where(r => r.cod_time == "A011")) then you could just the DataTableExtensions.CopyToDataTable extension method. As your query returns an anonymous type however, you will have to follow these instructions found on MSDN.
I Have been using LINQ to work on a JSON object returned from a remote sharepoint web service. I have posted this because most of the answers I found online were slightly different from what I needed.
a json list of daily activities is returned from a remote sharepoint list & is then summarised using LINQ
The simplified version of a custom object definition is shown below( & which is defined in the models area of an MVC application)
public class MyCustomObjectList
{
public string eventdate { get; set; }
public string userid { get; set; }
public string action { get; set; }
}
The JSON object is serialised into a MyCustomObjectList array.
var customobject = serializer.Deserialize<MyCustomObjectList>(jsonobject);
I wanted to work out how many actions of each type happened on a given day. NB eventdate is stored as a string in format yyyy-mm-dd hh:MM:ss. This was to simplify conversions between c#, JSON & Jquery ( where required I create DateTime objects elsewhere in the code using the
eventdate.
Some will argue this is inefficient, but I prefer to split processes into a sequential set of really simple operations, for the sake of easier debugging & to help other people follow my code. Thats why there are 2 Linq queries .
querya strips out the time component from the eventdate This ensures our later grouping happens by day, & not by second. To be doubly sure that there is no caching, I create it in a new field called actionday. I also rename action to activity, because intellisense was getting confused!! The other columns are copied as is.
var querya =
from c in customobject.rows
select new { actionday = c.eventdate.Substring(0, 10), activity = c.action, c.userid,
c.eventdate };
/* queryb produces a grouped count of querya, grouped on actionday & activity, creating new columns actionkey,ActionCount,Dte,action & DetailList ( which is a summary for debugging purposes)
*/
var queryb=
from p in querya group p by new { p.actionday, p.activity} into idGroup
actionkey = idGroup.Key,
ActionCount = idGroup.Count(),
Dte = idGroup.Key.actionday,
action = idGroup.Key.activity,
DetailList = idGroup
};
Here’s a version that sumarises by 3 columns
var queryc = from p in querya
group p by new { p.actionday, p.userid, p.activity} into idGroup
select new
{
actionday = idGroup.Key,
ActionCount = idGroup.Count(),
userid = idGroup.Key.userid,
Dte = idGroup.Key.actionday,
action = idGroup.Key.activity,
DetailList = idGroup
};