I have 2 lists of custom types. They have a common element called ItemID, I want to get all the elements where they exist in one, but not the other. Does anyone have any ideas?
I basically need the opposite of an inner join, but only want the elements from itemList that aren't in itemCheckoutList or they can be in itemCheckoutList if IsComplete is true. Here is the inner join I have to get all in both with IsComplete being false:
itemList.Join(itemCheckoutList,
i => i.ItemID,
ic => ic.ItemID,
(i, ic) => new { itemList = i, itemCheckoutList = ic }).Where(x => x.itemCheckoutList.IsComplete == false).ToList();
I believe this is what you want.
itemList.Where(i => i.IsComplete ||
!itemCheckoutList.Any(ic => ic.ItemID == i.ItemID))
EDIT
Based on your comment I think this is what you want.
itemList.Where(i => !itemCheckoutList.Any(ic => ic.ItemID == i.ItemID &&
!ic.IsComplete))
EDIT
If efficiency is an issue then you'll want to create a lookup for itemCheckoutList that you can reuse or just change itemCheckoutList to a Dictionary<int, CheckOutItem> as CodeCaster suggests. That can be done like this.
// This should preferably be called just once but
// would need to be called whenever the list changes
var checkOutListLookup = itemCheckoutList.ToLookup(ic => ic.ItemID);
// and this can be called as needed.
var results = itemList.Where(i => !checkOutListLookup.Contains(i.ItemID) ||
checkOutListLookup[i.ItemID].IsComplete);
Or if you make it a Dicionary<int, CheckOutItem> it would look like this.
var results = itemList.Where(i => !checkOutDictionary.ContainsKey(i.ItemID) ||
checkOutDictionary[i.ItemID].IsComplete);
You can also use GroupJoin:
itemList
.GroupJoin(itemCheckoutList, item => item.ItemID, itemCheckout => itemCheckout.ItemID, (item, itemCheckouts) => new {item, itemCheckouts})
.SelectMany(t => t.itemCheckouts.DefaultIfEmpty(), (t, itemCheckout) => new { t.item, itemCheckout })
.Where(t => t.item.IsComplete || t.itemCheckout == null)
.Select(t => t.item);
or the same written as linq query:
var l = from item in itemList
join itemCheckout in itemCheckoutList on item.ItemID equals itemCheckout.ItemID into gj
from itemCheckout in gj.DefaultIfEmpty()
where item.IsComplete || itemCheckout == null
select item;
Related
I have 2 queries with LINQ first one is lambda expression and second is only standard LINQ. I want to get the same output in both, but the 2nd query doesn't give me an output.
How I can solve this and what is the logic of joining a table 2 times?
var query1 = _context.UserEdu.Where(x => x.personId == id && x.status ==PersonEducationStatus.SA.ToString())
.Join(_context.Educations.Where(x => x.statusCode == ACTIVE), pe => pe.classNum, s => s.classNum, (pe, class) => class)
.Join(_context.Educations.Where(x => x.isActive), s => s.eduNum, defaultS => defaultS.eduNum, (class, defaultclass) => new
{ class, defaultclass.classNum })
.Join(_context.EducationDocument.Where(...),
s => s.classNum,
rd => rd.entity_id,
(class, rd) => new
{
... output
});
var query2 = (from pedu in _context.UserEdu.Where(x => x.personId == id && x.status == PersonEducationStatus.SA.ToString())
join class in _context.Educations.Where(x => x.statusCode == ACTIVE) on pedu.classNum equals class.classNum
join defaultclass in _context.Educations.Where(x => x.isActive) on pedu.classNum equals defaultclass.classNum
join rd in _context.EducationDocument.Where(...) on defaultclass.classNum equals rd.entity_id
select new
{
... output same with first query
});
Well,
pedu.classNum equals defaultclass.classNum
Is not
s => s.eduNum, defaultS => defaultS.eduNum
IOW, you have used in second join different keys.
I have this join query that pulls all school programs and products that is in a person's shopping cart:
//this pulls all items the user purchased
var poop = Context.Query<Cart>().Where(x => x.UserId == currentUserId && x.Status == "Archived")
.Select(
p => new
{
p.ItemId,
p.TypeId,
p.PurchaseDate
})
//This get the media type name of the cart items
.Join(
Context.Query<MediaType>(),
t => new {t.TypeId},
m => new {TypeId = m.Id},
(t, m) => new
{
t.ItemId,
t.TypeId,
t.PurchaseDate,
m.TypeName
}).OrderBy(d => d.PurchaseDate)
//Now i need specifics of the items like name, sku, etc. StartDate will be null for items that are products, but contains DateTime for items that are programs.
.Join(
Context.Query<ProgramProductView>(),
e => new {e.ItemId, e.TypeId},
prog => new {ItemId = prog.Id, prog.TypeId},
(e, prog) => new
{
e.ItemId,
e.TypeId,
e.PurchaseDate,
e.TypeName,
prog.FullName,
prog.StartDate,
prog.Sku,
prog.Closed
}).OrderBy(d => d.PurchaseDate);
So right there is where it crashes because prog.StartDate is null for products. I get SQL is not available error.
Is there a way to have the join allow null-able fields? I am only using lambda because it's easier to read and clean.
Well you just need to use Nullable<> property for you anonymous class in your last Join:
.Join(
Context.Query<ProgramProductView>(),
e => new {e.ItemId, e.TypeId},
prog => new {ItemId = prog.Id, prog.TypeId},
(e, prog) =>
new
{
...
(DateTime?)prog.StartDate,
...
}).OrderBy(d => d.PurchaseDate);
Hope it will help.
I have this entity structure:
Categories->Has many "Groups"->Has many "Types"->Has many "Items"
I need to get the descending tree (with includes) filtering by Item.Color == "blue", that is:
var tree = from c in db.Categories
.Include(ct =>
ct.Groups
.Select(gr =>
gr.Types
.Select(pr => pr.Products)
)
)
join g in db.Groups on c.CategoryId equals g.CategoryId
join t in db.Types on g.GroupId equals t.GroupId
join i in db.Items on t.TypeId equals i.TypeId
where i.Color == "blue" // example filter
select c;
With this query I have the descending tree, however I get Items with other colors.
I need only descending tree for blue items.
Thanks!
I am assuming you are using EntityFramework.
All you need to do is an include all the way down, and then use the .Any predicate on your entities. E.g.
var tree = db.Categories.Include(ct => ct.Groups.Select(gr =>
gr.Types.Select(pr =>
pr.Products)))
.Where(c => c.Groups.Any(g =>
g.Types.Any(t =>
t.Items.Any(i =>
i.Color == "blue"))))
.ToList()
A word of warning, if a type has multiple items, and only 1 is blue, you will get all of its Items, not just the blue one. But if a type has no blue items, you wont get it.
If you want to filter at each level, you will need to do projections at each level.
EDIT
So if you want to filter you have 2 options:
Projection at each level
Start by getting all blue items, and then retrieve theirs types, groups and categories.
Examples
1. Projections at each level
var tree = db.Categories.Include(ct => ct.Groups.Select(gr =>
gr.Types.Select(pr =>
pr.Products)))
.Where(c => c.Groups.Any(g =>
g.Types.Any(t =>
t.Items.Any(i =>
i.Color == "blue"))))
.Select(c => new { Groups = c.Groups.Where(g =>
g.Types.Any(t =>
t.Items.Any(i =>
i.Color == "blue"))
.Select( g => new { Types = g.Types.Where(t =>
t.Items.Any(i =>
i.Color == "blue"))
.Select(t => new { Items = t.Items.Where(i =>
i.Color == "blue")
.ToList() // Return list of anonymous types (
You cannot project back an entity type, you have to retrieve the annonymous types first, and then use Linq2Objects to project into the Entity type as EF will not allow you to do this. Often what I do is directly project into a view model ready for display.
I'm using LINQ on an IQueryable returned from NHibernate and I need to select the row with the maximum value(s) in a couple of fields.
I've simplified the bit that I'm sticking on. I need to select the one row from my table with the maximum value in one field.
var table = new Table { new Row(id: 1, status: 10), new Row(id: 2, status: 20) }
from u in table
group u by 1 into g
where u.Status == g.Max(u => u.Status)
select u
This is incorrect but I can't work out the right form.
BTW, what I'm actually trying to achieve is approximately this:
var clientAddress = this.repository.GetAll()
.GroupBy(a => a)
.SelectMany(
g =>
g.Where(
a =>
a.Reference == clientReference &&
a.Status == ClientStatus.Live &&
a.AddressReference == g.Max(x => x.AddressReference) &&
a.StartDate == g.Max(x => x.StartDate)))
.SingleOrDefault();
I started with the above lambda but I've been using LINQPad to try and work out the syntax for selecting the Max().
UPDATE
Removing the GroupBy was key.
var all = this.repository.GetAll();
var address = all
.Where(
a =>
a.Reference == clientReference &&
a.Status == ClientStatus.Live &&
a.StartDate == all.Max(x => x.StartDate) &&
a.AddressReference == all.Max(x => x.AddressReference))
.SingleOrDefault();
I don't see why you are grouping here.
Try this:
var maxValue = table.Max(x => x.Status)
var result = table.First(x => x.Status == maxValue);
An alternate approach that would iterate table only once would be this:
var result = table.OrderByDescending(x => x.Status).First();
This is helpful if table is an IEnumerable<T> that is not present in memory or that is calculated on the fly.
You can also do:
(from u in table
orderby u.Status descending
select u).Take(1);
You can group by status and select a row from the largest group:
table.GroupBy(r => r.Status).OrderByDescending(g => g.Key).First().First();
The first First() gets the first group (the set of rows with the largest status); the second First() gets the first row in that group.
If the status is always unqiue, you can replace the second First() with Single().
Addressing the first question, if you need to take several rows grouped by certain criteria with the other column with max value you can do something like this:
var query =
from u1 in table
join u2 in (
from u in table
group u by u.GroupId into g
select new { GroupId = g.Key, MaxStatus = g.Max(x => x.Status) }
) on new { u1.GroupId, u1.Status } equals new { u2.GroupId, Status = u2.MaxStatus}
select u1;
What about using Aggregate?
It's better than
Select max
Select by max value
since it only scans the array once.
var maxRow = table.Aggregate(
(a, b) => a.Status > b.Status ? a : b // whatever you need to compare
);
More one example:
Follow:
qryAux = (from q in qryAux where
q.OrdSeq == (from pp in Sessao.Query<NameTable>() where pp.FieldPk
== q.FieldPk select pp.OrdSeq).Max() select q);
Equals:
select t.* from nametable t where t.OrdSeq =
(select max(t2.OrdSeq) from nametable t2 where t2.FieldPk= t.FieldPk)
Simply in one line:
var result = table.First(x => x.Status == table.Max(y => y.Status));
Notice that there are two action.
the inner action is for finding the max value,
the outer action is for get the desired object.
I'm have a SQL statement which I am trying to transform in a LINQ statement...
SELECT DISTINCT mc.*
FROM ManufractorCategories mc
WHERE mc.Active = 'true'
AND mc.Folder = 'false'
AND (mc.Id not in (SELECT Category_id FROM Manufractor_Category
WHERE Manufractor_id = 3));
That's my last, not working LINQ statement
(IQueryable<object>)db.ManufractorCategories
.Where(o => o.Active == active)
.Where(o => o.Folder == folder)
.Select(i => new { i.Id, i.Folder }).Except(db.Manufractor_Categories.Where(t => t.Manufractor_id == id).Select(t => new { t.Category_id })).Distinct();
I've tried the whole Sunday on that, but the Except statement won't work.
Thanks in advances for any help!
The Except method requires two sets of the same type - this means that you would have to select objects of type ManufractorCategory in the nested query as well as in the outer query - then it would select all categories that are in the first one and not in the second one.
An easier alternative is to use the Contains method to check whether the current ID is in a list of IDs that you want to filter. The following should work:
var q =
db.ManufractorCategories
.Where(o => o.Active == active)
.Where(o => o.Folder == folder)
.Select(i => new { i.Id, i.Folder })
.Where(o =>
!db.Manufractor_Categories
.Select(t => t.Manufractor_id)
.Contains(o.Id)
.Distinct();
And a simplified version using query syntax:
var q =
from o in db.ManufractorCategories
where o.Active == active && o.Folder == folder &&
db.Manufractor_Categories
.Select(t => t.Manufractor_id)
.Contains(o.Id)
select new { i.Id, i.Folder };
The Except statement is going to get a list of objects with the Category_id property. However, you're query has a result that contains objects with the Id and Folder properties. The query will most likely be unable to see where these objects are equal, and so, the Except clause won't take effect.