LINQ get columns in result by which query was grouped by - c#

I have a problem with getting grouped columns in LINQ.
My class:
public class DTO_CAORAS
{
public int? iORAS_KEY_CON { get; set; }
public int? iMERC_KEY {get;set;}
public double? decD_ORAS_QUA {get;set;}
}
LINQ query:
var results =
from oras in listCAORAS_Delivered
group oras by new
{
oras.iORAS_KEY_CON,
oras.iMERC_KEY
}
into orasGroup
select new
{
decD_ORAS_QUA = orasGroup.Sum(x => x.decD_ORAS_QUA)
};
List results is filled only with one column - decD_ORAS_QUA. I don't know how to get columns, by which query is grouped - IORAS_KEY_CON and iMERC_KEY? I would like to fill results with iORAS_KEY_CON, iMERC_KEY and decD_ORAS_QUA.
Input data:
+---------------+-----------+---------------+
| iORAC_KEY_CON | iMERC_Key | decD_ORAS_QUA |
+---------------+-----------+---------------+
| 1 | 888 | 1 |
| 1 | 888 | 2 |
| 1 | 888 | 4 |
+---------------+-----------+---------------+
Desired output:
+---------------+-----------+---------------+
| iORAC_KEY_CON | iMERC_Key | decD_ORAS_QUA |
+---------------+-----------+---------------+
| 1 | 888 | 7 |
+---------------+-----------+---------------+

To also show the keys:
var results = from oras in listCAORAS_Delivered
group oras by new { oras.iORAS_KEY_CON, oras.iMERC_KEY } into g
select new DTO_CAORAS {
iORAS_KEY_CON = g.Key.iORAS_KEY_CON,
iMERC_KEY = g.Key.iMERC_KEY,
decD_ORAS_QUA = g.Sum(x => x.decD_ORAS_QUA)
};
As you are only grouping one column you can also:
var results = from oras in listCAORAS_Delivered
group oras.decD_ORAS_QUA by new { oras.iORAS_KEY_CON, oras.iMERC_KEY } into g
select new DTO_CAORAS {
iORAS_KEY_CON = g.Key.iORAS_KEY_CON,
iMERC_KEY = g.Key.iMERC_KEY,
decD_ORAS_QUA = g.Sum()
};

Related

How to concatenate multiple rows into a single row in ASP.NET MVC EF

I have 3 tables:
Recipe
RecipeIngredient (fk RecipeId)
RecipeTag (fk RecipeId)
Recipe
+-----+------------+-------------+------+
| Id | Name | Ingredients | Tags |
+-----+------------+-------------+------+
| 99 | Mango Sago | | |
| 100 | Tuna Melt | | |
+-----+------------+-------------+------+
RecipeIngredient
+-----+----------+------------+---------------------------------------------+----------+
| Id | Quantity | UOM | Name | RecipeId |
+-----+----------+------------+---------------------------------------------+----------+
| 115 | 2 | Pieces | Whole Ripe Mangoes | 99 |
| 116 | 1 | Pieces | Jolly Coconut Milk, 400ml | 99 |
| 117 | 2 | Tablespoon | Sugar | 99 |
| 118 | 1 | Cup | Cooked Tapioca Pearls | 99 |
| 119 | NULL | NULL | Mango Cubes | 99 |
| 120 | 1 | Pieces | Doña Elena 100% Tuna Shredded 185g, drained | 100 |
| 121 | 2 | Tablespoon | White Onion, chopped | 100 |
| 122 | 2 | Tablespoon | Jolly Real Mayonnaise | 100 |
| 123 | 1 | Tablespoon | Celery or Pickle Relish, finely chopped | 100 |
| 124 | 8 | Pieces | White Bread | 100 |
| 125 | 4 | Pieces | Cheddar or Mozzarella Cheese | 100 |
+-----+----------+------------+---------------------------------------------+----------+
RecipeTag
+----+-----------------+----------+
| Id | Name | RecipeId |
+----+-----------------+----------+
| 72 | Filipino Desert | 99 |
| 73 | Quick Recipe | 99 |
| 74 | Quick Recipe | 100 |
+----+-----------------+----------+
How do I add all the RecipeIngredient to the Ingredients column in the Recipe table and add all the RecipeTag to the Tags column in the Recipe table in the controller?
public JsonResult GetAllRecipes()
{
var recipes = db.Recipes.OrderBy(a => a.Name).ToList();
return new JsonResult { Data = recipes, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
public JsonResult GetAllRecipes()
{
var recipes = (from rec db.Recipes
join ing in db.Ingredients on rec.Id equals ing.RecipeId into subIngrs
from subIngr in subIngrs.DefaultIfEmpty()
join tag in db.RecipeTags on rec.Id equals tag.RecipeId into subTags
from subTag in subTags.DefaultIfEmpty()
order by rec.Name
select new
{
rec.Id,
rec.Name,
Quantity = subIngr == null ? null : subIngr.Quantity,
IngrName = subIngr == null ? null : subIngr.Name,
UOM = subIngr == null ? null : subIngr.UOM,
TagName = subTag == null ? null : subTag.Name
}).ToList()
.GroupBy(x => new { x.Id, x.Name }).Select(x => new
{
x.Key.Id,
x.Key.Name,
Ingredients = string.Join("," x.Where(y => y.IngrName != null).Select(y => $"{y.Quantity} {y.UOM} {y.Name}").Distinct()),
Tags = string.Join("," x.Where(y => y.TagName != null).Select(y => y.TagName).Distinct())
}).ToList();
return new JsonResult { Data = recipes, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
You can use STUFF if you want to do it in sql.
SELECT a.Id,
Recipe = a.Name,
Ingredient = STUFF((
SELECT ','
+ CAST(b.Quantity AS NVARCHAR(10)) + ' '
+ b.UOM + ' '
+ b.Name
FROM dbo.RecipeIngredient b
WHERE a.Id = b.RecipeId
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''),
Tag = STUFF((
SELECT ',' + c.Name
FROM dbo.RecipeTag c
WHERE a.ID = c.RecipeId
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM Recipe a
Here's a Demo.
To execute the stored procedure and return the results as a json in your controller method
var recipies = db.Database.SqlQuery<RecipeVM>("NameOfStoredProcedure");
return Json(recipes, JsonRequestBehavior = JsonRequestBehavior.AllowGet);
where RecipeVM is
public class RecipeVM
{
public int Id { get; set; }
public string Name { get; set; }
public string Ingredient { get; set; }
public string Tag { get; set; }
}
Assuming you have set up your navigation properties correctly (i.e. Recipe contains public virtual ICollection<RecipieIngredient> Ingredients { get; set; } etc) then to get the data in the concatenated format you want,
public JsonResult GetAllRecipes()
{
var recipes = db.Recipes
.OrderBy(r => r.Name)
.ToList() // this is necessary because we need Linq to Objects for the string formatting
.Select(r => new // can be anonymous objects because we are returning a JsonResult
{
Id = r.Id,
Name = r.Name,
Ingredients = r.Ingredients
.Select(i => string.Format("{0} {1} {2}", i.Quantity, i.UOM, i.Name).TrimStart())
.Aggregate((c, n) => c + ", " + n),
Tags = r.Tags
.Select(t => t.Name)
.Aggregate((c, n) => c + ", " + n)
});
return Json(recipes, JsonRequestBehavior = JsonRequestBehavior.AllowGet);
}
Side note, your RecipeIngredient table indicates nullable values for Quantity and UOM hence the TrimStart. I am assuming that if either Quantity or UOM is null, then the other is also null
Alternatively, (in LINQ to SQL) you can use
var recipes = (from r in db.Recipies
join i in db.RecipeIngredient on r.Id equals i.RecipeId into Ingedients
join t in db.RecipeTag on r.Id equals t.RecipeId into Tags
orderby r.Name
select new
{
Name = r.Name,
Ingedients = Ingedients,
Tags = Tags
}).ToList()
.Select(x => new
{
Name = x.Name,
Ingredients = x.Ingedients
.Select(y => string.Format("{0} {1} {2}", y.Quantity, y.UOM, y.Name).Trim())
.Aggregate((c, n) => c + ", " + n),
Tags = x.Tags
.Select(y => y.Name)
.Aggregate((c, n) => c + ", " + n)
});
return Json(recipes, JsonRequestBehavior = JsonRequestBehavior.AllowGet);
I'm not exactly sure what you're trying to accomplish, but if you're trying to programatically insert this data from user input, you could just iterate over the ingredients/tags and build a string to insert into the relevant tables.
If you're trying to do this from existing data, then I'm not sure EF is the tool to use. I would just write SQL scripts to handle that.

Grouping and aggregating using linq

Hi I have a List below that needs to be grouped and aggregated using Linq method syntax.
| id |Code|Descr | Number | Expiry |
|---------|----|------|--------|-----------|
| guidId1 | A | Desc1| Number1| 2017-03-18|
| guidId2 | A | Desc1| Number1| 2017-03-18|
| guidId3 | B | Desc2| Number1| 2017-03-18|
| guidId4 | B | Desc2| Number1| 2017-03-18|
| guidId5 | C | Desc3| Number1| 2017-03-18|
| guidId6 | A | Desc1| Number2| 2020-05-20|
| guidId7 | A | Desc1| Number2| 2020-05-20|
| guidId8 | A | Desc1| Number2| 2020-05-20|
| guidId9 | B | Desc2| Number2| 2020-05-20|
| guidId10| C | Desc3| Number2| 2020-05-20|
| guidId11| C | Desc3| Number2| 2020-05-20|
I have tried this but am not sure how to include the count:
myList.GroupBy(s => new { s.Number, s.Code, s.Expiry});
The output I want from the list:
{Code = "A",Descr = "Desc1",Number = "Number1",Expiry = "2017-03-18", Count = 2}
{Code = "B",Descr = "Desc2",Number = "Number1",Expiry = "2017-03-18", Count = 2}
{Code = "C",Descr = "Desc3",Number = "Number1",Expiry = "2017-03-18", Count = 1}
{Code = "A",Descr = "Desc1",Number = "Number2",Expiry = "2020-05-20", Count = 3}
{Code = "B",Descr = "Desc2",Number = "Number2",Expiry = "2020-05-20", Count = 1}
{Code = "C",Descr = "Desc3",Number = "Number2",Expiry = "2020-05-20", Count = 2}
Thanks in advance
You've grouped the Number, Code, and Expiry, but your result needs the Descr as well. You need to include that in your group, then get the count of the groups.
var query =
from s in myList
group 1 by new { s.Code, s.Descr, s.Number, s.Expiry } into g
select new { g.Key.Code, g.Key.Descr, g.Key.Number, g.Key.Expiry, Count = g.Count() };

linq where list contains all matched in list

I have three tables Keyword, Product and KeywordProduct.
If I try to filter "the", return A and B products
If I try to filter "matrix", return only B product
but when I filter "the matrix", I get B and A too. I need to get only B record.
that is the code:
var keywordTermList = ("the matrix").Split(' ');
db.Products
.Where(product => product.ProductKeywords.All(k => keywordTermList.Contains(k.Keyword.Name)))
Keyword Table
+-----------+---------+
| KeywordId | Name |
+-----------+---------+
| 1 | awakens |
| 2 | force |
| 3 | the |
| 4 | matrix |
+-----------+---------+
ProductKeyword Table
+------------+-----------+
| KeywordId | ProductId |
+------------+-----------+
| 3(the) | A |
| 2(force) | A |
| 1(awakens) | A |
| 3(the) | B |
| 4(matrix) | B |
+------------+-----------+
Product Table has A and B records.
How would I go about this? How can I get only B when I filter "the matrix".
If you want the subset to fully match the surperset
var query = products.Where(p => !keywordListTerm.Except(p.ProductKeywords.Select(pk => pk.Name)).Any());
Here is full example
public class Program
{
public static void Main(string[] args)
{
Product A = new Product();
A.Name = "A";
A.ProductKeywords = new List<Keyword>() {
new Keyword(){Name = "the"},
new Keyword(){Name = "force"},
new Keyword(){Name = "awakens"}
};
Product B = new Product();
B.Name = "B";
B.ProductKeywords = new List<Keyword>() {
new Keyword(){Name = "the"},
new Keyword(){Name = "matrix"}
};
List<Product> products = new List<Product>() { A, B };
var keywordListTerm = ("the force matrix").Split(' ');
var query = products.Where(p => !keywordListTerm.Except(p.ProductKeywords.Select(pk => pk.Name)).Any());
foreach (var item in query) {
Console.WriteLine(item.Name);
}
Console.ReadKey();
}
}
public class Product {
public string Name = string.Empty;
public List<Keyword> ProductKeywords;
}
public class Keyword
{
public string Name;
}
Hope this helps.

Summing duplicate values while reading in data for different types of outputs

I am reading in 5000 rows of data from a stream as follows from top to bottom and store it in a new CSV file.
ProductCode |Name | Type | Size | Price
ABC | Shoe | Trainers | 3 | 3.99
ABC | Shoe | Trainers | 3 | 4.99
ABC | Shoe | Trainers | 4 | 5.99
ABC | Shoe | Heels | 4 | 3.99
ABC | Shoe | Heels | 5 | 4.99
ABC | Shoe | Heels | 3 | 5.99
...
Instead of having duplicate entries, I want the CSV to have one row but with the Price summed:
E.g. If I want a csv file with only ProductCode, Name and Type, ignoring the Size. I want it too look like this:
ProductCode |Name | Type | Price
ABC | Shoe | Trainers | 14.97
ABC | Shoe | Heels | 14.97
Show only ProductCode, Name:
ProductCode |Name | Price
ABC | Shoe | 29.94
Show ProductCode, Name, Size, ignoring Type:
ProductCode |Name | Type | Size | Price
ABC | Shoe | 3 | 14.97
ABC | Shoe | 4 | 9.98
ABC | Shoe | 5 | 4.99
I store each row with all fields as a Product and keep a list of all Products:
public class Product
{
public string ProductCode { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Price { get; set; }
}
And then output the needed fields into the csv depending on the csvOutputType using ConvertToOutputFormat which is different for each Parser.
public class CodeNameParser : Parser {
public override string ConvertToOutputFormat(Product p) {
return string.Format("{0},{1},{2}", p.ProductCode, p.ProductName, p.Price);
}
}
My code is then:
string fileName = Path.Combine(directory, string.Format("{0}.csv", name));
switch (csvOutputType)
{
case (int)CodeName:
_parser = new CodeNameParser();
break;
case (int)CodeType:
_parser = new CodeTypeParser();
break;
case (int)CodeNameType:
_parser = new CodeNameTypeParser();
break;
}
var results = Parse(stream).ToList(); //Parse returns IEnumerable<Product>
if (results.Any())
{
using (var streamWriter = File.CreateText(fileName))
{
//writes the header line out
streamWriter.WriteLine("{0},{1}", header, name);
results.ForEach(p => { streamWriter.WriteLine(_parser .ConvertToOutputFormat(p)); });
streamWriter.Flush();
streamWriter.Close();
}
Optional<string> newFileName = Optional.Of(SharpZipWrapper.ZipFile(fileName, RepositoryDirectory));
//cleanup
File.Delete(fileName);
return newFileName;
}
I don't want to go through the 5000 rows again to remove the duplicates but would like to check if the entry already exists before I add it to the csv file. I know that I can groupBy the required fields, but since I have 3 different outputs, I would have to write the same code 3 times for different keys that I need to group by.
results = results
.GroupBy(p => new { p.ProductCode, p.Name, p.Type })
.Select(g => new Product {
ProductCode = g.Key.ProductCode,
Name = g.Key.Name,
Type = g.Key.Type,
Price = g.Sum(p => p.Price)
})
.ToList();
Is there any other way to do this?

Join with count and multiple conditions - LINQ C#

I have a property database and I am trying to get all properties added by an user. The main table is called 'Property' and there are other tables which are 'PropertyPhotos', 'City' etc. A sample database is as follows:
'Property' table
PropertyId| Area| State| UserId | ...
1 | 1 | 1 | AAA | ...
2 | 2 | 3 | BBB | ...
3 | 1 | 1 | AAA | ...
'PropertyPhotos'
PropertyPhotoId| PropertyId| FileName | MainPic
1 | 1 | x1.jpg | 1
2 | 1 | X2.jpg | 0
3 | 2 | x3.jpg | 1
4 | 3 | x4.jpg | 1
5 | 3 | x5.jpg | 0
6 | 3 | x6.jpg | 0
'AreaLookUp'
AreaLookUpId | AreaDescription
1 | London
2 | Birmingham
3 | Manchester
I am trying to write a LINQ query to get information on property added by a particular user. But I am stuck when trying to retrieve the 'FileName' of the MainPic and also get count. See code below with comments.
So, for the data above, this query should return the following for "UserId = AAA"
PropertyId | ... | MainPicSrc | PhotoCount
1 | ... | x1.jpg | 2
3 | ... | xr4jpg | 3
Please help!
public IEnumerable<PropertyExcerptViewModel> GetAddedPropertyVmByUserId(string userId)
{
var addedProperties = from p in db.Property where p.UserId == userId
join pp in db.PropertyPhotos on p.PropertyId equals pp.PropertyId
join a in db.AreaLookUp on p.Area equals a.AreaLookUpId
select new PropertyExcerptViewModel
{
PropertyId = p.PropertyId,
PropertyType = p.PropertyType,
TransactionType = p.TransactionType,
IsPropertyDisabled = p.IsPropertyDisabled,
IsPropertyVerified = p.IsPropertyVerified,
IsPropertyNotified = p.IsPropertyNotified,
MainPicSrc = pp.FileName, // How to put where condition to only get FileName of just the Main Pic
PhotoCount = pp.Count(), // How to get count of all pics with a particular proprtyId
Price = p.Price,
NoOfBedrooms = p.NoOfBedrooms,
Area = a.AreaLookUpDescription,
ShortDescription = (p.Description.Length > 300) ? p.Description.Substring(0,300) : p.Description
};
return addedProperties.ToList();
}
I think where statement might be easier if you care about clear match
var data=(from c in db.Property from v in db.PropertyPhotos from
n in db.AreaLookUpId
where c.PropertyId==v.PropertyId && c.Area==n.AreaLookUpId && c.UserId=="AAA"
// the rest is your select
PhotoCount = v.Where(j=>j. PropertyId==c.PropertyId).Count()
This also works - I ended up doing it this way
var addedProperties = from p in db.Property
join ppic in db.PropertyPhotos on p.PropertyId equals ppic.PropertyId into pp
join a in db.AreaLookUp on p.Area equals a.AreaLookUpId
join cal in db.CalendarEvent on p.PropertyId equals cal.PropertyId into c
where p.UserId == userId
select new PropertyExcerptViewModel
{
PropertyId = p.PropertyId,
PropertyType = p.PropertyType,
PropertyCategoryDescription = pc.PropertyCategoryDescription,
TransactionType = p.TransactionType,
IsPropertyDisabled = p.IsPropertyDisabled,
IsPropertyVerified = p.IsPropertyVerified,
IsPropertyNotified = p.IsPropertyNotified,
MainPicSrc = pp.Where(e => e.MainPic == true).FirstOrDefault().PhotoLocation,
PhotosCount = pp.Count(),
Price = p.Price,
NoOfBedrooms = p.NoOfBedrooms,
Area = a.AreaLookUpDescription,
ShortDescription = (p.Description.Length > 300) ? p.Description.Substring(0, 300) : p.Description,
LatestCalendarEvent = c.OrderByDescending(e => e.DateSaved).FirstOrDefault()
};
return addedProperties.ToList();

Categories