Incrementing an ID using LINQ - c#

Whenever I'm adding a new object from the front end, the id = 0. In the WebApi layer, I'm trying to find the max ID that exists in the list of object and then assign the next ID to the new objects. The code below doesn't increment the ID correctly
List<Event> events = eventVal.Where(e => e != null).ToList();
int eventMaxID = events.Max(e => e.id);
events.Where(e => e.id == 0)
.Select((e, ixc) => new { id = eventMaxID + 1, Iter = eventMaxID + 1 })
.ToList();
I'm not sure how to use the second parameter for the Select method.
Any help would be appreciated! Thanks.

In the second form of Select that you're using, ixc is the index of the item in the collection. You'll need to add that as well as the previous max Id. That way you shouldn't have to worry about assigning to Iter either (it appears you're just using it as some kind of counter) so I've removed it.
var autoIncrementedEvents = events.Where(e => e.id == 0)
.Select((e, ixc) =>
{
e.id = eventMaxId + 1 + ixc;
return e;
})
.ToList();
Note that the way your code is written the result of this Linq statment is thrown away. You'll want to assign it to something like I've done above.
I'm not going to comment on the validity of this as an overall approach in a web setting (race conditions, duplicate ids, etc). Ideally your datastore should be assigning the Id.

Related

C# LINQ to Entities - Retrieve all records and position for waiting list records

I have a waitlist table (id, locationid, timeslotid, sessiondate, memberid, dateAdded) which contains a list of people on various waitlist for various appointments.
I am trying to retrieve a list of all waitlist records for a specific user but i also need to get the position of that record (ordered by dateAdded) so that i can see if they are in position 1, 2, 3 etc in the queue.
The following code is what i have atm for getting all user records, but it am struggling on how to join the count to this query.
db.WaitingLists.Where(x => x.MemberId == member.Id && x.LocationId == locationId && x.SessionDate >= currentLocationDate.Date).ToList();
Some suggestions would be welcomed on how to do this.
Thanks
============= UPDATE ==============
This is the SQL that provides the response I need. I am trying to prevent using a stored Proc and try and use linq to entities where possible.
select
(
SELECT count(*) RowNr
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY CreatedDate) AS RowNr,
MemberId
FROM waitinglist where LocationId = wl.LocationId and TimeSlotId = wl.TimeSlotId and [SessionDate] = wl.SessionDate
) sub
)
as Position, * from WaitingList as wl where memberid = '00000000-0000-0000-0000-000000000000'
I haven't tested this, but it should be pretty close. Filter first by the location and date, then sort it, then use the overload of Select that gives you an index, then filter by the member ID.
db.WaitingLists
.Where(x => x.Member.LocationId == locationId && x.Member.SessionDate >= currentLocationDate.Date)
.OrderBy(x => x.DateAdded)
.Select((x, i) => new { Position = i, Member = x })
.Where(x => x.Member.MemberId == member.Id)
.ToList();
This will give you a list of anonymous objects with two properties:
Position, which is the position in the waiting list, and
Member, which is the member details
I can't say what the SQL will actually look like, and if it will be efficient.
This might work for you. I'm assuming that the queue position is across all locations and session dates. If it isn't, insert another Where clause in between db.WaitingLists and the OrderBy.
var temp = db.WaitingLists
.Orderby(x => x.dateAdded)
.Select( (r, i) => new { Request = r, QueuePosition = i + 1)
.Where(x => x.Request.MemberId == member.Id &&
x.Request.LocationId == locationId &&
x.Request.SessionDate >= currentLocationDate.Date);
You now have a list of anonymous-type objects that have the Request (my name for your type; rename it if you want to) and a second property called QueuePosition, based on the position of the requests in the queue, as represented by the WaitingList sorted by dateAdded.
If you need to extract just the Request object, you can get it this way:
var selectedRequests = temp.Select(x => x.Request).ToList();
If you want to get any entries that were in the first 3 positions, you'd do this:
var selectedRequests = temp.Where(x => x.QueuePosition <= 3).Select(x => x.Request).ToList();
Disclaimer: done from memory, not actually tested.

LINQ select next record with each matching result

I have objects from which measurements are saved to a single table. I want to find out how long an object has been in a certain state within a time period.
So in addition to getting the record with the wanted state I need to pair it up with the next measurement made from the same object to calculate the time between them.
I came up with this monster:
// Get the the object entry from Database
MeasuredObject object1;
try
{
object1 = (MeasuredObject)(from getObject in db.MeasuredObject where wantedObject.Id.Equals(getObject.Id) select getObject).Single();
}
catch (System.ArgumentNullException e)
{
throw new System.ArgumentException("Object does not exist", "wantedObject", e);
}
// Get every measurement which matches the state in the time period and the next measurement from it
var pairs = (from m in object1.Measurements
join nextM in object1.Measurements
on (from next in object1.Measurements where (m.Id < next.Id) select next.Id).Min() equals nextM.Id
where 'm is in time period and has required state'
select new { meas = m, next = nextM });
I would say this doesn't seem very efficient especially when I'm using Compact Edition 3.5.
Is there any way to navigate to the next measurement through m or could I somehow use orderby or group to select next by Id? Or even make the join clause simpler?
From the posted code looks like you are working with in memory collection. If that's true, then the following should be sufficient:
var items = (from m in object1.Measurements
where 'm is in time period and has required state'
orderby m.Id
select m)
.ToList();
var pairs = items.Select((item, index) => new
{
meas = item,
next = index + 1 < items.Count ? items[index + 1] : null
});
EDIT: The above is not the exact equivalent of your code because it applies the filter before pairing the items. The exact optimized equivalent would be like this:
var items = object1.Measurements.OrderBy(m => m.Id).ToList();
var pairs = items.Select((item, index) => new
{
meas = item,
next = index + 1 < items.Count ? items[index + 1] : null
})
.Where(pair => 'pair.meas is in time period and has required state');

LINQ to Entities does not recognize the method

This query I wrote is failing and I am not sure why.
What I'm doing is getting a list of user domain objects, projecting them to a view model while also calculating their ranking as the data will be shown on a leaderboard. This was how I thought of doing the query.
var users = Context.Users.Select(user => new
{
Points = user.UserPoints.Sum(p => p.Point.Value),
User = user
})
.Where(user => user.Points != 0 || user.User.UserId == userId)
.OrderByDescending(user => user.Points)
.Select((model, rank) => new UserScoreModel
{
Points = model.Points,
Country = model.User.Country,
FacebookId = model.User.FacebookUserId,
Name = model.User.FirstName + " " + model.User.LastName,
Position = rank + 1,
UserId = model.User.UserId,
});
return await users.FirstOrDefaultAsync(u => u.UserId == userId);
The exception message
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[WakeSocial.BusinessProcess.Core.Domain.UserScoreModel] Select[<>f__AnonymousType0`2,UserScoreModel](System.Linq.IQueryable`1[<>f__AnonymousType0`2[System.Int32,WakeSocial.BusinessProcess.Core.Domain.User]], System.Linq.Expressions.Expression`1[System.Func`3[<>f__AnonymousType0`2[System.Int32,WakeSocial.BusinessProcess.Core.Domain.User],System.Int32,WakeSocial.BusinessProcess.Core.Domain.UserScoreModel]])' method, and this method cannot be translated into a store expression.
Unfortunately, EF does not know how to translate the version of Select which takes a lambda with two parameters (the value and the rank).
For your query two possible options are:
If the row set is very small small, you could skip specifying Position in the query, read all UserScoreModels into memory (use ToListAsync), and calculate a value for Position in memory
If the row set is large, you could do something like:
var userPoints = Context.Users.Select(user => new
{
Points = user.UserPoints.Sum(p => p.Point.Value),
User = user
})
.Where(user => user.Points != 0 || user.User.UserId == userId);
var users = userPoints.OrderByDescending(user => user.Points)
.Select(model => new UserScoreModel
{
Points = model.Points,
Country = model.User.Country,
FacebookId = model.User.FacebookUserId,
Name = model.User.FirstName + " " + model.User.LastName,
Position = 1 + userPoints.Count(up => up.Points < model.Points),
UserId = model.User.UserId,
});
Note that this isn't EXACTLY the same as I've written it, because two users with a tied point total won't be arbitrarily assigned different ranks. You could rewrite the logic to break ties on userId or some other measure if you want. This query might not be as nice and clean as you were hoping, but since you are ultimately selecting only one row by userId it hopefully won't be too bad. You could also split out the rank-finding and selection of base info into two separate queries, which might speed things up because each would be simpler.

C# List grouping and assigning a value

I have a list of Orders. This list contains multiple orders for the same item, see the table below.
I then want to assign each item that is the same (i.e. ABC) the same block ID. So ABC would have a block ID of 1 & each GHJ would have a block ID of 2 etc. What is the best way of doing this?
Currently I order the list by Order ID and then have a for loop and check if the current Order ID is equal to the next Order ID if so assign the two the same block ID. Is there a better way of doing this using linq or any other approach?
Order ID Block ID
ABC
ABC
ABC
GHJ
GHJ
GHJ
MNO
MNO
You can do this that way, it will assign same blockid for same orderid
var ordered = listOrder.GroupBy(x => x.OrderId).ToList();
for (int i = 0; i < ordered.Count(); i++)
{
ordered[i].ForEach(x=>x.BlockId=i+1);
}
it will group orders by orderid then assign each group next blockid. Note that it won't be done fully in linq, because linq is for querying not changing data.
Always depends of what better means for you in this context.
There are a bunch of possible solutions to this trivial problem.
On top of my head, I could think of:
var blockId = 1;
foreach(var grp in yourOrders.GroupBy(o => o.OrderId))
{
foreach(var order in grp)
{
order.BlockId = blockId;
}
blockId++;
}
or (be more "linqy"):
foreach(var t in yourOrders.GroupBy(o => o.OrderId).Zip(Enumerable.Range(1, Int32.MaxValue), (grp, bid) => new {grp, bid}))
{
foreach(var order in t.grp)
{
order.BlockId = t.bid;
}
}
or (can you still follow the code?):
var orders = yourOrders.GroupBy(o => o.OrderId)
.Zip(Enumerable.Range(1, Int16.MaxValue), (grp, id) => new {orders = grp, id})
.SelectMany(grp => grp.orders, (grp, order) => new {order, grp.id});
foreach(var item in orders)
{
item.order.BlockId = item.id;
}
or (probably the closest to a simple for loop):
Order prev = null;
blockId = 1;
foreach (var order in yourOrders.OrderBy(o => o.OrderId))
{
order.BlockId = (prev == null || prev.OrderId == order.OrderId) ?
blockId :
++blockId;
prev = order;
}
Linq? Yes.
Better than a simple loop? Uhmmmm....
Using Linq will not magically make your code better. Surely, it can make it often more declarative/readable/faster (in terms of lazy evaluation), but sure enough you can make otherwise fine imperative loops unreadable if you try to force the use of Linq just because Linq.
As a side note:
if you want to have feedback on working code, you can ask at codereview.stackexchange.com

grouping and counting in linq-to-sql

I have the following query that receives a list of IDs and I want to do a count. There's also an object model CountModel that holds the counts with each property defined as an int.
public class GetCountByStatus(List<int> TheIDs)
{
...using MyDC...
var CountData = (from d in MyDC.Data
where TheIDs.Contains(d.ID)
group d by d.Status into statusgroup
select new CountModel()
{
CountStatus1 = (from g in statusgroup
where g.Status == 1
select g).Count(),
CountStatus2 = (from g in statusgroup
where g.Status == 2
select g).Count(),
CountStatusN = ....
}).Single();
If for instance there are no elements with status N, will this code crash or will the count be 0 for CountStatusN ? Is this the best way to do what I want?
Thanks.
I would go for a dictionary instead, try something like this:
var countData = MyDC.Data.Where(y => TheIDs.Contains(y.ID))
.GroupBy(y => y.Status).ToDictionary(y => y.Key, y => y.Count());
I haven't tried it my self and not written the code in VS, but I think that is almost how you do can do it. That will give you a dictionary where the key is the status and the value is the count of that status.
Defining a model with properties named SomethingX is not very flexible. That means you have to go in an change the model when there is a new status. Keeping the data in the dictionary instead will save you from that.
Count() will always return an integer, which is 0 if there are no elements with the given status. Therefore CountStatusN will always be an integer as well.

Categories

Resources