What I want is better explained with code. I have this query:
var items = context.Items.GroupBy(g => new {g.Name, g.Model})
.Where(/*...*/)
.Select(i => new ItemModel{
Name=g.Key.Name,
SerialNumber = g.FirstOrDefault().SerialNumber //<-- here
});
Is there a better way to get the serial number or some other property that is not used in the key? The only way I could think of is to use FirstOrDefault.
Why not just include the serial number as part of the key via the anonymous type you're declaring:
var items = context.Items.GroupBy(g => new {g.Name, g.Model, g.SerialNumber })
.Where(/*...*/)
.Select(i => new ItemModel {
Name=g.Key.Name,
SerialNumber = g.FirstOrDefault().SerialNumber //<-- here
});
Or, alternatively, make your object the key:
var items = context.Items.Where(...).GroupBy(g => g)
.Select(i => new ItemModel {...});
Sometimes it can be easier to comprehend the query syntax (here, I've projected the Item object as part of the key):
var items = from i in context.Items
group i by new { Serial = g.Serialnumber, Item = g } into gi
where /* gi.Key.Item.GetType() == typeof(context.Items[0]) */
select new ItemModel {
Name = gi.Key.Name,
SerialNumber = gi.Key.Serial
/*...*/
};
EDIT: you could try grouping after projection like so:
var items = context.Items.Where(/*...*/).Select(i => new ItemModel { /*...*/})
.GroupBy(g => new { g.Name, g.Model });
you get an
IGrouping<AnonymousType``1, IEnumerable<ItemModel>> from this with your arbitrary group by as the key, and your ItemModels as the grouped collection.
I would strongly advise against what you're doing. The serial number is being chosen arbitrarily since you do no ordering in your queries. It would be better if you specified exactly which serial number to choose that way there are no surprises if the queries return items in a different ordering than "last time".
With that said, I think it would be cleaner to project the grouping and select the fields you need and take the first result. They all will have the same key values so that will stay the same, then you can add on any other fields you want.
var items = context.Items.GroupBy(i => new { i.Name, i.Model })
.Where(/*...*/)
.Select(g =>
g.OrderBy(i => i.Name).Select(i => new ItemModel
{
Name = i.Name,
SerialNumber = i.SerialNumber,
}).FirstOrDefault()
);
Since you need all the data, you need to store all the group data into your value (in the KeyValuePair).
I don't have the exact syntax in front of me, but it would look like:
/* ... */
.Select(g => new {
Key = g.key,
Values = g
});
After that, you can loop through the Key to get your Name group. Inside of that loop, include a loop through the Values to get your ItemModel (I guess that's the object containing 1 element).
It would look like:
foreach (var g in items)
{
Console.WriteLine("List of SerialNumber in {0} group", g.Key);
foreach (var i in g.Values)
{
Console.WriteLine(i.SerialNumber);
}
}
Hope this helps!
You might want to look at Linq 101 samples for some help on different queries.
if the serial number is unique to the name and model, you should include it in your group by object.
If it is not, then you have a list of serials per name and model, and selecting firstordefault is probably plain wrong, that is, I can think of no scenario you would want this.
Related
I am kind of new to Lambda Expressions, I have tried to work out a simple solution to the following task I have set myself.
A customer has a collection of cars. Use LINQ to get a total number of cars he has.
Code below, not sure if this is correct? My second question is how do you display the TotalNumberCars to a textbox?
using (Entities dbcontext = new Entities())
{
var ListByOwner = from c in dbcontext.Owners
where c.OwnerID == OwnerID
group c by c.Cars into g
select new
{
Owner = g.Key,
TotalNumberCars = g.Sum(x => x.Cars)
};
lblTotalCars.Text = ListByOwner.ToList();
}
I don't know how your entity data model is structured, but I would do it like this:
using (var dbContext = new Entities())
{
var numberOfCars = dbContext.Cars.Count(c => c.OwnerId == OwnderId);
lblTotalCars.Text = numberOfCars.ToString();
}
If there's no c.OwnerId then maybe you can access it by typing c.Owner.OwnerId.
ListByOwner.ToList() is an array (generic list) of your new items from your Select projection. Each item is a dynamic entity with two properties of Owner and TotalNumberCars. You need to index or foreach into the list, extract what is needed into a string and that can be your text.
Such as lblTotalCars.Text = ListByOwner[0].Owner; will display the first item's owner.
Take this and fill in what you need.
check this code
lblTotalCars.Text = ListByOwner.ToList().sum(c=>c.TotalNumberCars).ToString();
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
I am wondering what is recommended in the following scenario:
I have a large loop that I traverse to get an ID which I then store in a database like so:
foreach (var rate in rates)
{
// get ID from rate name
Guid Id = dbContext.DifferentEntity
.Where(x => x.Name == rate.Name).FirstOrDefault();
// create new object with the newly discovered
// ID to insert into the database
dbContext.YetAnotherEntity.Add(new YetAnotherEntity
{
Id = Guid.NewGuid(),
DiffId = Id,
}
}
Would it be better/ faster to do this instead (first get all DifferentEntity IDs, rather than querying for them separately)?
List<DifferentEntity> differentEntities = dbContext.DifferentEntity;
foreach (var rate in rates)
{
// get ID from rate name
Guid Id = differentEntities
.Where(x => x.Name == rate.Name).FirstOrDefault();
// create new object with the newly discovered
// ID to insert into the database
dbContext.YetAnotherEntity.Add(new YetAnotherEntity
{
Id = Guid.NewGuid(),
DiffId = Id,
}
}
Is the difference negligible or is this something I should consider? Thanks for your advice.
Store your Rate Names in a sorted string array (string[]) instead of a List or Collection. Then use Array.BinarySearch() to make your search much faster. Rest of what I was going to write has already been written by #Felipe above.
Run them horses! There is really a lot we do not know. Is it possible to keep all the entities in memory? How many of them are duplicates with respect to Name?
A simplistic solution with one fetch from the database and usage of parallelism:
// Fetch entities
var entitiesDict = dbContext.DifferentEntity
.Distinct(EqualityComparerForNameProperty).ToDictionary(e => e.Name);
// Create the new ones real quick and divide into groups of 500
// (cause that horse wins in my environment with complex entities,
// maybe 5 000 or 50 000 fits your scenario better since they are not that complex?)
var newEnts = rates.AsParallel().Select((rate, index) => {
new {
Value = new YetAnotherEntity
{ Id = Guid.NewGuid(), DiffId = entitiesDict[rate.Name],},
Index = index
}
})
.GroupAdjacent(anon => anon.Index / 500) // integer division, and note GroupAdjacent! (not GroupBy)
.Select(group => group.Select(anon => anon.Value)); // do the select so we get the ienumerables
// Now we have to add them to the database
Parallel.ForEach(groupedEnts, ents => {
using (var db = new DBCONTEXT()) // your dbcontext
{
foreach(var ent in ents)
db.YetAnotherEntity.Add(ent);
db.SaveChanges();
}
});
In general in database scenarios, the expensive stuff is the fetch and commits, so try to keep them to a minimum.
You can decrease the number of queries you are doing in database. For example, take all names and query findind Ids where the names contains.
Try something like this.
// get all names you have in rates list...
var rateNames = rates.Select(x => x.Name).ToList();
// query all Ids you need where contains on the namesList... 1 query, 1 column (Id, I imagine)
var Ids = dbContext.DifferentEntity.Where(x => rateNames.Contains(x.Name).Select(x => x.Id).ToList();
// loop in Ids result, and add one by one
foreach(var id in Ids)
dbContext.YetAnotherEntity.Add(new YetAnotherEntity
{
Id = Guid.NewGuid(),
DiffId = id,
}
I need to retrieve a list of entities from my database that matches a list of items in a plain list (not EF). Is this possible with Entity Framework 4.1?
Example:
var list = new List<string> { "abc", "def", "ghi" };
var items = from i in context.Items
where list.Contains(i.Name)
select i;
This works great to return rows that match one property, but I actually have a more complex property:
var list = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
// i need to write a query something like this:
var items = from i in context.Items
where list.Contains(new Tuple<string,string>(i.Name, i.Type))
select i;
I know that is not valid because it will say it needs to be a primitive type, but is there any way to do what I'm trying to accomplish or will I need to resort to a stored procedure?
You have a few options:
1) You could, of course, write a stored procedure to do what you need and call it.
2) You could read the table into memory and then query the in memory list...that way you don't have to use primitives:
var items = from i in context.Items.ToList()
where list.Contains(new Tuple<string, string>(i.Name, i.Type))
select i;
3) You could also convert your query to use primitives to achieve the same goal:
var items = from i in context.Items
join l in list
on new { i.Name, i.Type } equals
new { Name = l.Item1, Type = l.Item2 }
select i;
I would go with the second option as long as the table isn't extremely large. Otherwise, go with the first.
You need to break it down to sub-properties. For example, something like (this might not compile, i'm not able to test at the moment, but it should give you something to work with):
var items = from i in context.Items
where list.Select(x => x.Item1).Contains(i.Name)
&& list.Select(x => x.Item2).Contains(i.Type)
select i;
You have to think about what the resulting SQL would look like, this would be difficult to do directly in SQL.
My suggestion would be you split out one field of the tuples and use this to cut down the results list, get back the query result then filter again to match one of the tuples e.g.
var list = new List<string> { "abc", "def" };
var list2 = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
var items = (from i in context.Items
where list.Contains(i.Name)
select i)
.AsEnumerable()
.Where(i => list2.Any(j => j.val1 == i.Name && j.val2 == i.Type);
I have two IList<Traffic> I need to combine.
Traffic is a simple class:
class Traffic
{
long MegaBits;
DateTime Time;
}
Each IList holds the same Times, and I need a single IList<Traffic>, where I have summed up the MegaBits, but kept the Time as key.
Is this possible using Linq ?
EDIT:
I forgot to mention that Time isn't necessarily unique in any list, multiple Traffic instances may have the same Time.
Also I might run into X lists (more than 2), I should had mentioned that as well - sorry :-(
EXAMPLE:
IEnumerable<IList<Traffic>> trafficFromDifferentNics;
var combinedTraffic = trafficFromDifferentNics
.SelectMany(list => list)
.GroupBy(traffic => traffic.Time)
.Select(grp => new Traffic { Time = grp.Key, MegaBits = grp.Sum(tmp => tmp.MegaBits) });
The example above works, so thanks for your inputs :-)
this sounds more like
var store = firstList.Concat(secondList).Concat(thirdList)/* ... */;
var query = from item in store
group item by item.Time
into groupedItems
select new Traffic
{
MegaBits = groupedItems.Sum(groupedItem => groupedItem.MegaBits),
Time = groupedItems.Key
};
or, with your rework
IEnumerable<IList<Traffic>> stores;
var query = from store in stores
from item in store
group item by item.Time
into groupedItems
select new Traffic
{
MegaBits = groupedItems.Sum(groupedItem => groupedItem.MegaBits),
Time = groupedItems.Key
};
You could combine the items in both lists into a single set, then group on the key to get the sum before transforming back into a new set of Traffic instances.
var result = firstList.Concat(secondList)
.GroupBy(trf => trf.Time, trf => trf.MegaBits)
.Select(grp => new Traffic { Time = grp.Key, MegaBits = grp.Sum()});
That sounds like:
var query = from x in firstList
join y in secondList on x.Time equals y.Time
select new Traffic { MegaBits = x.MegaBits + y.MegaBits,
Time = x.Time };
Note that this will join in a pair-wise fashion, so if there are multiple elements with the same time in each list, you may not get the results you want.