Extending a Select.... to accommodate more fields as extension using EF - linq

I have a class:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Salary { get; set; }
public string Address {get;set;}
}
And query using Entity Framework is:
var selectedEmployee = entities.Employees
.Where(e=>e.Salary>10000)
.Select(emp => new EmpDTO
{
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary
});
My question is:
I want to allow extending this query without rewriting the base query. It should allow adding a new field in the .Select(.....) by extending the above query.
Without rewriting the complete query:
var selectedEmployee = entities.Employees
.Where(e=>e.Salary>10000)
.Select(emp => new EmpDTO
{
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary,
Address = emp.Address
});
How can I do that?
Thanks

If I understand, you can try this:
public IQuerable<EmpDTO> GetEmployee(Func<Employee, EmpDTO> projection = null)
{
if(projection == null)
projection = emp => new EmpDTO {
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary,
};
return entities.Employees.Where(e => e.Salary > 10000).Select(projection);
}
Implementation:
var query = classInstance.GetEmployee();
//or
query = classInstance.GetEmployee(emp => new EmpDTO {
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary,
Address = emp.Address
});
If you always want to get some set of fields, like Id, Name and
Salary and sometimes take additional fields(and specify only their
as method arguments), you should to take all fields from DB and only
then filter them depends on your condition - it is bad practice to do
SELECT *, so you should get default set of fields or specify all desired fields mannualy.
Solution with SELECT *:
public List<EmpDTO> GetEmployee(Func<Employee, EmpDTO> projection)
{
var query = entities.Employees.Where(e => e.Salary > 10000).ToList().Select(x => {
var item = projection == null ? new EmpDTO() : projection(x);
item.Id = x.Id;
item.Name = x.Name;
item.Salary = x.Salary;
return item;
}).ToList();
}
At this case return value is List<T> not IQuerable<T>;
Implementation:
var items = classInstance.GetEmployee(emp => new EmpDTO { Address = emp.Address });
//items also will contain fields: Id, Name and Salary by default

Related

linq distinct with object selection

I have the following linq statement:
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floornr = x.floor,
appnr = x.roomnr
})
.Distinct()
.ToList();
Somehow this does not return distinct datasets. I assume it has something to do with the selection of the object? The distinct function is therefore not comparing the attributes directly but rather the objects? I am not understanding it fully unfortunately but in ms sql this statement works fine.
I also tried the following but it does not return a List object and I would need to use var or something else and I need a List of Consumer() objects.
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floornr = x.floor,
appnr = x.roomnr
})
.GroupBy(x => new { x.firstname, x.lastname, x.haus, x.etage, x.appnr })
.ToList();
You can do/try this:
public class Consumer
{
public string firstname { get; set; }
public string lastname { get; set; }
public string floor { get; set; }
public string house { get; set; }
}
List<Consumer> objConsumer = new List<Consumer>()
{
new Consumer(){ firstname="Govind", lastname="Sahu", house="298",floor="1st Floor"},
new Consumer(){ firstname="Govind", lastname="Sahu", house="298",floor="1st Floor"},
new Consumer(){ firstname="Govind1", lastname="Sahoo", house="297",floor="1st Floor"}
};
1st approach:
var obj = objConsumer.GroupBy(s => new {
s.firstname,
s.lastname,
s.house,
s.floor,
}).Select(o=>o.FirstOrDefault()).ToList();
2nd approach:
var obj1 = objConsumer.Select(s => new { firstname = s.firstname, lastname = s.lastname, house = s.house, floor = s.floor }).Distinct().ToList();
I found another solution by using DistinctBy from MoreLinq. It returns the right result when I use it like this
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floor = x.floor,
appnr = x.roomnr
})
.DistinctBy(x=> new
{
x.firstname,
x.lastname,
x.floor,
x.appnr,
x.house
})
.ToList();

SELECT result map to entity in Dynamic Linq in Entity Framework Core

I have a Linq query which is selecting 2 columns(that can be any 2 from all columns) dynamically based on some condition.I need to map the query result in to below model irrespective of selected column names
public class FinalModel
{
public string Text { get; set; }
public string Id { get; set; }
}
Currently I am using reflection to map the result in to that model because i am getting some anonymous list of objects and it is working fine, But I want to remove that reflection and need to add the mapping in the select itself, my current implementation is like below
string column1 = "Name" //can be other columns also
string column2 = "Age"
var result = _context.table1
.Select("new ("+ column1 +","+ column2 +")")
.Distinct()
.Take(10) // having more records in table
.ToDynamicList()
.Select(x => new FinalModel()
{
Id = x.GetType().GetProperty(column1).GetValue(x).ToString(),
Text = x.GetType().GetProperty(column2).GetValue(x).ToString(),
});
The above code is working fine but I need to remove the below section
.Select(x => new FinalModel()
{
Id = x.GetType().GetProperty(column1).GetValue(x).ToString(),
Text = x.GetType().GetProperty(column2).GetValue(x).ToString(),
});
Is there any way to remove the refletion and add that model mapping directly inside Select("new (column1,column2)")
Is there any way to add orderBy with Column2 variable?
You can use generic versions of Select and ToDynamicList and OrderBy($"{column2}") for sorting:
var result = _context.table1
.Select<FinalModel>($"new ({column1} as Id, {column2} as Text)")
.Distinct()
.OrderBy("Text")
.Take(10)
.ToDynamicList<FinalModel>();
Or if you want to stick with dynamic:
var result = _context.table1
.Select($"new ({column1}, {column2})")
.Distinct()
.OrderBy($"{column2}")
.Take(10)
.ToDynamicList()
.Select(d => new FinalModel()
{
Id = d[column1].ToString(),
Text = d[column2].ToString(),
})
.ToList();
You need to use .Select<T> instead of just .Select() to make sure that the selected entity the correct type. So in your case you need .Select<FinalModel>.
Use the as cast operator to "rename" the properties from the source-entity to the destination entity (FinalModel)
If you want the result to be typed, also use .ToDynamicList<FinalModel>().
Full example code below:
using System.Linq.Dynamic.Core;
class Program
{
static void Main(string[] args)
{
var myQuery = new[] { new XModel { Age = "1", Name = "n" } }.AsQueryable();
string column1 = "Name";
string column2 = "Age";
var result = myQuery
.Select<FinalModel>("new (" + column1 + " as Id, " + column2 + " as Text)")
.Distinct()
.Take(10)
.ToDynamicList<FinalModel>();
Console.WriteLine(result[0].Id + " " + result[0].Text);
}
public class XModel
{
public string Name { get; set; }
public string Age { get; set; }
}
public class FinalModel
{
public string Text { get; set; }
public string Id { get; set; }
}
}

How do I use LINQ group by clause to return unique employee rows?

I'm pretty new to LINQ, and I can't for the life of me figure this out. I've seen lots of posts on how to use the group by in LINQ, but for some reason, I can't get it to work. This is so easy in ADO.NET, but I'm trying to use LINQ. Here's what I have that is relevant to the problem. I have marked the part that doesn't work.
public class JoinResult
{
public int LocationID;
public int EmployeeID;
public string LastName;
public string FirstName;
public string Position;
public bool Active;
}
private IQueryable<JoinResult> JoinResultIQueryable;
public IList<JoinResult> JoinResultIList;
JoinResultIQueryable = (
from e in IDDSContext.Employee
join p in IDDSContext.Position on e.PositionID equals p.PositionID
join el in IDDSContext.EmployeeLocation on e.EmployeeID equals el.EmployeeID
where e.PositionID != 1 // Do not display the super administrator's data.
orderby e.LastName, e.FirstName
// ***** Edit: I forgot to add this line of code, which applies a filter
// ***** to the IQueryable. It is this filter (or others like it that I
// ***** have omitted) that causes the query to return multiple rows.
// ***** The EmployeeLocationsList contains multiple LocationIDs, hence
// ***** the duplicates employees that I need to get rid of.
JoinResultIQueryable = JoinResultIQueryable
.Where(e => EmployeeLocationsList.Contains(e.LocationID);
// *****
// ***** The following line of code is what I want to do, but it doesn't work.
// ***** I just want the above join to bring back unique employees with all the data.
// ***** Select Distinct is way too cumbersome, so I'm using group by.
group el by e.EmployeeID
select new JoinResult
{
LocationID = el.LocationID,
EmployeeID = e.EmployeeID,
LastName = e.LastName,
FirstName = e.FirstName,
Position = p.Position1,
Active = e.Active
})
.AsNoTracking();
JoinResultIList = await JoinResultIQueryable
.ToListAsync();
How do I get from the IQueryable to the IList only returning the unique employee rows?
***** Edit:
Here is my current output:
[4][4][Anderson (OH)][Amanda][Dentist][True]
[5][4][Anderson (OH)][Amanda][Dentist][True]
[4][25][Stevens (OH)][Sally][Dental Assistant][True]
[4][30][Becon (OH)][Brenda][Administrative Assistant][False]
[5][30][Becon (OH)][Brenda][Administrative Assistant][False]
Actually you do not need grouping here, but Distinct. Ordering before Distinct or grouping is useless. Also AsNoTracking with custom projection is not needed.
var query =
from e in IDDSContext.Employee
join p in IDDSContext.Position on e.PositionID equals p.PositionID
join el in IDDSContext.EmployeeLocation on e.EmployeeID equals el.EmployeeID
where e.PositionID != 1 // Do not display the super administrator's data.
select new JoinResult
{
LocationID = el.LocationID,
EmployeeID = e.EmployeeID,
LastName = e.LastName,
FirstName = e.FirstName,
Position = p.Position1,
Active = e.Active
};
query = query.Distinct().OrderBy(e => e.LastName).ThenBy(e => e.FirstName);
JoinResultIList = await query.ToListAsync();
The problem is that few employees have more than one location is causing the results to be repeated.You can handle it in multiple ways. Im using Let clause to tackle the issue in the below example
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
public int PositionID { get; set; }
}
public class EmployeeLocation
{
public int EmployeeID { get; set; }
public int LocationID { get; set; }
}
public class Position
{
public int PositionID { get; set; }
public string Position1 { get; set; }
}
public class Location
{
public int LocationID { get; set; }
}
public class JoinResult
{
//Suggestion : Insetad of LocationID there should be a varibale that has all the locations of an employee
public IEnumerable<int> LocationIDs;
public int LocationID;
public int EmployeeID;
public string LastName;
public string FirstName;
public string Position;
public bool Active;
}
//Setting up mock data
List<Position> positions = new List<Position>();
positions.Add(new Position() { Position1 = "Dentist", PositionID = 2 });
positions.Add(new Position() { Position1 = "Dental Assistant", PositionID = 3 });
positions.Add(new Position() { Position1 = "Administrative Assistant", PositionID = 4 });
List<Employee> employees = new List<Employee>();
employees.Add(new Employee() { EmployeeID = 4, FirstName = "Amanda", LastName = "Anderson (OH)", PositionID = 2 });
employees.Add(new Employee() { EmployeeID = 25, FirstName = "Sally", LastName = "Stevens (OH)", PositionID = 3 });
employees.Add(new Employee() { EmployeeID = 30, FirstName = "Brenda", LastName = "Becon (OH)", PositionID = 4 });
List<Location> locations = new List<Location>();
locations.Add(new Location() { LocationID = 4 });
locations.Add(new Location() { LocationID = 5 });
List<EmployeeLocation> employeeLocation = new List<EmployeeLocation>();
employeeLocation.Add(new EmployeeLocation() { LocationID = 4, EmployeeID = 4 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 5, EmployeeID = 4 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 4, EmployeeID = 25 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 4, EmployeeID = 30 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 5, EmployeeID = 30 });
var result = (from e in employees
join p in positions on e.PositionID equals p.PositionID
let employeeLocations = (from el in employeeLocation where el.EmployeeID == e.EmployeeID select new { LocationID = el.LocationID })
where e.PositionID != 1 // Do not display the super administrator's data.
orderby e.LastName, e.FirstName
select new JoinResult
{
LocationID = employeeLocations.Select(p=>p.LocationID).First()//Here its just selecting the first location,
LocationIDs = employeeLocations.Select(p=> p.LocationID),//This is my suggestion
EmployeeID = e.EmployeeID,
LastName = e.LastName,
FirstName = e.FirstName,
Position = p.Position1,
}).ToList();
Okay. So here is the solution I came up with. I installed the morelinq NuGet package, which contains a DistinctBy() method. Then I added that method to the last line of the code shown in my problem.
JoinResultIList = JoinResultIQueryable
.DistinctBy(jr => jr.EmployeeID)
.ToList();

LINQ group by and max clause usage

I have below Employee class which has 3 important fields
public class Employee
{
public string Name
{
get;
set;
}
public string Department
{
get;
set;
}
public double Salary
{
get;
set;
}
}
I have a list of such employees. I want to find out name employee from each department
whose salary is maximum in his/her department.
I Wrote below query but its not working.
List<Employee> list = new List<Employee>();
list.Add(new Employee { Name="Hemant",Salary=10,Department="PTS"});
list.Add(new Employee { Name = "Gunjan", Salary = 11, Department = "PTS" });
list.Add(new Employee { Name = "Akshay", Salary = 8, Department = "PTS" });
list.Add(new Employee { Name = "Omkar", Salary = 10, Department = "EBG" });
list.Add(new Employee { Name = "Hemant1", Salary = 14, Department = "EBG" });
var query1 = from e in list
group e by e.Department into g
select new { Dept = g.Key,MaxSal = g.Max((e) => e.Salary )};
foreach (var item in query1)
{
Console.WriteLine("Department : " + item.Dept + " : " + " Salary is : " + item.MaxSal);
}
But above piece of code is not working, I am not able to select employee name. I think I must use ascending/descending clause and select first or last record. But I am not able to figure it out. Can someone help.
Regards,
Hemant Shelar
Yes, you should OrderByDescending and then get First:
var query1 = list.GroupBy(x => x.Department)
.Select(g => g.OrderByDescending(x => x.Salary).First());
Below is console code:
foreach (var item in query1)
{
string output = string.Format("Deparment: {0}, Name: {1}, Max Salaray: {2}",
item.Department, item.Name, item.Salary
);
Console.WriteLine(output);
}

linq count/groupby not working

I want to group by the categoryid and then do a count on this. But I don't know how to do this. I have tried a couple of ways without success. Here is my latest:
public class Count
{
public int TradersCount { get; set; }
public int Id { get; set; }
public string Description { get; set; }
}
public IQueryable<Count> CountTradersAttachedToCategories()
{
var data = from tc in _db.tblTradersCategories
select new Count
{
Description = tc.tblCategory.description,
Id = tc.tblCategory.categoryId,
TradersCount = tc.Select(x => x.categoryid).GroupBy().Count()
};
return data;
}
tblTradersCategories joins both
tblTraders/tblCategories
A single trader can have many categories
A single category can have many traders
Thanks in advance for any help.
Clare
Try this:
var data = from tc in _db.tblTradersCategories
group tc by new { tc.tblCategory.categoryId,
tc.tblCategory.description } into g
select new { Count = g.Count(),
Id = g.Key.categoryId,
Description = g.Key.description };
If you want that in your Count class you may need to use AsEnumerable() to perform the conversion in process:
var converted = data.AsEnumerable()
.Select(c => new Count { TradersCount = c.Count,
Id = c.Id,
Description = c.Description });
You can try doing them all in one go:
var data = from tc in _db.tblTradersCategories
group tc by new { tc.tblCategory.categoryId,
tc.tblCategory.description } into g
select new Count { TradersCount = g.Count,()
Id = g.Key.categoryId,
Description = g.Key.description };
But I don't know if that will work. It depends on how the LINQ provider handles it.

Resources