LINQ GroupBy List<> - c#

I have a straightforward LINQ query that is attempting to perform a GroupBy where one of the items in the statements is a List<string>.
var viewModel = reports
.GroupBy(c => new { c.Id, c.PetList })
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Prior to this statement I am executing my EF repository method which ultimately calls a method to create the PetList above.
If I remove the PetList from the GroupBy() it works as expected. Is there something I must do in order to group by a List<string> type?

I would assume that Id is an identifier, and hence any two c with the same Id is in fact the same and has the same PetList. As such we can GroupBy just the Id and get the PetList another way:
var viewModel = reports
.GroupBy(c => c.Id)
.Select(g => new ArmReportModel
{
PetList = g.First().PetList, // Might need FirstOrDefault() with some providers
Pets = g.Count()
});
Barring that, I'd want to first make sure I could use an IEqualityComparer<T> with the GroupBy. If the provider allows for that, then no problem. Otherwise I'd start with:
reports.Select(c => new {c.Id, c.PetList}).AsEnumerable()
This retrieves the minimum necessary from the provider into memory, so that the linq-to-objects provider can be used from that point on.
I need to be able to define an IEqualityComparer<T> for some T, so I stop using anonymous types:
private class IdAndList
{
public int Id { get; set; }
public List<string> PetList { get; set; }
}
private class ReportIdAndPetListComparer : IEqualityComparer<IdAndList>
{
public bool Equals(IdAndList x, IdAndList y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if (x.Id != y.Id) return false;
if (x.PetList == null) return y.PetList == null;
if (y.PetList == null) return false;
int count = x.PetList.Count;
if (y.PetList.Count != count) return false;
for (int i = 0; i != count; ++i)
if (x.PetList[i] != y.PetList[i]) return false;
return true;
}
public int GetHashCode(IdAndList obj)
{
int hash = obj.Id;
if (obj.PetList != null)
foreach (string pet in obj.PetList)
hash = hash * 31 + pet.GetHashCode();
return hash;
}
}
Some of the tests for null PetLists can be removed if you know that's not possible.
Now:
var viewModel = reports.Select(c => new IdAndList{c.Id, c.PetList}).AsEnumerable()
.GroupBy(c => c, new ReportIdAndPetListComparer())
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Or if the provider can't deal with constructing the IdAndPetList type, then:
var viewModel = reports.Select(c => new {c.Id, c.PetList})
.AsEnumerable()
.Select(c => new IdAndList{c.Id, c.PetList})
.GroupBy(c => c, new ReportIdAndPetListComparer())
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});

Related

Isolating where clause in LINQ C#

I have the following code
RoseFlowersData = (from c in dbContext.ClientFirst
where c.RoseFlower.Id == Id
select new FlowerDTO
{
flowerId = c.Id,
flowerCatergory = c.flowerCatergory
}),
AsterFlowerData = (from c in dbContext.ClientFirst
where c.AsterFlower.Id == Id
select new FlowerDTO
{
flowerId = c.Id,
flowerCatergory = c.flowerCatergory
}),
in both, the only difference is the where clause (where c.RoseFlower.Id == Id; where c.AsterFlower.Id == Id)
I want to use following function to get me data based on different flower type (like RoseFlower, AsterFlower etc)
private IQueryable<FlowerDTO> GetFlowerData(int flowerId, <What should I pass here?>)
{
var data = (from c in dbContext.ClientFirst
where c.RoseFlower.Id == flowerId
select new FlowerDTO
{
flowerId = c.Id,
flowerCatergory = c.flowerCatergory
});
return data;
}
I am confused on how I can use this function for both and further flower types. I have tried looking for solutions to isolate the where clause but after hours of search, I have not been able to find a solution. Maybe I am not searching for the right thing.
Thank you for your time and help
If you convert your LINQ to a fluid syntax using the LINQ extension methods instead:
private IQueryable<FlowerDTO> GetFlowerData(Predicate<Flower> where)
{
var data = dbContext.ClientFirst
.Where(where)
.Select(c => new FlowerDTO
{
flowerId = c.Id,
flowerCatergory = c.flowerCatergory
});
return data;
}
Then call your method:
GetFlowerData(f => f.Id == desiredFlowerId && f.FlowerType == "RoseFlower");
I agree with #Andrew H, but instead of passing Predicate<Flower> where as a parameter, I did passing only the Id and FlowerType, just to have less repetitions and better to maintenance:
private IQueryable<FlowerDTO> GetFlowerData(int id, string flowerType)
{
var data = dbContext.ClientFirst
.Where(f => f.Id == Id && f.FlowerType == flowerType)
.Select(c => new FlowerDTO
{
flowerId = c.Id,
flowerCatergory = c.flowerCatergory
});
return data;
}
and then:
GetFlowerData(1, "RoseFlower");
GetFlowerData(2, "AsterFlower");
Just ideas to you create your own function. Have fun.

Returning a LINQ database query from a Method

Hello everyone I have this query I am performing in multiple places. Instead of retyping the query over and over, I would like to be able to call a method that returns the query. I am not sure what to put as the return type for the method or if this is even possible to do. I use the query to write a csv file of the information, and I use the query to add items to my observable collection that is bound to a list view.
using (ProjectTrackingDBEntities context = new ProjectTrackingDBEntities())
{
var result = context.TimeEntries.Where(Entry => Entry.Date >= FilterProjectAfterDate
&& Entry.Date <= FilterProjectBeforerDate
&& (FilterProjectName != null ? Entry.ProjectName.Contains(FilterProjectName) : true))
.GroupBy(m => new { m.ProjectName, m.Phase })
.Join(context.Projects, m => new { m.Key.ProjectName, m.Key.Phase }, w => new { w.ProjectName, w.Phase }, (m, w) => new { te = m, proj = w })
.Select(m => new
{
Name = m.te.Key.ProjectName,
Phase = m.te.Key.Phase,
TimeWorked = m.te.Sum(w => w.TimeWorked),
ProposedCompletionDate = m.proj.ProposedCompletionDate,
ActualCompletionDate = m.proj.ActualCompletionDate,
Active = m.proj.Active,
StartDate = m.proj.StartDate,
Description = m.proj.Description,
EstimatedHours = m.proj.EstimatedHours
});
}
I am able to do both right now by retyping the query and performing the subsequent foreach() loops on the data. I would rather be able to do something like:
var ReturnedQuery = GetProjectsQuery();
foreach(var item in ReturnedQuery)
{
//do stuff
}
Any help would be appreciated.
You want to return IQueryable<T> with a known model that represents what it is you are returning. You should not return an anonymous type. Also you want to pass in the DbContext so it can be disposed of by the caller and not in the method otherwise you will receive an exception that the DbContext has been disposed of.
For example:
public IQueryable<ProjectModel> GetProjectQuery(ProjectTrackingDBEntities context) {
return context.TimeEntries.Where(Entry => Entry.Date >= FilterProjectAfterDate
&& Entry.Date <= FilterProjectBeforerDate
&& (FilterProjectName != null ? Entry.ProjectName.Contains(FilterProjectName) : true))
.GroupBy(m => new { m.ProjectName, m.Phase })
.Join(context.Projects, m => new { m.Key.ProjectName, m.Key.Phase }, w => new { w.ProjectName, w.Phase }, (m, w) => new { te = m, proj = w })
.Select(m => new ProjectModel
{
Name = m.te.Key.ProjectName,
Phase = m.te.Key.Phase,
TimeWorked = m.te.Sum(w => w.TimeWorked),
ProposedCompletionDate = m.proj.ProposedCompletionDate,
ActualCompletionDate = m.proj.ActualCompletionDate,
Active = m.proj.Active,
StartDate = m.proj.StartDate,
Description = m.proj.Description,
EstimatedHours = m.proj.EstimatedHours
});
}
ProjectModel.cs
public class ProjectModel {
public string Name {get;set;}
public string Phase {get;set;}
// rest of properties
}
Calling code
using (ProjectTrackingDBEntities context = new ProjectTrackingDBEntities())
{
var ReturnedQuery = GetProjectsQuery(context);
foreach(var item in ReturnedQuery)
{
//do stuff
}
}
It is easy to return the enumerator, but you can't return an enumerator for an anonymous type, unfortunately. Probably the easiest path forward for you would be to return enumerator over the full row object, like this:
public IEnumerable<TimeEntries> GetTimeEntries()
{
using (ProjectTrackingDBEntities context = new ProjectTrackingDBEntities())
{
return context.TimeEntries
.Where
(
Entry =>
Entry.Date >= FilterProjectAfterDate &&
Entry.Date <= FilterProjectBeforerDate &&
(FilterProjectName != null ? Entry.ProjectName.Contains(FilterProjectName) : true)
)
.GroupBy(m => new { m.ProjectName, m.Phase })
.Join
(
context.Projects,
m => new { m.Key.ProjectName, m.Key.Phase },
w => new { w.ProjectName, w.Phase },
(m, w) => new { te = m, proj = w }
);
}
)
}
And use it like this:
var query = GetTimeEntries();
foreach (var row in query.Select( m => new { Name = row.te.Key.ProjectName })
{
Console.WriteLine(row.Name);
}

Entity Framework building Where clause on the fly using Expression

Using Entity Framework C# and have this query, I need the part where it says:
where x.Login_Status == "Submitted"
to be dynamic. There are different cases it could be "Submitted" or null or something else and instead of writing multiple if statement with different queries in it, want to have a Predicate in a where clause.
status = (from x in ctx.table
where x.Login_Status == "Submitted"
orderby x.SUB_DATE descending
select new Model_Table()
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
Is that possible?
Solution:
Inside the if statement when more parameters encountered use this
where_expression = x => x.Login_Status == "Submitted" || x.Login_Status == null;
Here is a complete code that worked for me, anything between square brackets replace to suit your code:
Expression<Func<[Replace with your Entity], bool>> where_submitted = x => x.Login_Status == "Submitted";
// Check if all selected
if (CheckBox_Show_All_Submitted.Checked)
{
where_submitted = x => x.Login_Status == "Submitted" || x.Login_Status == null;
}
status =
ctx.[Replace with your Entity Table]
.Where(where_submitted)
.OrderByDescending(x => x.SUB_DATE)
.Select(x => new Model_Table
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
You need an Expression<Func<Entity,bool>>, not a Predicate<Entity>. The difference is that a predicate is a compiled delegate, and an expression is code as data and thus can be translated to SQL.
Here is an example:
//You can have this expression have different values based on your logic
Expression<Func<Entity,bool>> where_expression = x => x.Login_Status == "Submitted";
var query =
ctx.Table
.Where(where_expression)
.OrderByDescending(x => x.SUB_DATE)
.Select(x => new Model_Table())
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList();
Please note that you need to replace Entity with the name of the real class.
Create an extension method for IQueryable like this:
public static class MethodExtensions{
public static IEnumerable<Model_Table> Query(this IQueryable<TEntity> source, string data){
return (from x in source
where x.Login_Status == data
orderby x.SUB_DATE descending
select new Model_Table()
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
}
}
Now you can use it like this:
var result = ctx.table.Query("somethingelse");

Lambda not equal on join

Table 1 called Category contains 70 records
Table 2 called FilterCategorys contains 0 records (currently).
my lambda join, I want to pull only records that don't match, so in this case I expect to get 70 records back. Here's my incorrect Lambda:
var filteredList = categorys
.Join(filterCategorys,
x => x.Id,
y => y.CategoryId,
(x, y) => new { catgeory = x, filter = y })
.Where(xy => xy.catgeory.Id != xy.filter.CategoryId)
.Select(xy => new Category()
{
Name = xy.catgeory.Name,
Id = xy.catgeory.Id,
ParentCategoryId = xy.catgeory.ParentCategoryId
})
.ToList();
Whats the correct syntax I need here?
Not sure if you have a requirement of using lambdas (rather than query syntax), but I prefer query syntax for statements that have outer joins.
This should be equivalent:
var filteredList = (
from c in Categorys
join fc in FilterCategorys on c.Id equals fc.CategoryId into outer
from o in outer.DefaultIfEmpty()
select new
{
Category = new Category
{
Name = c.Name,
Id = c.Id,
ParentCategoryId = c.ParentCategoryId
},
Exists = (o != null)
})
.Where(c => !c.Exists)
.Select(c => c.Category);
If you want to do it in purely lambda:
var match = categorys.Join(filterCategorys, x => x.Id, y => y.CategoryId, (x, y) => new { Id = x.Id });
var filteredList = categorys.Where(x => !match.Contains(new {Id = x.Id}));
I haven't measured the performance of this, but for 70 records, optimization is not an issue.
Well I came up with a solution that takes away the need for the join.
var currentIds = filterCategorys.Select(x => x.Id).ToList();
var filteredList = categorys.Where(x => !currentIds.Contains(x.Id));
very similar to #Zoff Dino answer, not sure about performance, maybe someone would like to check.
Try this:
var categories= ...
var filteredCategories=...
var allExceptFiltered = categories.Except(filteredCategories, new CategoryComparer()).ToList();
If you don't provide a custom Comparer that framework has no way of knowing that 2 Category objects are the same(even if they have the same ID),it just thinks that they are different objects (it checks for reference equality )
so you must add this class to your project:
public class CategoryComparer: IEqualityComparer<Category>
{
public bool Equals(Category x, Category y)
{
if (x == null && y == null)
return true;
if (x == null)
return false;
if (y == null)
return false;
return x.CategoryId.GetHashCode() == y.CategoryId.GetHashCode();
}
public int GetHashCode(Category obj)
{
return obj.CategoryId.GetHashCode();
}
}
update
Also check out Wyatt Earp's answer,it is very useful to know how to do an outer join
update 2
Your problem is the Join method.
The Where clause is "called" after the join.so after you have joined the listed based on the ID you select those which have different IDs,that's why you get no resuts
Could you draw bracket and it should work.
....Where(xy => (xy.catgeory.Id != xy.filter.CategoryId))

dynamically add IEnumerable<int?> on group by result set in c#

Can any suggest me to figure out this requirement.
var enableIds = context.items.Where(tble => tble.Date == null)
.GroupBy(a => a.Id)
.Select(g => new
{
Id = g.Key,
EId = g.Select(c => c.EId).Distinct()
});
For above query i'm getting below result set
"Id\":1,\"EId\":[1,2]
Now i've to add -1 value to EId dynamically like this ("EId\":[1,2,-1] )
You could use an Enumerable Concat extension:
var enableIds = context.items.Where(tble => tble.Date == null)
.GroupBy(a => a.Id)
.Select(g => new
{
Id = g.Key,
EId = g.Select(c => c.EId).Distinct()).ToList().Concat(new[] { -1 })
});
I would try something like this using a helper method Concat<T>. The helper method could be implemented as an extension method for the IEnumerable<T>.
var enableIds = _db.items.Where(tble => tble.Date == null)
.GroupBy(a => a.Id)
.Select(g => new
{
Id = g.Key,
EId = Concat(g.Select(c => c.EId).Distinct(), -1)
});
public static IEnumerable<T> Concat<T>(IEnumerable<T> enumerable, params T[] enumerable2)
{
if (enumerable == null) throw new ArgumentNullException("enumerable");
if (enumerable2 == null) throw new ArgumentNullException("enumerable2");
foreach (T t in enumerable)
{
yield return t;
}
foreach (T t in enumerable2)
{
yield return t;
}
}

Categories