Group by with maximum - linq

I want to group by category, show it's name, then show the highest id that is related to it. Here's some data and the result that I want further down. Any ideas? I've been playing around with GroupJoin but can't seem to get it to work.
My Data
var stuff = new[] {
new {id = 5, catId = 2},
new {id = 56, catId = 2},
new {id = 56, catId = 2},
new {id = 8, catId = 1},
new {id = 9, catId = 3}};
var categories = new[] {
new {catId = 1, Name = "Water"},
new {catId = 4, Name = "Wind"},
new {catId = 2, Name = "Fire"}};
What I want my results to look like
Water - 8
Wind - null
Fire - 56

categories
.GroupJoin
(
stuff,
c=>c.catId,
s=>s.catId,
(c,s)=>new
{
c.Name,
Max = s.Any() ? (int?)s.Max (m => m.id) : null
}
);

It seems that you want a "LEFT OUTER JOIN" with LINQ:
var query = from cat in categories
join s in stuff
on cat.catId equals s.catId into gj
from stuffJoin in gj.DefaultIfEmpty()
group stuffJoin by new { cat.catId, cat.Name } into catGroup
select new {
Category = catGroup.Key.Name,
MaxID = catGroup.Max(s => s == null ? 0 : s.id) // stuff is null for Wind
};
foreach (var x in query)
Console.WriteLine("Category: {0} Max-ID: {1}", x.Category, x.MaxID);
Outputs:
Category: Water Max-ID: 8
Category: Wind Max-ID: 0
Category: Fire Max-ID: 56

Related

Linq join on a case

I would like to call my join depending on the ProductType.
var stocks = (from st in _context.StockDetails
join p in _context.ProductType1 on p.ProductId equals p.Id
select new StockList
{
ProductId = st.ProductId,
ProductType = st.ProductType,
StockValue = st.StockValue,
InOut = st.InOut,
ProductName = p.ProductName
});
In that case, st.ProductType = ProductType1
But I have also different tables for ProductType2, ProductType3... And want to join the adequate one.
These tables are not identical. They have their specific column and also some common ones. Table ProductType1 for books, ProductType2 for gardening tools,... Of course ProductName is a common column to all (and a few others like weight,...) But I have decided to split these products in different tables and here I am!!!
This process is not at the end.
Strategically, would it be better practice to have them in the same table with specific name fields like String1, String2, Int1, Int2,.... and the common column with proper names like ProductName, Weight,...
Thanks for your help.
According to your design I do not see many options. You have to choose which is better for you
SOLUTION 1
var stocks1 = from st in _context.StockDetails
join p in _context.ProductType1 on p.ProductId equals p.Id
where st.ProductType == 1
select new StockList
{
ProductId = st.ProductId,
ProductType = st.ProductType,
StockValue = st.StockValue,
InOut = st.InOut,
ProductName = p.ProductName
};
var stocks2 = from st in _context.StockDetails
join p in _context.ProductType2 on p.ProductId equals p.Id
where st.ProductType == 2
select new StockList
{
ProductId = st.ProductId,
ProductType = st.ProductType,
StockValue = st.StockValue,
InOut = st.InOut,
ProductName = p.ProductName
};
var stocks3 = from st in _context.StockDetails
join p in _context.ProductType3 on p.ProductId equals p.Id
where st.ProductType == 3
select new StockList
{
ProductId = st.ProductId,
ProductType = st.ProductType,
StockValue = st.StockValue,
InOut = st.InOut,
ProductName = p.ProductName
};
...
var stocks = stocks1.Concat(stocks2).Concat(stocks3);
SOLUTION 2
var products =
_context.ProductType1.Select(p => new { ProductType = 1, p.ProductId, p.ProductName })
.Concat(context.ProductType2.Select(p => new { ProductType = 2, p.ProductId, p.ProductName }))
.Concat(context.ProductType3.Select(p => new { ProductType = 3, p.ProductId, p.ProductName }));
var stocks = from st in _context.StockDetails
join p in products on new { st.ProductType, st.ProductId } equals new { p.ProductType, p.ProductId }
select new StockList
{
ProductId = st.ProductId,
ProductType = st.ProductType,
StockValue = st.StockValue,
InOut = st.InOut,
ProductName = p.ProductName
};
SOLUTION 3
var stocks = from st in _context.StockDetails
join p1 in _context.ProductType1 on new { st.ProductType, st.ProductId } equals new { ProductType = 1, p1.ProductId } into j
from p1 in j.DefaultIfEmpty()
join p2 in _context.ProductType1 on new { st.ProductType, st.ProductId } equals new { ProductType = 2, p2.ProductId } into j
from p2 in j.DefaultIfEmpty()
join p3 in _context.ProductType3 on new { st.ProductType, st.ProductId } equals new { ProductType = 3, p3.ProductId } into j
from p3 in j.DefaultIfEmpty()
select new StockList
{
ProductId = st.ProductId,
ProductType = st.ProductType,
StockValue = st.StockValue,
InOut = st.InOut,
ProductName = p1.ProductName ?? p2.ProductName ?? p3.ProductName
};

GroupBy using LINQ but base distinct on another column, and still be able to count other records

How should I run a GroupBy based on Id using LINQ when there is an object similar to following:
public class foo
{
public int id { get; set; }
public string name { get; set; }
public string lang { get; set; }
public int displayOrder { get; set; }
public int count { get; set; }
}
The list could be:
id = 1, name="test1", lang = "en", displayOrder = 1, count = 1
id = 1, name="test2", lang = "fr", displayOrder = 2, count = 2
id = 1, name="test3", lang = "de", displayOrder = 3, count = 1
id = 2, name="test4", lang = "en", displayOrder = 2, count = 1
id = 2, name="test5", lang = "fr", displayOrder = 3, count = 1
id = 3, name="test6", lang = "en", displayOrder = 6, count = 1
id = 3, name="test7", lang = "fr", displayOrder = 4, count = 1
id = 4, name="test8", lang = "en", displayOrder = 5, count = 1
id = 5, name="test9", lang = "de", displayOrder = 6, count = 1
I want to run LINQ so that it Groups By Id values, but the distinct id values should be filtered based on lang e.g. "fr", if nothing is available in "fr" it should output only default language record for "en"
but should also Count the total number of records based on Id, it should retrieve following results for above:
id = 1, name="test2", lang = "fr", displayOrder = 2, count = 4
id = 2, name="test5", lang = "fr", displayOrder = 3, count = 2
id = 3, name="test7", lang = "fr", displayOrder = 4, count = 2
id = 4, name="test8", lang = "en", displayOrder = 5, count = 1
id = 5, name="test9", lang = "de", displayOrder = 6, count = 1
Please, is there a way to do something like this using LINQ ?
All of you LINQ experts, I'm ideally looking for query using lambda, this would be a great help. Thanks in advance.
You can sort the target language to the front, and select the first item in a group:
var query = from f in foos
group f by f.id into g
let lang = (from f in g
orderby
f.lang == "fr" ? 0 : 1,
f.lang == "en" ? 0 : 1,
f.lang
select f).First()
select new
{
id = g.Key,
lang.name,
lang.lang,
lang.displayOrder,
count = g.Sum(f => f.count)
};
This assumes that pairs (id, lang) are unique.
Demo.
This may run slightly faster as it does not need to go through all items in the group for ordering.
var res = data
.GroupBy(d => d.id)
.Select(g => new {
id = g.Key,
count = g.Sum(d => d.count),
Lang = g.FirstOrDefault(d => d.lang == "fr") ?? g.First()
})
.Select(g => new {
g.id,
g.Lang.lang,
g.Lang.name,
g.Lang.displayOrder,
g.count
})
.ToList();

Bulk updates using linq entities

I have a list of object like
List
category has properties, CategoryId, CategoryName, Categorymessage,
I have to use this list to update records in the database table
Categories, which has the same columns CategoryId, CategoryName, CategoryMessage
for the items in list which have a matching CategoryId as in the database table
How can I do that as a bulk update, using Entity framework extensions,
the examples I saw have the update value hard coded like below, (statusId =2),
whereas for me that value has to be retrieved from the item in the list which matches the categoryId in the database table.
context.Tasks.Update(
t => t.StatusId == 1,
t => new Task {StatusId = 2});
Thanks in advance.
I am not sure whether this what you are looking for? I haven't run this code.
List<Category> categoryList = new List<Category> {
new Category { Id = 1, Message = "A", Name = "A"},
new Category { Id = 2, Message = "B", Name = "B"},
new Category { Id = 3, Message = "C", Name = "C"},
new Category { Id = 4, Message = "D", Name = "D"},
};
using (var container = new Model1Container())
{
IQueryable<Category> categoryQueryable = container.Categories.Where(x => categoryList.Any(t => t.Id == x.Id));
foreach (Category item in categoryQueryable)
{
var categorySource = categoryList.Where(x => x.Id == item.Id).Select(m => m).FirstOrDefault();
item.Message = categorySource.Message;
item.Name = categorySource.Message;
}
container.SaveChanges();
}

using mock data instead of EF for webapi unit test

I am trying to unit test a webapi project with zero joy.
The api is using EF6.1 to access data. My test project is using moq and xunit.
I have setup a basic test like
[Fact]
public void CheckGroupedDataCountEquals2()
{
var mockData = new List<Skill>()
{
new Skill() {Id = 1, Name = ".net"},
new Skill() {Id = 2, Name = "asp.net", ParentId = 1},
new Skill() {Id = 3, Name = "c#", ParentId = 1},
new Skill() {Id = 4, Name = "php"},
new Skill() {Id = 5, Name = "zend", ParentId = 4},
new Skill() {Id = 6, Name = "zend3", ParentId = 5}
};
// Arrange
_skillMock.Setup(x => x.GroupedSkills())
.Returns((new List<GroupedSkill>()));
var controller = new SkillsController(_skillMock.Object);
// Act
IHttpActionResult actionResult = controller.Get();
var contentResult = actionResult as OkNegotiatedContentResult<List<GroupedSkill>>;
// Assert
Assert.NotNull(contentResult);
var result = contentResult.Content as List<GroupedSkill>;
Assert.Equal(2,result.Count);
}
My apicontroller action looks like
[HttpGet]
[Route("")]
// GET api/<controller>
public IHttpActionResult Get()
{
var data = _skillRepository.GroupedSkills();
if (data!=null)
{
return Ok(data);
}
return NotFound();
}
And finally....my repo
public List<GroupedSkill> GroupedSkills()
{
using (var context = new DataContext())
{
var data = context.Skills.ToList();
var newList = data.Where(x => x.ParentId != null).ToList().GroupBy(x => x.ParentId,
(key, elements) =>
new
{
data.First(i => i.Id == key).Name,
GroupId = key,
Skills = elements.ToList()
})
.ToList();
var filtered = new List<GroupedSkill>();
foreach (var item in newList)
{
filtered.Add(new GroupedSkill()
{
GroupId = item.GroupId.Value,
Name = item.Name,
Skills = item.Skills
});
}
return filtered;
}
}
My test is always failing with the group count is always 0. I think its because I am not adding my mock list of mockData and my repository knows nothing about it.....am I right? how do I pass my mock list of data to the repo from my test
You are correct, you're passing it an empty list.
You first create the mockedData list, and populate it with real Skills, but it doesn't look like you ever use it. This should also be considered part of the Arrange section, for what that's worth.
var mockData = new List<Skill>()
{
new Skill() {Id = 1, Name = ".net"},
new Skill() {Id = 2, Name = "asp.net", ParentId = 1},
new Skill() {Id = 3, Name = "c#", ParentId = 1},
new Skill() {Id = 4, Name = "php"},
new Skill() {Id = 5, Name = "zend", ParentId = 4},
new Skill() {Id = 6, Name = "zend3", ParentId = 5}
};
Then when you begin your Arrange section by creating the _skillMock, but you creating a new, empty list.
// Arrange
_skillMock.Setup(x => x.GroupedSkills())
.Returns((new List<GroupedSkill>()));
And I can't tell where _skillMock is initialized; you shouldn't need it (and really don't want it) to be a class instance variable, because it could change across other tests. Instead, you should be setting this list up similar to how you built your mockData list.
You are properly passing the mock object in this line:
var controller = new SkillsController(_skillMock.Object);
However because you haven't added anything to that list, the list will always be empty.
Ideally, what I think you want is a real list filled with mocks of your GroupedSkill objects, something like this:
var mockData = new List<GroupedSkill>()
{
new Mock<GroupedSkill>(1, ".net").Object,
new Mock<GroupedSkill>(2, "asp.net", 1).Object,
// ... keep going
}
And then pass that list to your controller instead.

Using Linq to determine the missing combination of records

Let me start to explain with an example.
var vProducts = new[] {
new { Product = "A", Location ="Y", Month = "January", Demand = 50 },
new { Product = "A", Location ="Y", Month = "February", Demand = 100 },
new { Product = "A", Location ="Y", Month = "March", Demand = 20 },
new { Product = "A", Location ="Y", Month = "June", Demand = 10 }
};
var vPeriods = new[] {
new { Priority = 1, Month = "January" },
new { Priority = 2, Month = "February" },
new { Priority = 3, Month = "March" },
new { Priority = 4, Month = "April" },
new { Priority = 5, Month = "May" },
new { Priority = 6, Month = "June" }
};
var vAll = from p in vProducts
from t in vPeriods
select new
{
Product = p.Product,
Location = p.Location,
Period = t.Priority,
PeriodName = t.Month,
Demand = p.Demand
};
This above query will create all combinations of Products & Period. But, I need to get a list of all products along with the ones that do not have matching Month as shown below.
example
Product Location Priority Month Demand
A Y 1 January 50
A Y 2 February 100
A Y 3 March 20
A Y 4 April null
A Y 5 May null
A Y 6 June 10
Thanks for any comments.
You want to do a left outer join, it will go something like:
var res = from period in vPeriods
join product in vProducts on period.Month equals product.Month into bla
from p in bla.DefaultIfEmpty()
select new { period.Month, period.Priority, Product = p == null ? null : p.Product, Demand = p == null ? -1 : p.Demand };
foreach (var a in res)
{
Console.WriteLine(string.Format("{0} {1} {2}", a.Product, a.Month, a.Demand));
}
Of course that products that do not have matching months don't have locations etc. (as you stated in your example)

Resources