In the following code:
var finalArticles =
from domainArticle in articlesFoundInDomain
join articleCategoryVersion in dbc.ArticlesCategoriesVersions
on domainArticle.ArticleID equals articleCategoryVersion.ArticleID
join articleCategory in dbc.ArticleCategories
on articleCategoryVersion.CategoryID equals articleCategory.CategoryID
where articleCategory.ParentID == 52
group articleCategory by articleCategory.CategoryID
into newArticleCategoryGroup
I understand that the group clause should be returning an IEnumerable where k is the Key, in this case CategoryID.
I think I'm misunderstanding Linq at this point because I assume that for each 'k' there should be a list of articles in 'v', but I don't understand the mechanisms or terminology or something. When I try to project this statement into a new anonymous object I don't seem to get any articles... where are they?
Edit:
Okay so I've got a piece of code that is working, but unfortunately it's hitting the SQL server multiple times:
var articlesAssociatedWithKnowledgeTypes =
from categories in dbc.ArticleCategories
join categoryVersions in dbc.ArticlesCategoriesVersions
on categories.CategoryID equals categoryVersions.CategoryID
join articles in articlesFoundInGivenDomain
on categoryVersions.ArticleID equals articles.ArticleID
where categories.ParentID == 52 && articles.Version == categoryVersions.Version
select new
{
ArticleID = articles.ArticleID,
ArticleTitle = articles.Title,
ArticleVersion = articles.Version,
CategoryID = categories.CategoryID,
CategoryName = categories.Name
} into knowledgeTypesFlat
group knowledgeTypesFlat by new { knowledgeTypesFlat.CategoryID, knowledgeTypesFlat.CategoryName } into knowledgeTypesNested
select new
{
CategoryID = knowledgeTypesNested.Key.CategoryID,
CategoryName = knowledgeTypesNested.Key.CategoryName,
Articles = knowledgeTypesNested.ToList()
};
I thought the ToList() on Articles would sort that out but it doesn't. But, the code works although I'm not sure if this is optimal?
The grouping returns an enumeration of IGroupings. IGrouping<K, V> itself implements IEnumerable<V>. Think of each group as an enumerable of all the members of that group plus an extra property Key
In your first query you are showing a group by and the second one is a group join, both return different results. The group by returns an IEnumerable<IGrouping<TKey, TElement>>. To get the result you're expecting you could group by CategoryId and CategoryName and project as I show below:
var finalArticles =
from domainArticle in articlesFoundInDomain
join articleCategoryVersion in dbc.ArticlesCategoriesVersions
on domainArticle.ArticleID equals articleCategoryVersion.ArticleID
join articleCategory in dbc.ArticleCategories
on articleCategoryVersion.CategoryID equals articleCategory.CategoryID
where articleCategory.ParentID == 52
group articleCategory by new{ articleCategory.CategoryID,articleCategory.CategoryName}
into g
select new {CatId=g.Key.CategoryID, CatName=g.Key.CategoryName,Articles =g.ToList() };
When you need the grouped elements you can call ToList or ToArray as I did above
Your finalArticles query results in a IEnumerable<IGrouping<int, Article>> (assuming CategoryID is int and your articles are of type Article).
These IGrouping<int, Article> provides a Key property of type int (your CategoryID and also the IEnumerable<Article> representing the sequence of articles for that CategoryID.
You can turn this for example into a Dictionary<int, List<Article>> mapping CategoryIDs to the lists of articles:
var dictionary = finalArticles.ToDictionary(group => group.Key, group => group.ToList());
or to a list of categories containing articles:
var categories = finalArticles.Select(group => new {
CategoryID = group.Key,
Articles = group.ToList()}).ToList();
Update after your comment:
var finalArticles =
from domainArticle in articlesFoundInDomain
join articleCategoryVersion in dbc.ArticlesCategoriesVersions
on domainArticle.ArticleID equals articleCategoryVersion.ArticleID
join articleCategory in dbc.ArticleCategories
on articleCategoryVersion.CategoryID equals articleCategory.CategoryID
where articleCategory.ParentID == 52
group articleCategory by new {articleCategory.CategoryID, articleCategory.Name}
into newArticleCategoryGroup
select new
{
CategoryID = newArticleCategoryGroup.Key.CategoryID,
CategoryName = newArticleCategoryGroup.Key.Name,
Articles = newArticleCateGroup.ToList()
}
Related
I'm new to Linq and I'm trying to join two table's - left join to be precise, I'm expecting top 10 results where the property of a result is a collection.
consider the sample model
Class A { int Id, List<B> Collection }
Class B { int Id, int x, int y }
I'm trying to perform left join such that the response I expect needs to be in the following format:
int A
Collection (part of B) =>{int x}
I tried with the following query
From A in _context.A
Join B in (from B in _context.B select new {Id, x }) on B.Id equals A.Id Into subB
From minimalB in subB.defaultIfEmpty()
Select {A.Id, minimalB.x}
How do i achieve the result such that the x property maps in as a collection to the result.
I Apologize for absurd explanation of the question in prior!
I don't think you need from minimalB in subB.defaultIfEmpty part, because it will create something similar to a cartesian product from your sets of data which represented as range variables (a and minimalB ). So to have a collection of X-s, you can try the following:
var query = from a in context.A
join b in context.B on a.Id equals b.Id into groupedB
where groupedB.Any()
select new { a.Id, Xs = groupedB.Select(b => b.X) };
By the way, if you already have the B collection in your A class, you can make the query without explicit joins (under the hood the query provider still will make join)
var query = _context.A.Select(a => new
{
a.Id,
Bs = a.Collection.Select(b => new { b.X, b.Y })
});
I am implementing a controller and I need to get all staff members which have a certain RiskTypeID, which will be selected by the user when they click on Navigation Item.
Here is how I would create the joins in SQL
SQL
Select
RTHG.RiskTypeID,
SM.FullName
From RiskTypeHasGroup RTHG
Inner join RiskGroup RG On RTHG.RiskGroupID = RG.ID
Inner join RiskGroupHasGroupMembers RGHGM ON RG.ID = RGHGM.RiskGroupID
Inner Join GroupMember GM ON RGHGM.GroupMemberID = GM.ID
Inner Join GroupMemberHasStaffMember GMHSM ON GM.ID = GMHSM.GroupMemberID
Inner Join StaffMember SM ON GMHSM.StaffMemberID = SM.ID
Where RTHG.RiskTypeID = 1
I’ve pulled back data before using Linq and lambda but only using simple expressions, I now need to be able to make a call which will bring back the same data as the sql outlined above, I’ve searched online but can’t find anything similar to my requirement.
Here is my Controller, I placed comments inside as guidance
Controller
public ActionResult ViewRiskTypes(int SelectedRiskTypeID)
{
var RiskTypes = _DBContext.RiskTypes.ToList(); // Get all of the current items held in RiskTypes tables, store them as a List in Var RiskTypes
var ViewModel = new List<RiskTypeWithDetails>(); // Create colletion which holds instances of RiskTypeWithDetails and pass them to the ViewModel
var Details = new RiskTypeWithDetails(); // Create a new instance of RiskType with details and store the instance in var Details
foreach (var RiskType in RiskTypes) // Loop through each Item held in var RiskTypes
{
Details.RiskTypes.Add(new RiskTypesItem { ID = RiskType.ID, Description = RiskType.Description }); // assign each items ID & Description to the same feilds in a new
// instance of RiskTypeItems (which is a property of RiskTypeWithDetails)
}
foreach (var RiskType in RiskTypes) // Loop through each item in RiskTypes
{
if (RiskType.ID == SelectedRiskTypeID) // Check Item ID matches SelectedRiskTypeID value
{
//var Details = new RiskTypeWithDetails();
Details.RiskTypeDescription = RiskType.Description; //assign the Risk type Descripton to RiskTypeWithDetails RiskTypeDescription Property
Details.RiskDetails = _DBContext
.RiskTypeHasGroups
//.GroupMemberTypeHasGroupMembers
.Where(r => r.RiskTypeID == SelectedRiskTypeID) // Where RiskTypeId matches Selected ID bring back following data from Db
.Select(r => new RiskDetails
{
RiskGroupDescription = r.RiskGroup.Description,
GroupMembers = r.RiskGroup.RiskGroupHasGroupMembers
.Select(v => v.GroupMember).ToList(),
//StaffMembers = r.RiskGroup.RiskTypeHasGroups
// .Join(r.RiskGroup.RiskTypeHasGroups,
// a => a.RiskGroupID , b => b.RiskGroup.ID,
// (a, b) => new {a, b})
// .Join(r.RiskGroup.RiskGroupHasGroupMembers,
// c => c.) // Dosent join as I would expect... no idea what to do here
}).ToList();
ViewModel.Add(Details); //Add all data retrieved to the ViewModel (This creates one item in the collection)
}
}
return View(ViewModel);
}
As you will see I want to get all Staff Members with a match for the selected RiskTypeID. I need some assistance in converting the above SQL to work within my controller as a lambda expression
Thanks in advance
You were on the right track with your commented out code! For starters, LINQ has two different sytaxes: query and method chain. You were using the method chain syntax and it can get really unmaintainable really quickly.
For an instance like this, query syntax is where it's at.
Here's the result:
from rhtg in _dbContext.RiskTypeHasGroup
where rhtg.RiskTypeID == 1
join rg in _dbContext.RiskGroup
on rhtg.RiskGroupID equals rg.ID
join rghgm in _dbContext.RiskGroupHasGroupMembers
on rg.ID equals rhtg.ID
join gm in _dbContext.GroupMember
on rg.ID equals gm.ID
join gmhsm in _dbContext.GroupMemberHasStaffMember
on gm.ID equals gmhsm.GroupMemberID
join sm in _dbContext.StaffMember
on gmhsm.StaffMemberID equals sm.ID
select new
{
rhtg.RiskTypeId,
sm.FullName
};
Do note, that I used .Net conventions for the different variables.
Here's some documentation on the query syntax:
https://msdn.microsoft.com/en-us/library/gg509017.aspx
You can write the exact same query in linq as follows:
var query = (from RTHG in _DBContext.RiskTypeHasGroup RTHG
join RG in _DBContext.RiskGroup on RTHG.RiskGroupID equals RG.ID
join RGHGM in _DBContext.RiskGroupHasGroupMembers on RG.ID equals RGHGM.RiskGroupID
join GM in _DBContext.GroupMember on RGHGM.GroupMemberID = GM.ID
join GMHSM in _DBContext.GroupMemberHasStaffMember on GM.ID equals GMHSM.GroupMemberID
join SM in _DBContext.StaffMember on GMHSM.StaffMemberID equals SM.ID
where RTHG.RiskTypeID == 1
select new {RTHG.RiskTypeID,SM.FullName});
I just need to make full outer join with Linq, But When i union two quires i get this error:
Instance argument: cannot convert from 'System.Linq.IQueryable' to 'System.Linq.ParallelQuery
And here is my full Code:
using (GoodDataBaseEntities con = new GoodDataBaseEntities())
{
var LeftOuterJoin = from MyCustomer in con.Customer
join MyAddress in con.Address
on MyCustomer.CustomerId equals MyAddress.CustomerID into gr
from g in gr.DefaultIfEmpty()
select new { MyCustomer.CustomerId, MyCustomer.Name, g.Address1 };
var RightOuterJoin = from MyAddress in con.Address
join MyCustomer in con.Customer
on MyAddress.CustomerID equals MyCustomer.CustomerId into gr
from g in gr.DefaultIfEmpty()
select new { MyAddress.Address1, g.Name };
var FullOuterJoin = LeftOuterJoin.Union(RightOuterJoin);
IEnumerable myList = FullOuterJoin.ToList();
GridView1.DataSource = myList;
GridView1.DataBind();
}
The types of your two sequences are not the same, so you can't do a Union.
new { MyCustomer.CustomerId, MyCustomer.Name, g.Address1 };
new { MyAddress.Address1, g.Name };
Try making sure that the fields have the same names and types in the same order.
Why not select it all as one thing? Depending on your setup (i.e., if you have foreign keys properly set up on your tables), you shouldn't need to do explicit joins:
var fullJoin = from MyCustomer in con.Customer
select new {
MyCustomer.CustomerId,
MyCustomer.Name,
MyCustomer.Address.Address1,
MyCustomer.Address.Name
};
Method syntax:
var fullJoin = con.Customers.Select(x => new
{
x.CustomerId,
x.Name,
x.Address.Address1,
x.Address.Name
});
union appends items from one collection to the end of another collection, so if each collection had 5 items, the new collection will have 10 items.
What you seem to want is to end up with 5 rows with more infomration is each. That's not a job for Union. You might be able to do it with Zip(), but you'll really be best with the single query as shown by DLeh.
I have a database that looks like this:
tbl_Seminar
ID
isActive
tbl_SeminarFees
ID
seminar_id -- foreign key
fee_text
I want to get all seminars that are active (isActive ==1) and a list of the fees associated with that seminar. Each Seminar may have n records in tbl_SeminarFees that are its fees. I am able to return a linq structure that returns me a list of objects that look like this {seminar, SeminarFee} but I wanted to create a nested structure that looks like this:
{seminar, List<SeminarFee>}
What should my linq query look like?
here is my linq currently:
var results = from s in context.Seminar
join p in context.SeminarFees on
s.ID equals p.SeminarID
where s.IsActive == 1
select new
{
Seminar = s,
Fees = p
};
How do I change this to get a list of these: {seminar, List<SeminarFee>}
Thanks
UPDATE
#lazyberezovsky gave me a good idea to use a group join and into another variable. But then how do I loop through the result set. Here is what I have now:
foreach (var seminarAndItsFeesObject in results)
{
//do something with the seminar object
//do something with the list of fees
}
This however gives me the following error:
Argument type 'SeminarFees' does not match the
corresponding member type
'System.Collections.Generic.IEnumerable`1[SeminarFees]'
What am I doing wrong?
Thanks
You can use group join which groups inner sequence items based on keys equality (a.k.a. join..into) to get all fees related to seminar:
var results = from s in context.Seminar
join f in context.SeminarFees on
s.ID equals f.SeminarID into fees // here
where s.IsActive == 1
select new
{
Seminar = s,
Fees = fees
};
You can't call ToList() on server side. But you can map results on client later.
BTW You can define navigation property Fees on Seminar object:
public virtual ICollection<SeminarFee> Fees { get; set; }
In this case you will be able load seminars with fees:
var results = context.Seminar.Include(s => s.Fees) // eager loading
.Where(s => s.IsActive == 1);
var results = from s in context.Seminar
join p in context.SeminarFees on s.ID equals p.SeminarID
where s.IsActive == 1
group p by s into grouped
select new {
Seminar = grouped.Key,
Fees = grouped.ToList()
};
i currently have the following LINQ statement:
using (MYEntities ctx = CommonMY.GetMYContext())
{
List<datUser> lstC = (from cObj in ctx.datUser
join fs in ctx.datFS on cObj.UserID equals fs.datUser.UserID
where userOrg.Contains(fs.userOrg.OrgName)
select cObj).ToList();
foreach (datUser c in lstC)
{
Claim x = new Claim
{
UserID= c.userID,
FirstName = c.FirstName,
LastName = c.LastName,
MiddleName = c.MiddleName,
};
}
}
right now it returns all users, but it duplicates them if they have more then 1 org associated with them.
how can i ensure that it only returns distinct UserIDs?
each user can have multiple orgs, but i really just need to return users that have at least 1 org from the userOrg list.
Right before your ToList, put in .Distinct().
In response to #DJ BURB, you should probably use the Distinct overload that takes in an IEqualityComparer to best be sure that you're doing it based off of the unique id of each record.
Look at this blog post for an example.
use group by.
syntax:
var result= from p in <any collection> group p by p.<property/attribute> into grps
select new
{
Key=grps.Key,
Value=grps
}
You will have to call Distinct(), there is no linq query equivalent of that command.