LINQ to CRM One to Many results without flattening - c#

I'm trying to use LINQ to CRM to get some data in a parent/child format. It's a One to Many and I don't want the Child records flattening with the Parent. Firstly is what I'm asking possible?
I do not have navigation properties and there will be many parents returned, in turn each parent will have many children.
I'm trying to avoid multiple queries, just fetching it in 1.
So I'm after:
So the parent has many Children and the children have some other lookup properties in other tables 1 to 1.
Parent
--Child + other info
--Child + other info
Parent
--Child + other info
Parent
--Child + other info
--Child + other info
--Child + other info
etc
I've tried getting the Details as a IQueryable first, then using that joined on the header:
var detailsOnly = (from det in db.details
join inc in db.Incidents on det.detOtherId equals inc.incidentid into incidentsLo
from subInc in incidentsLo.DefaultIfEmpty()
join cli in db.Accounts on subInc.accountid equals cli.accountid into accountsLo
from subCli in accountsLo.DefaultIfEmpty()
select new
{
det,
AccName = subAcc.name,
AccRef = subAcc.accountnumber,
IncidentTicketNumber = subInc.ticketnumber,
IncidentKeyDescription = subInc.title,
IncidentMainContact = subInc.maincontactname
});
var query = from head in db.headers
where head.IsDone == isdone & head.type == typeId & head.accountid == AccountId
select new MyHeader(head)
{
MyDetails = detailsOnly.Where(md => md.det.detOtherId == head.headOtherId)
.Select(d => new MyDetail(d.det)
{
AccName = d.AccName,
AccRef = d.AccRef,
IncidentTicketNumber = d.IncidentTicketNumber,
IncidentKeyDescription = d.IncidentKeyDescription,
IncidentMainContact = d.IncidentMainContact
}).ToList()
};
var result = query.ToList();

It doesn't look like this is getting a real answer, so the best I can say is to execute the details collecting part, group it, then select the other parts from that.
var detailsOnly = (from det in db.details
join inc in db.Incidents on det.detOtherId equals inc.incidentid into incidentsLo
from subInc in incidentsLo.DefaultIfEmpty()
join cli in db.Accounts on subInc.accountid equals cli.accountid into accountsLo
from subCli in accountsLo.DefaultIfEmpty()
select new MyDetail(det)
{
AccName = subAcc.name,
AccRef = subAcc.accountnumber,
IncidentTicketNumber = subInc.ticketnumber,
IncidentKeyDescription = subInc.title,
IncidentMainContact = subInc.maincontactname
}).ToArray();
var groupedDetails = detailsOnly.GroupBy(c => c.det.detOtherId);
var query = db.headers.Where(head => head.IsDone == isdone
&& head.type == typeId
&& head.accountid == AccountId);
var result = query.Join(groupedDetails, c => c.headOtherId, c => c.Key, (a, b) => new MyHeader(a) { MyDetails = b })).ToList();
I could be way off on that, but you see the gist. I don't think it's the best way, but I don't have any experience with LINQ-to-CRM so I don't know. Something like this should work, though. Better than nothing. There might also be syntax errors, because this hasn't been tested.

Related

Updating a property of an object using LINQ when value needed is from db

How do you suppose I tackle this? Basically, I have this inital query:
var orders = (from order in _dbContext.Orders
join orderDetail in _dbContext.OrderDetails on order.ID equals orderDetail.OrderID
where order.StoreID == storeID
select new Order
{
ID = order.ID,
No = order.ID,
Type = "", // Notice that this is empty; this one needs updating
Quantity = order.Quantity,
// more properties here
}).AsQueryable();
After this query, I need to loop through the result and update the Type property based on different criteria like this:
string type = "";
foreach (OrderDetailDto order in orders)
{
if (order.UserID != null)
type = "UserOrder";
else if (order.UserID == null)
type = "NonUserOrder";
else if (order.Cook == null && (order.Option == "fiery"))
type = "FieryCook";
else if (check if this has corresponding records in another table) // this part I don't know how to effectively tackle
type = "XXX";
// Update.
order.Type = type;
}
The problem is one of my criteria needs me to check if there are existing record in the database. I would use JOIN but if I have to loop thru several hundreds or thousands of records and then JOIN each one of them then check on db just to get one value, I think that would be very slow.
I can't do the JOIN on the initial query because I might do a different JOIN based on a different criterion. Any ideas?
You could just join all the lookup tables you might possibly need in left join type way:
from o in Orders
from c in Cooks.Where(x => x.OrderId == m.OrderId).DefaultIfEmpty()
from u in Users.Where(x => x.OrderId == o.OrderId).DefaultIfEmpty()
select new
{
Order = m,
Cook = c,
User = u
}
or depending on your usage patterns you could build the required tables into local Lookups or Dictionaries for linear time searching thereafter:
var userDict = Users.ToDictionary(x => x.UserId);
var userIdDict = Users.Select(x => x.UserId).ToDictionary(x => x);
var cooksLookup = Cooks.ToLookup(x => x.Salary);

Linq to Entities Left outer join grouped into a collection

from component in Materials.OfType<Container>().Where(m => m.Active)
join segmentFinanceRating in segmentFinanceRatingView on component.Id equals segmentFinanceRating.MaterialId into segmentFinanceRatingGroup
from segmentFinanceRatingWithDefault in segmentFinanceRatingGroup.DefaultIfEmpty()
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = segmentFinanceRatingWithDefault
}
I have the above LINQ to Entities query that has a LEFT JOIN to get rating values for 1 or more segments for a given component.
The segmentFinanceRating entity has the properties, { MaterialId, SegmentId, Rating, LowRated }
At the moment the results are not grouped to the relevant component, i.e. the SegmentRatings property is not a single collection of segmentFinanceRating objects, instead I have multiple data rows with 1 segmentFinanceRating object in each.
I have seen some examples of using group x by y into z but I couldn't get it working, possibly due to some of the collections on the component that I need too, I'm not sure.
Any help would be appreciated on how to do this, thanks.
GroupBy in List doesn't work for you?
var list = (from component in Materials.OfType<Container>().Where(m => m.Active)
join segmentFinanceRating in segmentFinanceRatingView on component.Id equals segmentFinanceRating.MaterialId into segmentFinanceRatingGroup
from segmentFinanceRatingWithDefault in segmentFinanceRatingGroup.DefaultIfEmpty()
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = segmentFinanceRatingWithDefault
}).ToList().GroupBy(s=> s.SegmentRatings);
In this case it's much easier to do the join in the anonymous type:
from component in Materials.OfType<Container>().Where(m => m.Active)
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = (from segmentFinanceRating in segmentFinanceRatingView
where segmentFinanceRating.MaterialId == component.Id
select segmentFinanceRating)
}
You will have an empty collection of SegmentRatings when there are none for a specific component, giving the same effect as outer join.

Converting SQL to LINQ query when I cannot use "IN"

I'm trying to convert this very simple piece of SQL to LINQ:
select * from Projects p
inner join Documents d
on p.ProjectID = d.ProjectID
left join Revisions r
on r.DocumentID = d.DocumentID
and r.RevisionID IN (SELECT max(r2.RevisionID) FROM Revisions r2 GROUP BY r2.DocumentID)
WHERE p.ProjectID = 21 -- Query string in code
This says, if any revisions exist for a document, return me the highest revision ID. As it's a left join, if not revisions exist, I still want the results returned.
This works as expected, any revisions which exist are shown (and the highest revision ID is returned) and so are all documents without any revisions.
When trying to write this using LINQ, I only get results where revisions exist for a document.
Here is my attempt so far:
var query = from p in db.Projects
join d in db.Documents on new { ProjectID = p.ProjectID } equals new { ProjectID = Convert.ToInt32(d.ProjectID) }
join r in db.Revisions on new { DocumentID = d.DocumentID } equals new { DocumentID = Convert.ToInt32(r.DocumentID) } into r_join
from r in r_join.DefaultIfEmpty()
where
(from r2 in db.Revisions
group r2 by new { r2.DocumentID }
into g
select new { MaxRevisionID = g.Max(x => x.RevisionID) }).Contains(
new { MaxRevisionID = r.RevisionID }) &&
p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new { d.DocumentID, d.DocumentNumber, d.DocumentTitle, RevisionNumber = r.RevisionNumber ?? "<No rev>", Status = r.DocumentStatuse == null ? "<Not set>" : r.DocumentStatuse.Status };
I'm not very good at LINQ and have been using the converter "Linqer" to help me out, but when trying I get the following message:
"SQL cannot be converted to LINQ: Only "=" operator in JOIN expression
can be used. "IN" operator cannot be converted."
You'll see I have .DefaultIfEmpty() on the revisions table. If I remove the where ( piece of code which does the grouping, I get the desired results whether or not a revision exists for a document or not. But the where clause should return the highest revision number for a document IF there is a link, if not I still want to return all the other data. Unlike my SQL code, this doesn't happen. It only ever returns me data where there is a link to the revisions table.
I hope that makes a little bit of sense. The group by code is what is messing up my result set. Regardless if there is a link to the revisions table, I still want my results returned. Please help!
Thanks.
=======
The code I am now using thanks to Gert.
var query = from p in db.Projects
from d in p.Documents
where p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new
{
p.ProjectID,
d.DocumentNumber,
d.DocumentID,
d.DocumentTitle,
Status = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().DocumentStatuse.Status,
RevisionNumber = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().RevisionNumber
};
gvDocumentSelection.DataSource = query;
gvDocumentSelection.DataBind();
Although this works, you'll see I'm selecting two fields from the revisions table by running the same code, but selecting two different fields. I'm guessing there is a better, more efficient way to do this? Ideally I would like to join on the revisions table in case I need to access more fields, but then I'm left with the same grouping problem again.
Status = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().DocumentStatuse.Status,
RevisionNumber = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().RevisionNumber
Final working code:
var query = from p in db.Projects
from d in p.Documents
where p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new
{
p.ProjectID,
d.DocumentNumber,
d.DocumentID,
d.DocumentTitle,
LastRevision = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault()
};
var results = from x in query
select
new
{
x.ProjectID,
x.DocumentNumber,
x.DocumentID,
x.DocumentTitle,
x.LastRevision.RevisionNumber,
x.LastRevision.DocumentStatuse.Status
};
gvDocumentSelection.DataSource = results;
gvDocumentSelection.DataBind();
If you've got 1:n navigation properties there is a much simpler (and recommended) way to achieve this:
from p in db.Projects
from d in p.Documents
select new { p, d,
LastRevision = d.Revisions
.OrderByDescending(r => r.RevisionId)
.FirstOrDefault() }
Without navigation properties it is similar:
from p in db.Projects
join d in db.Documents on new { ProjectID = p.ProjectID }
equals new { ProjectID = Convert.ToInt32(d.ProjectID) }
select new { p, d,
LastRevision = db.Revisions
.Where(r => d.DocumentID = Convert.ToInt32(r.DocumentID))
.OrderByDescending(r => r.RevisionId)
.FirstOrDefault() }
Edit
You can amend this very wide base query with all kinds of projections, like:
from x in query select new { x.p.ProjectName,
x.d.DocumentName,
x.LastRevision.DocumentStatus.Status,
x.LastRevision.FieldA,
x.LastRevision.FieldB
}

Linq to SQL join and group

I have two tables in my database:
Town:
userid, buildingid
Building:
buildingid, buildingname
What i want is to populate a GridView like this:
But I don't want the buildings to be shown more than once. Here is my code:
var buildings = dc.Towns
.Where(t => t.userid == userid)
.GroupJoin(dc.Buildings,
t => t.buildingid,
b => b.buildingid,
(Towns, Buildings) => new
{
BuildningName = Buildings.First().buildingname,
Count = Towns.Building.Towns.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
New code which works:
var buildings = (from t in dc.Towns
where t.userid == userid
join b in dc.Buildings
on t.buildingid equals b.buildingid
into j1
from j2 in j1.DefaultIfEmpty()
group j2 by j2.buildingname
into grouped
select new
{
buildingname = grouped.Key,
Count = grouped.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
var buildings = from t in dc.Towns
join b in dc.Buildings on t.buildingid equals b.buildingid into j1
from j2 in j1.DefaultIfEmpty()
group j2 by b.buildingname into grouped
select new { buildingname = grouped.key, Count = grouped.Count()}
I think this should do it. I have not tested it so it might give error but it will be something like this.
Wouldn't something like this do it?
Users
.Select(User => new {User, User.Building})
.GroupBy(x => x.Building)
.Select(g=> new {Building = g.Key, Count = g.Count()})
According to my experience with Linq to SQL, when the expression is becoming complicated it is better to write a stored procedure and call it with Linq to SQL. In this way you get better maintainability and upgradeability.
Rather than an option to pure SQL, I see “Linqu to SQL” as a tool to get hard typed object representation of SQL data sets. Nothing more.
Hope it helps you.

Crazy Query need some feedback

var query =context.Categories.Include("ChildHierarchy")
.Where(c =>
context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID));
Questions:
I need to include some data from another Navigation Propery (".Include("otherprop")")
Is it possible to do a select new after all of this?
Thanks
The title to your question intrigued me with the words "Crazy Query", and yes, you're right, it is a bit crazy.
You have a .Where(...) clause with the following predicate:
ch => ch.ParentCategoryID == ch.ParentCategoryID
Now that's going to always be true. So I guess that you're trying to do something else. I'll have a crack at what that might be at the end of my answer.
I then did some cleaning up of your query to get a better idea of what you're doing. This is what it now looks like:
var query =
context
.Categories
.Where(c => context
.CategoryHierarchy
.Select(ch => ch.ChildCategoryID)
.Contains(c.CategoryID));
So rather than use nested queries I would suggest something like this might be better in terms of readability and possibly performance:
var query =
from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID into ghs
where ghs.Any()
select c;
This gives the same results as your query so hopefully this is helpful.
I do get the impression that you're trying to do a query where you want to return each Category along with any child categories it may have. If that's the case here are the queries you need:
var lookup =
(from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
select new { Category = c, Children = lookup[c.CategoryID], };
The lookup query first makes a join on categories and the category hierarchies to return all children categories and their associated ParentCategoryID and then it creates a lookup from ParentCategoryID to a list of associated Category children.
The query now just has to select all categories and perform a lookup on the CategoryID to get the children.
The advantage of using the .ToLookup(...) approach is that it easily allows you to include categories that don't have children. Unlike using a Dictionary<,> the lookup does not throw an exception when you use a key that it hasn't got a value for - instead it returns an empty list.
Now, you can add back in the .Include(...) calls too.
var lookup =
(from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
select new { Category = c, Children = lookup[c.CategoryID], };
Is that what you're after?
1) Then add it - context.Categories.Include("ChildHierarchy").Include("OtherCollection");
2) Absolutely, yes
var query = context.Categories
.Include("ChildHierarchy")
.Include("OtherProp")
.Where(c => context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID))
.Select(c => new { c.A, c.B, c.etc });

Categories

Resources