I need to define a local variable within a C# enumeration as I have an additional enumeration with that which is called several times creating inefficiencies. The code currently looks like this:
List<object> listData = new List<object>();
List<EmployeeAdvanced> workDataList = GetEmployeeWorkAdvancedData();
List<EmployeeBasic> employeeList = GetEmployeeBasicData();
listData .AddRange(
from employee in employeeList
where employee.SomeNumber > 0
select new{
ID = employee.ID,
SSN = employee.SSN
StreetAddress = workDataList.FirstOrDefault(x=> x.ID==employee.EmployeeID).Address,
Zipcode = workDataList.FirstOrDefault(x=> x.ID==employee.EmployeeID).Zipcode}
);
I actually have numerous enumerations of the workDataList list, but I'm selecting on the same item each time (EmployeeID). Is there some way I can just select my individual workDataList object once, then use it for all assignments within each individual from select loop.
The code above is a simplified version so names, data structures are just created for descriptive clarity.
Let me know if I can provide any additional information.
Thanks!
I think you're looking for let.
List<object> listData = new List<object>();
List<EmployeeAdvanced> workDataList = GetEmployeeWorkAdvancedData();
List<EmployeeBasic> employeeList = GetEmployeeBasicData();
listData .AddRange(
from employee in employeeList
where employee.SomeNumber > 0
let workData = workDataList.FirstOrDefault(x=> x.ID==employee.EmployeeID)
select new{
ID = employee.ID,
SSN = employee.SSN
StreetAddress = workData.Address,
Zipcode = workData.Zipcode}
);
listData.AddRange(from employee in employeeList
where employee.SomeNumber > 0
let employeeTmp = workDataList.FirstOrDefault(x => x.ID == employee.EmployeeID)
select new {
ID = employee.ID,
SSN = employee.SSN
StreetAddress = employeeTmp.Address,
Zipcode = employeeTmp.Zipcode
}
);
You should use the let keyword. Here is a thoroughly explanation of the usage.
Related
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 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.
Here is my data:
private List<Department> Data
{
get
{
return new List<Department>
{
new Department{
Id = 1,
Name = "Tech",
Employees = new List<Employee>{
new Employee{Name = "x", Id = 1 },
new Employee{ Name = "y", Id = 2}
}
},
new Department{
Id = 2,
Name = "Sales",
Employees = new List<Employee>{
new Employee{Name = "a", Id = 3},
new Employee {Name = "b", Id = 4}
}
}
};
}
}
and here I am getting a list of all employees with their appropriate departments:
List<Employee> employees = (from department in Departments
let d = department
from e in d.Employees
select new Employee{
Id = e.Id,
Name = e.Name
Department = d
}).ToList();
What is bothering me is that I have to recreate my Employee object in order to attach the appropriate department to it. Is there a way that I could write my LINQ statement where I don't have to recreate the Employee?
There might be a better way to phrase this question-- so feel free to let me know is there is.
Edit
The reason I'm going down this path is that I'm storing my data by serializing my department:
[
{
"Id":1,
"Name":"Sales",
"Employees":[{"Id":2,"Name":"x"},{"Id":1,"Name":"y"}]
},
{
"Id":2,
"Name":"Tech",
"Employees":[{"Id":3,"Name":"d"},{"Id":4,"Name":"f"}]
}
]
It looks like you want to use LINQ to update an instance. This is not the intended use. Use LINQ to query the instances you want to have, and then loop over the results to update. (non-nested) Loops are not evil.
var query =
from d in Departments
from e in d.Employees
select new { Employee = e, Department = d };
foreach(var x in query)
{
x.Employee.Department = x.Department;
}
You should not have this problem in the first place - You should fully construct your Employee instances when you initially create them, not sometime later - if an employee needs a department to be used, you should add a constructor that allows/enforces providing it:
public Employee(int id, string name, Department department)
{
...
}
You could, if you really, really want, use a let-clause for a side-effect, since assignment expressions return a value:
List<Employee> employees = (from department in Departments
from e in department.Employees
let _ = e.Department = department
select e).ToList();
Also I fully agree with BrokenGlass...
Using let is redundant and not useful in your example query.
Besides, LINQ is not the right tool here. You want to affect the state of the objects you're querying (i.e. creating side-effects), which is generally not recommended.
By direct comparison, this is a better alternative to what you're trying do to:
foreach(var department in Departments)
foreach(var employee in department.Employees)
employee.Department = department;
If you can however, you should do the department assignment at the time you add the employees to the department, either in an AddEmployee method in the Department class, or maybe in a Employee.Department property setter.
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 need to group the following list by the department value but am having trouble with the LINQ syntax. Here's my list of objects:
var people = new List<Person>
{
new Person { name = "John", department = new List<fields> {new fields { name = "department", value = "IT"}}},
new Person { name = "Sally", department = new List<fields> {new fields { name = "department", value = "IT"}}},
new Person { name = "Bob", department = new List<fields> {new fields { name = "department", value = "Finance"}}},
new Person { name = "Wanda", department = new List<fields> {new fields { name = "department", value = "Finance"}}},
};
I've toyed around with grouping. This is as far as I've got:
var query = from p in people
from field in p.department
where field.name == "department"
group p by field.value into departments
select new
{
Department = departments.Key,
Name = departments
};
So can iterate over the groups, but not sure how to list the Person names -
foreach (var department in query)
{
Console.WriteLine("Department: {0}", department.Department);
foreach (var foo in department.Department)
{
// ??
}
}
Any ideas on what to do better or how to list the names of the relevant departments?
Ah, should have been:
foreach (Person p in department.Name) Console.WriteLine(p.name);
Thanks for the extra set of eyes, Fyodor!
Your department property seems like an awkward implementation, particularly if you want to group by department. Grouping with a List as your key is going to lead to a ton of complexity, and it's unnecessary since you only care about one element in the List.
Also, you seem to have created the fields class as a way of simulating either dynamic/anonymous types, or just the Dictionary<string, string> class, I can't really tell. I suggest not doing that; C# already has those types baked in, and working around them will just be inefficient and stop you from using Intellisense. Whatever led you to do that, there's probably a better, more C#-ish way. Besides--and this is key--your code looks like you can just forget all that and make department a simple string.
If you have control over the data structure, I'd suggest reorganizing it:
var people = new List<Person> {
new Person { name = "John", department = "IT"},
new Person { name = "Sally", department = "IT"},
new Person { name = "Bob", department = "Finance"},
new Person { name = "Wanda", department = "Finance"},
};
Suddenly, grouping all that becomes simple:
var departments = from p in people
group p by p.department into dept
select dept;
foreach (var dept in departments)
{
Console.WriteLine("Department: {0}", dept.Key);
foreach (var person in dept)
{
Console.WriteLine("Person: {0}", person.name);
}
}
If you must leave the data structure as it is, you could try this:
from p in people
from field in p.department
where field.name equals "department"
group p by field.value into dept
select dept;
That should work with the above nested loop.
The list of persons for each department can be accessed via department.Name. Simply iterate over it:
foreach( var person in department.Name ) Console.WriteLine( person.name );
The value of department.Department, on the other hand, is of type string. This value comes from departments.Key, which in turn comes from field.value - because that's the key that you group by.
The foreach statement over department.Department still compiles fine, because string implements IEnumerable<char>. Consequently, your foo variable is of type char.