LINQ Join needed? - linq

Me again with a dumb question/scenario I need advice on.
I have the following that pulls back the contents of a column:
return getappropriateuserfield.tblAutoComplete
.Where(p => p.MemberId == memberid && p.ACItem == acitem)
.Select(p => p.ACColumn)
.Distinct()
.ToArray();
Depending upon this result, I'd like to then take the ACColumn result, go to tblPreferences, look down ColumnName, and if it matches an entry in there, pull back the Alias (present in tblPreferences)
So, for example we have tblAutoComplete:
MemberID ACItem ACColumn
1 2 UUF1
tblPreferences looks like
MemberID ColumnName Alias
1 UUF1 Category
If the user sticks in "2" as the ACItem, the first part result would be "UUF1" - the linq above does this.
How do I alter the linq so that the second part takes place, ie. takes the UUF1, looks in tblPreferences, checks out ColumnName, sees the result matches so the final result is the Alias, "Category"
Do I need to do this in 2 parts or can I do it as one query, potentially using a join?
Apologies for the thickness.

Looks like a join to me, which is probably most easily expressed with a query expression:
var query = from ac in foo.tblAutoComplete
where ac.MemberId == memberid && ac.ACItem == acitem
join pref in foo.tblPreferences.Where(x => x.MemberId == memberid)
on ac.ACColumn equals pref.ColumnName
select pref.Category;
Note that I've removed the Distinct() call here, which means you may get repeats. You can put Distinct() on the output, of course.
The result of will be an IQueryable<string> (assuming Category is a string). If you need more bits, you could use an anonymous type.
EDIT: I've edited the query so it's got an extra "where" clause when fetching the preferences to start with. That should be equivalent to adding MemberId to the join.

Related

Is Select optional in a LINQ statement?

I was looking over some LINQ examples, and was thereby reminded they are supposed to have a "select" clause at the end.
But I have a LINQ that's working and has no "Select":
public IEnumerable<InventoryItem> Get(string ID, int packSize, int CountToFetch)
{
return inventoryItems
.Where(i => (i.Id.CompareTo(ID) == 0 && i.PackSize > packSize) || i.Id.CompareTo(ID) > 0)
.OrderBy(i => i.Id)
.ThenBy(i => i.PackSize)
.Take(CountToFetch)
.ToList();
}
Is this because:
(a) select is not really necessary?
(b) Take() is doing the "select"
(c) ToList() is doing the "select"
Truth be told, this was working before I added the "ToList()" also... so it seems LINQ is quite permissive/lax in what it allows one to get away with.
Also, in the LINQ I'm using, I think the OrderBy and ThenBy are redundant, because the SQL query used to populate inventoryItems already has an ORDER BY ID, PackSize clause. Am I right (that the .OrderBy() and .ThenBy() are unnecessary)?
Linq statements do in fact need a select clause (or other clauses, such as a group by). However, you're not using Linq syntax, you're using the Linq Enumerable extension methods, which all (for the most part) return IEnumerable<T>. Therefore, they do not need the Select operator.
var result = from item in source
where item.Value > 5
select item;
Is exactly the same as
var result = source.Where(item => item.Value > 5);
And for completeness:
var result = from item in source
where item.Value > 5
select item.Value;
Is exactly the same as
var result = source.Where(item => item.Value > 5)
.Select(item => item.Value);
Linq statements (Linq syntax statements) need a special clause at the end to signify what the result of the query should be. Without a select, group by, or other selection clause, the syntax is incomplete, and the compiler does not know how to change the expression into the appropriate extension methods (which is what Linq syntax actually gets compiled to).
As far as ToList goes, that's one of the Enumerable extension methods that does not return an IEnumerable<t>, but instead a List<T>. When you use ToList or ToArray the Enumerable is enumerated immediately and converted to a list or array. This is useful if your query is complex and you need to enumerate the results multiple times without running the query multiple times).
You only use select when you want to project your object into a different type..
if you had a list that contains an object with an ID property that was an int
var newList = items.Select(i => i.ID);
newList would be an IEnumerable<int>
NB.
A common mistake is to mix up a Select with a Where.
items.Where(i => i.ID == 1); returns an IEnumerable<item>
items.Select(i => i.ID == 1); returns an IEnumerable<bool>
as the Select projects each item into the result of the function passed in..

Linq to Entities Query explanation

Is there any way I can make this Linq to entities query in another way (better) and understand what I did?
First, can I have the string.jon() in the first part (select(p => new {...)?
Second, why do I need the first select to end with .ToList() for the string.join() to work?
The tables relation are as follow:
And here is the code:
Productos.Select(p => new {
Id = p.Id,
Code = p.CodigoProd,
Name = p.Nombre,
Cant = p.Inventario.Sum(i => i.Cantidad),
Pric = p.Inventario.OrderBy(i => i.Precio).Select (i => i.Precio).FirstOrDefault(),
cate = p.ProductosXCategoria.Select(pc => pc.CategoriasdeProducto.Nombre)
}).Where (p => p.Cant != null).ToList()
.Select (r => new {
r.Id, r.Code, r.Cant, r.Name, r.Pric, Categ = string.Join("-",r.cate)
})
the result is this (which is the result i expected to be):
IEnumerable<> (17 items)
**Id-- Code-- Cant-- Name-- Pric-- Categ**
1-- AXI-- 30-- Pepsi-- 10-- Granos
3-- ASI-- 38-- Carne blanca-- 12-- Granos-Limpieza
The query looks fine to me.
The reason you can't move the string.Join method to the first Select, is that LINQ-to-Entities ultimately has to be able to translate to SQL. string.Join has no direct translation to SQL, so it doesn't know how to translate your LINQ query to it. By calling ToList() first, you bring the results of the first Select into memory, where the subsequent Select works with Linq-to-Objects. Since Linq-to-Objects does not need to translate to SQL, it can operate directly on the results of the first query in memory.
Generally, you would want to put everything that would be better left to SQL before the ToList() call (such as filtering, sorting, averaging, grouping, etc.), and leave additional work that can't be translated to SQL (or isn't as efficient to do so) for after the results have been brought into local memory.

Match on 2 values

I'm trying to figure out how to modify this to match on 2:
var result = _context.FirstOrDefault(c => c.CarId == carId);
I'm not sure how to tack this on. I just want to base it on c.CarId == carId && c.UserId == userId
where carId and userId are incoming params to my method that this LINQ statement resides in. I want to keep this as a lambda expression syntax.
Just do it exactly as you've written it:
var result = _context.FirstOrDefault(c => c.CarId == carId && c.UserId == userId);
There's nothing wrong with that. The lambda expression isn't restricted to compare a single property.
If you want to learn about LINQ in more detail, I'd start with LINQ to Objects, which is simpler to understand and predict. There are various tutorials around for it, and I have a blog series called Edulinq which examines each operator in detail.

LINQ subquery question

Can anybody tell me how I would get the records in the first statement that are not in the second statement (see below)?
from or in TblOrganisations
where or.OrgType == 2
select or.PkOrgID
Second query:
from o in TblOrganisations
join m in LuMetricSites
on o.PkOrgID equals m.FkSiteID
orderby m.SiteOrder
select o.PkOrgID
If you only need the IDs then Except should do the trick:
var inFirstButNotInSecond = first.Except(second);
Note that Except treats the two sequences as sets. This means that any duplicate elements in first won't be included in the results. I suspect that this won't be a problem since the name PkOrgID suggests a unique ID of some kind.
(See the documentation for Enumerable.Except and Queryable.Except for more info.)
Do you need the whole records, or just the IDs? The IDs are easy...
var ids = firstQuery.Except(secondQuery);
EDIT: Okay, if you can't do that, you'll need something like:
var secondQuery = ...; // As you've already got it
var query = from or in TblOrganisations
where or.OrgType == 2
where !secondQuery.Contains(or.PkOrgID)
select ...;
Check the SQL it produces, but I think it should do the right thing. Note that there's no point in performing any ordering in the second query - or even the join against TblOrganisations. In other words, you could use:
var query = from or in TblOrganisations
where or.OrgType == 2
where !LuMetricSites.Select(m => m.FkSiteID).Contains(or.PkOrgID)
select ...;
Use Except:
var filtered = first.Except(second);

How can I merge two outputs of two Linq queries?

I'm trying to merge these two object but not totally sure how.. Can you help me merge these two result objects?
//
// Create Linq Query for all segments in "CognosSecurity"
//
var userListAuthoritative = (from c in ctx.CognosSecurities
where (c.SecurityType == 1 || c.SecurityType == 2)
select new {c.SecurityType, c.LoginName , c.SecurityName}).Distinct();
//
// Create Linq Query for all segments in "CognosSecurity"
//
var userListAuthoritative3 = (from c in ctx.CognosSecurities
where c.SecurityType == 3 || c.SecurityType == 0
select new {c.SecurityType , c.LoginName }).Distinct();
I think I see where to go with this... but to answer the question the types of the objects are int, string, string for SecurityType, LoginName , and SecurityName respectively
If you're wondering why I have them broken like this is because I want to ignore one column when doing a distinct. Here are the SQL queries that I'm converting to SQL.
select distinct SecurityType, LoginName, 'Segment'+'-'+SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType =1
select distinct SecurityType, LoginName, 'Business Line'+'-'+SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType =2
select distinct SecurityType, LoginName, SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType in (1,2)
You can't join these because the types are different (first has 3 properties in the resulting type, second has two).
If you can tolerate putting a null value in for the 3rd result of the second query this will help. I would then suggest you just do a userListAuthoritative.concat(userListAuthoritative3 ) BUT I think this will not work as the anonymous types generated by the linq will not be of the same class, even tho the structure is the same. To solve that you can either define a CustomType to encapsulate the tuple and do select new CustomType{ ... } in both queries or postprocess the results using select() in a similar fashion.
Acutally the latter select() approach will also allow you to solve the parameter count mismatch by implementing the select with a null in the post-process to CustomType.
EDIT: According to the comment below once the structures are the same the anonymous types will be the same.
I assume that you want to keep the results distinct:
var merged = userListAuthoritative.Concat(userListAuthoritative3).Distinct();
And, as Mike Q pointed out, you need to make sure that your types match, either by giving the anonymous types the same signature, or by creating your own POCO class specifically for this purpose.
Edit
If I understand your edit, you want your Distinct to ignore the SecurityName column. Is that correct?
var userListAuthoritative = from c in ctx.CognosSecurities
where new[]{0,1,2,3}.Contains(c.SecurityType)
group new {c.SecurityType, c.LoginName, c.SecurityName}
by new {c.SecurityType, c.LoginName}
select g.FirstOrDefault();
I'm not exactly sure what you mean by merge, since you're returning different (anonymous) types from each one. Is there a reason the following doesn't work for you?
var userListAuthoritative = (from c in ctx.CognosSecurities
where (c.SecurityType == 1 || c.SecurityType == 2 || c.SecurityType == 3 || c.SecurityType == 0)
select new {c.SecurityType, c.LoginName , c.SecurityName}).Distinct();
Edit: This assumed they were of the same type -- but they're not.
userListAuthoritative.Concat(userListAuthoritative3);
Try below code, you might need to implement IEqualityComparer<T> in your ctx type.
var merged = userListAuthoritative.Union(userListAuthoritative3);

Resources