Using .Contains() on a property in a list - c#

I have a List of Activity. In the Activity class is an ID property (a Guid for arguments sake). I want to check if this list has an Activity in it with a Guid I have. Rather than this:
foreach(Activity activity in ActivityList)
{
if(activity.Id == GuidToCompare)
//Code here
}
Is there a more efficient way to achieve the same result as I could if I were to have just a list of Guids (instead of a list of Activity's) and to use .Contains()?
I've got a list of a struct called ActivityAndPO. In this struct is a Guid.
I have a list of PO's. In the PO class is a Guid.
I want to loop through all of objects in the the ActivityAndPO list where the Guid's exist in the list of PO's.

Sure.
foreach(Activity activity in ActivityList.Where(a => a.Id == GuidToCompare) )
{
//Code here
}
But since Id implies there will be at most 1 activity:
//var act = ActivityList.Where(a => a.Id == GuidToCompare).SingleOrDefault(); // clearer
var act = ActivityList.SingleOrDefault(a => a.Id == GuidToCompare); // shorter
if (act != null)
{
//Code here
}

Take a look to the LINQ, You can replace with it your code by: ActivityList.Any(i => i.Id == GuidToCompare);

foreach(var activity in ActivityList.Where(p=>p.Id == GuidToCompare))
{
// Code here
}

to find all activity objects with the given GUID you can use:
var results = ActivityList.FindAll(item => item.ID == GuidToCompare);

If you are looking for only one Id one time, there is no more efficient way.
If you are looking for Ids multiple times you can build a HashSet :
var activityIdsQuery = from a in ActivityList
select a.Id;
HashSet<Guid> activityIds = new HashSet<Guid>(activityIdsQuery);
//Use the hashset
activityIds.Contains(id);
If you need to find an instance of activity you can build a Dictionary (works only if Id is unique) :
Dictionary<Guid, Activity> activities = ActivityList.ToDictionary(a => a.Id);
Others solution using Linq with Where/FirstOrDefault/Any on the Id won't be more efficient than yours.

I Havent tested it but im fairly sure this should work:
if ( ActivityList.Any ( a => a.Id == GuidToCompare ) ) {
// TODO - Exists.
}
MSDN Any : http://msdn.microsoft.com/en-us/library/bb534972.aspx

Just to offer you all the ways you can write this query with Linq
var result = (from activity in activityList
where activity.Id == GuidToCompare
select activity ).FirstOrDefault();
if(result != null)
/* Code here */
Now, it is up to you to choose the more readable snippet ;)

For those who can't use LINQ:
List<Activity> ActivityList = new List<Activity>();
foreach (Activity activity in ActivityList.FindAll(delegate(Activity a)
{
return a.Id == GuidToCompare;
}))
{
//Code here
}

Related

LINQ equivalent of WHERE IN clause

I have 3 tables Purchase, PurchaseBill, and PurchasePayment.
Purchase has one-to-many relationship with PurchaseBill
PurchaseBill has one-to-many relationship with PurchasePayment
I'm looking for a Linq (fluent) equivalent of this SQL Query
SELECT COUNT('') FROM PurchasePayment
WHERE PurchaseBillId IN (
SELECT PurchaseBillId FROM PurchaseBill WHERE PurchaseId = #PurchaseId
)
Basically I want to know if any PurchasePayment record exist for certain #PurchaseId
Currently I'm using
var bills = _context.PurchaseBill.Where(a => a.PurchaseId == obj.Id);
foreach (var bill in bills)
{
var hasPayment = _context.PurchasePayment.Any(a => a.PurchaseBillId == bill.Id);
if (hasPayment)
ctx.AddFailure("Cannot modify paid Purchase.");
}
I tried
var hasPayment = _context.PurchasePayment.Any(w => bills.Contains(w.PurchaseBillId));
but it's not working. Is it possible to do it without looping in Linq?
I agree with Gert; you ought to have a PurchaseBill class with an ICollection<PurchasePayment> PurchasePayments property (linking onto the multiple payments) and an int PurchaseId property (linking back to a single purchase) and be able to do something like:
var hasPayment = context.PurchaseBills.Any(
pb => pb.PurchaseId == obj.Id && pb.PurchasePayments.Any()
);
"Is there any purchasebill with PurchaseId blahblah and any PurchasePayment?"
Or, if you plan to do something with each bill, and its payments, something like:
var bills = context.PurchaseBills.Include(pb => pb.PurchasePayments);
foreach(var pb in bills){
if(pb.PurchasePayments.Any()){
//do something with the bill and its purchase here
}
}
But don't do this if all you're going to do is add a message to an error list; it's a lot of data to download for the sake of looking through and finding one bill record that might have a payment record - that is better done by the first recommendation that asks the DB for a simple boolean - "is there any payment anywhere for this purchaseId?"
Have you tried this:
var hasPayment = _context.PurchasePayment.Where(x => _context.PurchaseBill.Where(a => a.PurchaseId == obj.Id).Any(y => y.PurchaseBillId == x.PurchaseBillId)).Any();
I tried an example here:
https://dotnetfiddle.net/uTs6pd
You can implement something like this:
var PurchaseId=1;
var result = _context.PurchasePayment.Where(x=>_context.PurchaseBill.Where(y=>y.PurchaseId==PurchaseId).Select(y=>y.PurchaseBillId).ToList().Contains(x.PurchaseBillId)).Count();

How do I filter duplicates in Entity Framework minimize performance loss?

How can I make the performance better of the code below?
I'm loading the carItems from an external webservice in a list.
carItem is checked whether it exists in EF.
If carItem is new, then it's mapped to carsCol and added to the database. What are some easy ways to improve the performance of this using code?
carItems = carItems.Where(x => x.Name == "Tesla");
// Filter existing cars
List<Car> carsCol = new List<Car>();
foreach (var item in carItems)
{
if (GetById(item.Id) == null)
{
carsCol.Add(item);
}
}
Entities.AddRange(carsCol);
Depending on the situation, you can try to find out which ids already exist in the database by making a single query before the foreach.
var newCarItemIds = carItems.Select(x => x.Id);
var alreadyExistentCarItemIds = Entities.CarItems.Where(x => newCarItemIds.Contains(x.Id)).Select(x=>x.Id);
foreach(var item in carItems)
{
if(!alreadyExistentCarItemIds.Contains(x))
{
carsCol.Add(item);
}
}
You can use Distinct Linq functions: example
You must to implement IEquatable interface
I would propose this. You can do a left outter join with the cars that you get from your API and the existing one. Than you get the new cars that will be added.
var newCars = (from c in carItems
join e in Entities.CarItems on c.Id equals e.Id into g
from x in g.DefaultIfEmpty()
select new { c, IsNew = x == null }).Where(x = x.IsNew).ToList()
With that you do only one access to the database. Also when working with IEnumerable is always good to convert it to either a list or array, that way each time that you iterates through that object you don't run a query in your database.

LINQ Select Last & Unique Record from a DB using List

I have a table where all vehicles are registered and another table where I have millions of pings for each registered vehicle.
I'm trying to select the last ping from each vehicle that has sent a ping in the last 30 minutes using the LINQ QUERY. I've done the code below through the "for each" idea, but I'm not sure if it is the best way to do.
I would like to know if there is any better way to select this using a single line? I know that I can "group by" them by vehicle_fleetNumber but I couldn't achieve the proper result as the TAKE() is limiting the final result.
var timeRestriction = DateTime.UtcNow.AddMinutes(-30);
var x = _db.Vehicles.Where(r=> r.isActive.Equals(true) && r.helperLastPing > timeRestriction);
foreach (var vehicle in x)
{
var firstOrDefault = _db.Tracks.OrderByDescending(r => r.collectedOn)
.FirstOrDefault(r => r.vehicle_fleetNumber.Equals(vehicle.fleetNumber));
}
return View();
Thank you,
Yes, you should do it in the database by joining both tables and using GroupBy:
var query = from v in _db.Vehicles
join t in _db.Tracks
on v.fleetNumber equals t.vehicle_fleetNumber
where v.isActive && v.helperLastPing > timeRestriction
group t by t.vehicle_fleetNumber into vehicleGroup
select vehicleGroup.OrderByDescending(x => x.collectedOn).First();
foreach(var track in query)
{
// ...
}
Instead of the foreach you can also use query.ToArray or ToList, i don't know what you want to do with it.
If you get moreLinq from nuget you will find the .maxby() method:
for example in a different context:
//get the correct exchange rate
var rateList = _db.lists_ExchangeRates.Where(
rates => rates.Currency == currencyCode);
Decimal? exRate = rateList.MaxBy(rates => rates.LastUpdated).ExchangeRate;
Also see below this gives additional info.
MoreLinq maxBy vs LINQ max + where
In my case if I want the last data that has been save I use this method
var id = db.DPSlips.Max(item => item.Id);
So I thought this might work as will just try
var timeRestriction = DateTime.UtcNow.AddMinutes(-30);
var x = _db.Vehicles.Max(a => a.isActive == true && a.helperLastPing > timeRestriction);

LINQ request many-to-many

I have the following tables
Users
- ID
- FirstName
- LastName
MultiplyItems
- ItemID
- Title
UserMultiplyItems
- UserID
- ItemID
I have a variable
List<int> delegateList = {1, 3, 5};
where 1, 3, 5 are ItemID
I want to select all users, where at least one ItemID linked selectable user.
I try the following:
var result = from i in _dbContext.Users
where
((delegateList == null) || i.MultiplyItems.Any(p=> delegateList.Any(a => a == p.ItemID)))
select new UserModel()
{
....
};
but it does not work. error:
Cannot compare elements of type 'System.Collections.Generic.List`1'.
Only primitive types, enumeration types and entity types are
supported.
How to do it correctly?
Thanks
I would write this one:
var filteredUsers = delegateList == null
? _dbContext.Users
: _dbContext.Users.Where(user => user.MultiplyItems
.Any(item => delegateList.Contains(item.Id)));
var result = filteredUsers.Select(user => new UserModel
{
//the UserModel initialization
});
You should not check the following line inside the query:
delegateList == null
It is translated to SQL, and SQL has no idea about what is List and how to compare it with null.
var result = from i in _dbContext.Users
from mi in i.multiplyItems
select new yourClass();
if(delegateList!=null)
{
result = result.Where(delegateList.contains(mi.ItemID));
}
result.ToList();
I dont have visual studio open to test, but it should be a lot like that.
I'm not exactly sure if this is what you wanted, but i thought its better to try and help.
This will output all users from the Users Table where the ItemID is contained in the delegateList. The magic lays in the Contains operator you can get elements from list a contained in list b
var selection = from a in db.UserMultiplyItems
from b in db.Users
where delegateList.Contains(a.ItemID) && a.UserID == b.ID
select b;
Try changing it to:
var result = from u in _dbContext.Users
where
((delegateList == null) || u.MultiplyItems.Any( mi => delegateList.Contains(mi.ItemID)))
select new UserModel()
{
....
};
Note I've also renamed your "i" and "p" things to "u" and "mi" to make it easier to read.
Alternatively you could not use LINQ at all and just stick with lambda expressions:
List<UserModel> usersWithItems =
context
.Users
.Where(u => u.MultiplyItems.Any(mi => (delegateList == null) || (delegateList.Contains(mi.ItemID))))
.Select(um => (new UserModel() { ... } ) )
.ToList();
Which personally I prefer, it means you don't need to know linq at all.

using linq to order tickets by lowest date where ticket is not closed?

I have an object called Ticket with that contains a list of objects called TicketActions. The Ticket object has a field called Date_Closed and the Actions object has a field called Action_Date:
Ticket
Date_Closed
TicketActions
-Action_Date
What I'm trying to do is order a List of tickets (List) based on the latest date of each Action in ascending order where the Ticket does not have a value for Date_Closed. The goal is to load this list into a listview and show tickets in a way that displays tickets in order on the page, placing the ones that have gone the longest without an action at the top. Does that make sense?
Here is what I ended up with so far that isn't working:
protected List<FullTicket> BuildTickets(int ticketsToShow)
{
using (var db = new SupportLogDBDataContext())
{
var result =
(from ticket in db.Support_Tickets
join status in db.Ticket_Statuses on ticket.Status_ID equals status.ID
select new FullTicket
{
TicketID = ticket.ID,
DateOpened = (DateTime)ticket.Date_Opened,
DateClosed = (DateTime)ticket.Date_Closed,
Subject = ticket.Subject,
Status = new KeyPair { Key = status.Status, Value = status.ID },
CreatedBy = new GuidPair { Key = ticket.Reported_By, Value = (Guid)ticket.AD_GUID },
TicketActions =
(from a in db.Ticket_Actions
where a.Ticket_ID == ticket.ID
select a).ToList()
}).Take(ticketsToShow).ToList();
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max()); //error on this line (invalid arguments)
return result;
}
}
People reply quick here!
Try this:
var result = (from ticket in tickets
where !ticket.DateClosed.HasValue
select ticket).OrderByDescending(t => (from a in t.TicketActions
select a.ActionDate).Max());
From here you can take as many as you need.
David B's analysis is slightly off. The line...
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
... will not compile because the argument to the Where method is not a lambda expression or delegate.
I would suggest this solution (assuming that the relevant property of the TicketAction type is ActionDate):
return result.Where(i => i.DateClosed == null)
.OrderBy(i => i.TicketActions.Max(a => a.ActionDate));
Or, in query comprehension syntax:
return from i in result
where i.DateClosed == null
orderby i.TicketActions.Max(a => a.ActionDate)
select i;
Here is some simple code.
var sorted = tickets.Where(t => t.DateClosed == null)
.OrderBy(t => t.TicketActions.Max(ta => ta.Action_Date.Ticks));
Sorry, I prefer LINQ function syntax, but if you want it in query syntax, it shouldn't be too hard to convert.
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
This line generates an error because TicketActions.Max() is not defined.
You need to project TicketAction into something that can be Max'd. For example:
result.OrderBy(i =>
i.TicketActions
.Where(ta => i.DateClosed == null)
.Select(ta => ta.Id)
.Max()
);
Also note:
OrderBy does not modify its source. OrderBy returns an ordered IEnumerable, which you didn't assign anywhere.
OrderBy's enumerable is deferred, and you want a List result instead, so you should call ToList.
You are accessing Ticket.TicketActions outside of the query. This will cause one database round trip per ticket to load that property.
Here is a modification to your query that avoids the problems mentioned above by ordering and using navigational properties within the query.
from ticket in db.Support_Tickets
where ticket.DateClosed == null
let lastDate = ticket.TicketActions
.Select(ta => ta.ActionDate)
.OrderByDescending(date => date)
.FirstOrDefault()
let ticketStatus = ticket.TicketStatus
order by lastDate
select new FullTicket
{
...
}

Categories

Resources