linq where list contains all matched in list - c#

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.

Related

C# linq IQueryable multiple filtering

I cant find a way how to do a multiple filtering with LINQ.
what i want to do:
1.Filter the Version(Achieved)
2.Filter down the CompanyName(filtering/removing duplicates)
3.Get the Latest Timestamp
4.Then add all to a List.
Here is so far the code that i have written(which is not working).
public List<ReleaseStatistics> GetReleaseStatistics(IQueryable<InstallationInformation> input, IQueryable<DBContext.Version> mapping)
{
List<ReleaseStatistics> Releasestats = new List<ReleaseStatistics>();
foreach (var version in mapping)
{
IQueryable<InstallationInformation> QueryResult1 = input.Where(x => x.ProductVersion == version.VersionNumber);
IQueryable<InstallationInformation> QueryResult2 = QueryResult1.GroupBy(x => x.CompanyName).SelectMany(y => y);
List<InstallationInformation> ListofInstallationInformation = QueryResult2.ToList<InstallationInformation>();
if (ListofInstallationInformation.Count >= 1)
{
Releasestats.Add(new ReleaseStatistics
{
ReleaseVersion = version.ReleaseName,
CustomerCount = QueryResult1.Where(x => x.ProductVersion == version.VersionNumber).Count()
});
}
}
return Releasestats;
}
Addition information:
One of the problem is that there are duplicate and i want to Filter/remove them, but i want to get the latest timestamp of each CompanyName and then add it to the list.
the problem is that the line
IQueryable<InstallationInformation> QueryResult2 = QueryResult1.GroupBy(x => x.CompanyName).SelectMany(y => y);
actually does nothing.
Suppose QueryResult1 is
CompanyName | F1 | F2 |
CN1 | f1a | f2a |
CN1 | f1a | f2a |
CN2 | f1b | f2b |
then QueryResult1.GroupBy(x => x.CompanyName) is
Group | Data
CN1 | CompanyName | F1 | F2 |
CN1 | f1a | f2a |
CN1 | f1a | f2a |
CN2 | CompanyName | F1 | F2 |
CN2 | f1b | f2b |
then QueryResult1.GroupBy(x => x.CompanyName).SelectMany(y => y); is again
CompanyName | F1 | F2 |
CN1 | f1a | f2a |
CN1 | f1a | f2a |
CN2 | f1b | f2b |
what you want to do is probably
var QueryResult2 = QueryResult1.GroupBy(x => x.CompanyName).Select(y => new {CompanyName = y.Key, MaxTimestamp = y.Max(z => z.TimeStamp)});
I did it with classes to simulate your query
DBContext context = new DBContext();
List<InstallationInformation> input = new List<InstallationInformation>();
var query = (from m in context.mapping
join i in input on m.VersionNumber equals i.ProductVersion
select new { version = m.VersionNumber, companyName = i.CompanyName}
).ToList();
List<ReleaseStatistics> results = query.GroupBy(x => x.version).Select(x => new ReleaseStatistics() { ReleaseVersion = x.Key, CustomerCount = x.Distinct().Count() }).ToList();
}
}
public class DBContext
{
public List<Version> mapping { get; set; }
}
public class InstallationInformation
{
public int ProductVersion { get; set; }
public string CompanyName { get; set; }
}
public class Version
{
public int VersionNumber { get; set; }
}
public class ReleaseStatistics
{
public int ReleaseVersion { get; set; }
public int CustomerCount { get; set; }
}
HERE WAS MY END RESULT:
public List<ReleaseStatistics> GetReleaseStatistics(IQueryable<InstallationInformation> input, IQueryable<DBContext.Version> mapping)
{
List<ReleaseStatistics> Releasestats = new List<ReleaseStatistics>();
foreach (var version in mapping)
{
IQueryable<InstallationInformation> QueryResult1 = input.Where(x => x.ProductVersion == version.VersionNumber);
IQueryable<string> companynamecollection = QueryResult1.Select(x => x.CompanyName).Distinct();
List<InstallationInformation> listofAggregatedInstallationInformation = new List<InstallationInformation>();
foreach (var item in companynamecollection)
{
var maxdatetime = QueryResult1.Where(x => x.CompanyName == item).Select(x => x.Timestamp).Max();
IQueryable<InstallationInformation> listofresult = QueryResult1.Where(y => y.CompanyName == item && y.Timestamp == maxdatetime);
foreach (var item2 in listofresult)
{
listofAggregatedInstallationInformation.Add(new InstallationInformation
{
InstallationInformationID = item2.InstallationInformationID,
LicenceKey = item2.LicenceKey,
ProductName = item2.ProductName,
ProductVersion = item2.ProductVersion,
CompanyName = item2.CompanyName,
Timestamp = item2.Timestamp,
ImportRunID = item2.ImportRunID
});
}
}
if (listofAggregatedInstallationInformation.Count >= 1)
{
Releasestats.Add(new ReleaseStatistics
{
ReleaseVersion = version.ReleaseName,
CustomerCount = listofAggregatedInstallationInformation.Where(x => x.ProductVersion == version.VersionNumber).Count()
});
}
}
return Releasestats;
}

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.

LINQ get columns in result by which query was grouped by

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()
};

C# Combine two lists with nested lists into one using LINQ

I have two list of the following structure in c#. Each name in <> is a list. I want to join those two lists into one List<Server>. The following pseudo code displays two such lists and how the result should look like:
<Servers> <Servers>
+... +...
+-- Server A,1 +-- Server A,1
| +... | +...
| +--<Maps> | +--<Maps>
| +--Map x | +--Map x
| +... | +...
| +--<Times> | +--<Times>
| +--Time i | +--Time l
| +--Time j | +--<Jumps>
| +--<Jumps> | +--Jump t
| +--Jump s | +--Jump u
| +--Map y | +--Map z
| +... | +...
| +--<Times> | +--<Jumps>
| +-- Time k | +--Jump v
+-- Server B,1 +-- Server B,2
Result should be:
<Servers>
+...
+-- Server A,1
| +...
| +--<Maps>
| +-- Map x
| +...
| +--<Times>
| +--Time i
| +--Time j
| +--Time l
| +--<Jumps>
| +--Jump s
| +--Jump t
| +--Jump u
| +-- Map y
| +...
| +--<Times>
| +--Time k
| +-- Map z
| +...
| +--<Jumps>
| +--Jump v
+-- Server B,1
+-- Server B,2
I tried using full outer join with linq but the result was also not what I wanted, for reasons I didn't understand servers that had the same keys weren't matched and so I always had duplicates of the same server with different data. That's when I stopped trying to do it with linq and just used loops to merge the lists by hand.
The code below gives me the desired result list. For now I will use it until I find a better way to do it. Is there a simple/shorter way doing that with linq? Using lambda expressions?
foreach (Server importServer in oImportList)
{
if (!CoreList.Contains(importServer))
{
CoreList.Add(importServer);
continue;
}
Server coreServer = CoreList.FirstOrDefault(o => o.Equals(importServer));
coreServer.Name = importServer.Name;
coreServer.KZTimerVersion = importServer.KZTimerVersion;
foreach(Map importMap in importServer.Maps)
{
if (!coreServer.Maps.Contains(importMap))
{
coreServer.Maps.Add(importMap);
continue;
}
Map coreMap = coreServer.Maps.FirstOrDefault(o => o.Equals(importMap));
coreMap.Jumps.Concat(importMap.Jumps);
coreMap.Times.Concat(importMap.Times);
}
}
You might have been looking for .Union, but that doesn't deal with the nested lists so I don't think it's what you really need. I set up a full example--although the structure isn't the same as yours I think you can easily apply the core linq:
using System;
using System.Collections.Generic;
using System.Linq;
public class Map
{
public string Name { get; set; }
public IEnumerable<MapProperty> Properties { get; set; }
}
public class MapProperty
{
public string Name { get; set; }
}
public class Test
{
public static IEnumerable<Map> Merge(IEnumerable<Map> a, IEnumerable<Map> b)
{
return a.Concat(b)
.GroupBy(map => map.Name)
.Select(mg => new Map
{
Name = mg.Key,
Properties = mg.SelectMany(v => v.Properties)
.GroupBy(p => p.Name)
.Select(pg => new MapProperty { Name = pg.Key })
});
}
public static void Main()
{
var mapsA = new[]
{
new Map
{
Name = "MapA",
Properties = new[]
{
new MapProperty { Name = "Jumps" },
new MapProperty { Name = "Hops" },
}
},
new Map
{
Name = "MapB",
Properties = new[]
{
new MapProperty { Name = "Jumps" },
new MapProperty { Name = "Skips" },
}
}
};
var mapsB = new[]
{
new Map
{
Name = "MapA",
Properties = new[]
{
new MapProperty { Name = "Jumps" },
new MapProperty { Name = "Times" },
}
},
new Map
{
Name = "MapC",
Properties = new[]
{
new MapProperty { Name = "Jumps" },
new MapProperty { Name = "Skips" },
}
}
};
var mapsC = Merge(mapsA, mapsB);
foreach (var map in mapsC)
{
Console.WriteLine($"{map.Name}");
foreach (var prop in map.Properties)
{
Console.WriteLine($"\t{prop.Name}");
}
}
}
}
The output:
MapA
Jumps
Hops
Times
MapB
Jumps
Skips
MapC
Jumps
Merge is the important bit. It first puts a and b all into one list, then GroupBy name forms groups of maps that have the same name. The Select takes each group and makes a new Map with the contents of all the Maps in that group (this is the merge!). Then I did the same thing to the new merged map's contents and group up MapPropertys and combine/merge them.
In your specific case you'll need a little more work and levels to merge everything. I'd suggest finding a way to make a Merge method for each level in the tree and call the next level's from each parent level so you don't end up with a massive linq chain.

Recursive category and inner join

I want to make recursive category menu for product categories on my web page.Each category must retrieve related first item according to CategoryId on "Product" table but this category should be disappear if category hasn't any product.Actually, i can make to easily with using INNER JOIN for non-recursive category menu.How can i solve this issue?Is there any idea?
I can use a method as follow but this method both amateur and may be null first item.
Category table
+--------------+---------------+------------+
| CategoryId | CategoryName | ParentId |
+--------------+---------------+------------+
| 1 | Cookware | NULL |
+--------------+---------------+------------+
| 2 | Tableware | NULL |
+--------------+---------------+------------+
| 3 | Teapots | 1 |
+--------------+---------------+------------+
| 4 | Cutleries | 3 |
+--------------+---------------+------------+
| 5 | 30pcs Cutlery | 2 |
+--------------+---------------+------------+
Product table
+--------------+--------------+--------------------+------------+
| ProductId | ProductCode | ProductName | CategoryId |
+--------------+--------------+--------------------+------------+
| 1 | G110090 | Teapot | 3 |
+--------------+--------------+--------------------+------------+
| 2 | D220623 | Cutlery Set | 5 |
+--------------+--------------+--------------------+------------+
RecursiveCategory method
public string RecursiveCategory(IEnumerable<Category> category, int? parent)
{
string catNode = string.Empty;
if(category.Any(n=>n.ParentId == parent))
{
catNode += "<ul>";
foreach(Category c in category)
{
catNode += "<li>";
catNode += "<a href='/Detail/" + GetFirstItem(c.CategoryId).ProductId + "'>"+c.CategoryName+"</a>";
catNode += RecursiveCategory(category, c.ParentId);
catNode += "</li>";
}
catNode += "</ul>"
}
return catNode;
}
GetFirstItem method
public Product GetFirstItem(int categoryId)
{
Product prod = new Product();
foreach(Product p in db.Product.Where(n=>n.CategoryId == categoryId))
{
prod.ProductId = p.ProductId;
prod.ProductName = p.ProductName;
...
}
return prod;
}
Try this to construct a hierarchy from a given point (using null in the first call will give you the full tree with the products for each category). As a modification you could make product lazy load if you need to:
public class Category
{
IEnumerable<Category> Children {get;set;}
IEnumerable<Product> Products {get;set;}
}
public IEnumerable<Category> GetCategory(int? parent)
{
var result = new List<Category>();
foreach (var cat in categories.Where(p => p.parentId = parent)
{
var generatedCategory = new Category();
generatedCategory.Children = GetCategory(cat.id);
generatedCategory.Products = products.Where(p => p.CategoryId = cat.CategoryId);
result.Add(generatedCategory);
}
return result;
}
Note: I haven't tested the code just a guide about how to construct it easily.

Categories