I'm trying to loop through an IEnumerable but for some reason it is not going through the foreach loop, as the values for each ChartClass was not modified.
public IEnumerable<ChartClass> get(string ID, string buildname, string placeholder)
{
var context = new Entities();
var metrics = from c in context.VSTS_CODE_METRICS
where c.BUILD_NAME == buildname && c.OBJECT_TYPE == "Namespace"
group c by c.BUILD_ID into g
select new ChartClass
{
Build_ID = g.Key,
BuildTrim = g.Key,
Index = g.Average(c => c.MAINTAINABILITYINDEX_).Value
};
foreach (var i in metrics)
{
int num = i.BuildTrim.LastIndexOf('_');
i.BuildTrim = "2";
}
return metrics;
}
I'm trying to change each ChartClass' BuildTrim field to "2" but it's not happening for some reason
Why don't you just set the BuildTrim equal to "2" in the query?
public IEnumerable<ChartClass> get(string ID, string buildname, string placeholder)
{
var context = new Entities();
var metrics = from c in context.VSTS_CODE_METRICS
where c.BUILD_NAME == buildname && c.OBJECT_TYPE == "Namespace"
group c by c.BUILD_ID into g
select new ChartClass
{
Build_ID = g.Key,
BuildTrim = "2",//g.Key,
Index = g.Average(c => c.MAINTAINABILITYINDEX_).Value
};
/*foreach (var i in metrics)
{
int num = i.BuildTrim.LastIndexOf('_');
i.BuildTrim = "2";
}*/
return metrics;
}
metrics is an IQueryable. Every single time you iterate the object it is going to to to the database, query the items that you're asking for, put them into the objects that you specified, and then allow those objects to be iterated. You're modifying the objects that are being returned, but those same in-memory objects won't be used if you iterate the sequence again. Instead it's going back to the database a second time and pulling back a fresh query that doesn't have your changes.
As mentioned in the other answer, if you simply modify your first Select call to set BuildTrim to the desired value to begin with, rather than modifying an object that is just about to be thrown away, your query will work as intended.
Related
I searched some links here to change nested loops to single Linq, I tried using those, part of code is not working, I need some expert guidance to fix this,
UPDATE 1:
I guess wasn't clear in my explanation, the loops works fine! as expected, I am getting correct results, but I am doing optimization, instead of using two loops i need the same code to be converted to single linq.
here is the code :
foreach (var ob in all_request_list.Where(x => x.StartDate != x.EndDate)) {
int consq_dates = ob.EndDate.DateDiff(ob.StartDate);
for (int i = 0; i <= consq_dates; i++) {
combined_list.Add(new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = ob.StartDate.AddDays(i), ProfileID = ob.ProfileID });
}
}
I have problem adding increment variable i to ob.StartDate.AddDays(i).
any help will be appreciated.
Is this what you're looking for?
var items = from ob in all_request_list
where ob.StartDate != ob.EndDate
let consq_dates = ob.EndDate.DateDiff(ob.StartDate)
from i in Enumerable.Range(0, consq_dates + 1)
select new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = ob.StartDate.AddDays(i), ProfileID = ob.ProfileID };
combined_list.AddRange(items);
But: You've code that works. You understand that code. Why do you wan't to change that? BTW: Your two loops will be faster than that linq.
You can use the following Linq:
var items = all_request_list
.Where(x => x.StartDate != x.EndDate)
.SelectMany(x => Enumerable.Range(0, x.EndDate.DateDiff(x.StartDate) + 1)
.Select(y => new { ShiftID = x.ShiftID, SkillID = x.SkillID, EmployeeID = x.EmployeeID, AssignDate = x.StartDate.AddDays(y), ProfileID = x.ProfileID }))
combined_list.AddRange(items);
What it does exactly is Creating an IEumerable<> of results for each item in the all_request_list.Where using Enumerable.Range (This is the part which replaces your for loop), than flattens it using the SelectMany method.
It might be better than a for loop in the terms of readability/maintainability but keep in mind that Linq usually slower than plain loops (tl;dr: Understand what Linq does internally and what it will do in your case).
I don't know exactly what error you're getting, but it could be due to the fact that certain functions cannot be executed inside a linq statement, since it internally translates it to sql. Try this:
foreach (var ob in all_request_list.Where(x => x.StartDate != x.EndDate))
{
int consq_dates = ob.EndDate.DateDiff(ob.StartDate);
for (int i = 0; i <= consq_dates; i++)
{
var newDate = ob.StartDate.AddDays(i);
combined_list.Add(new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = newDate , ProfileID = ob.ProfileID });
}
}
If it still gives you an error, could you specify what error you're receiving, such as the name, type, etc.
I've been using Stopwatch and it looks like the below query is very expensive in terms of performance, even though what I already have below I find most optimal based on various reading (change foreach loop with for, use arrays instead of collection, using anonymous type not to take the whole table from DB). Is there a way to make it faster? I need to fill the prices array, which needs to be nullable. I'm not sure if I'm missing something?
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price };
float?[] prices = new float?[lookupProducts.Length];
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if (idsAndPrices.Any(r => r.ProductId == id))
{
prices[i] = idsAndPrices.Where(p => p.ProductId == id)
.Select(a=>a.Price).FirstOrDefault();
}
else
{
prices[i] = null;
}
}
return prices;
}
It's likely every time you call idsAndPrices.Any(r => r.ProductId == id), you are hitting the database, because you haven't materialized the result (.ToList() would somewhat fix it). That's probably the main cause of the bad performance. However, simply loading it all into memory still means you're searching the list for a productID every time (twice per product, in fact).
Use a Dictionary when you're trying to do lookups.
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = myReadings.ToDictionary(r => r.ProductId, r => r.Price);
float?[] prices = new float?[lookupProducts.Length];
for (int i = 0; i < lookupProducts.Length; i++)
{
string id = lookupProducts[i];
if (idsAndPrices.ContainsKey(id))
{
prices[i] = idsAndPrices[id];
}
else
{
prices[i] = null;
}
}
return prices;
}
To improve this further, we can identify that we only care about products passed to us in the array. So let's not load the entire database:
var idsAndPrices = myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
Now, we might want to avoid the 'return null price if we can't find the product' scenario. Perhaps the validity of the product id should be handled elsewhere. In that case, we can make the method a lot simpler (and we won't have to rely on having the array in order, either):
public Dictionary<string, float> getPricesOfGivenProducts(string[] lookupProducts)
{
return myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
}
And a note unrelated to performance, you should use decimal for money
Assuming that idsAndPrices is an IEnumerable<T>, you should make it's initialization:
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
It's likely that the calls to:
idsAndPrices.Any(r => r.ProductId == id)
and:
idsAndPrices.Where(p => p.ProductId == id)
..are causing the IEnumerable<T> to be evaluated every time it's called.
Based on
using anonymous type not to take the whole table from DB
I assume myReadings is the database table and
var idsAndPrices =
from r in myReadings
select new { ProductId = r.ProductId, Price = r.Price };
is the database query.
Your implementation is far from optimal (I would rather say quite inefficient) because the above query is executed twice per each element of lookupProducts array - idsAndPrices.Any(...) and idsAndPrices.Where(...) statements.
The optimal way I see is to filter as much as possible the database query, and then use the most efficient LINQ to Objects method for correlating two in memory sequences - join, in your case left outer join:
var dbQuery =
from r in myReadings
where lookupProducts.Contains(r.ProductId)
select new { ProductId = r.ProductId, Price = r.Price };
var query =
from p in lookupProducts
join r in dbQuery on p equals r.ProductId into rGroup
from r in rGroup.DefaultIfEmpty().Take(1)
select r?.Price;
var result = query.ToArray();
The Any and FirstOrDefault are O(n) and redundant. You can get a 50% speed up just by removing theAll call. FirstOrDefault will give you back a null, so use it to get a product object (remove the Select). If you want to really speed it up you should just loop through the products and check if prices[p.ProductId] != null before setting prices[p.ProductId] = p.Price.
bit of extra code code there
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
prices[i] = idsAndPrices.FirstOrDefault(p => p.ProductId == id);
}
better yet
Dictionary<Int, Float?> dp = new Dictionary<Int, Float?>();
foreach(var reading in myReadings)
dp.add(r.ProductId, r.Price);
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if(dp.Contains(id)
prices[i] = dp[id];
else
prices[i] = null;
}
I have a query which selects a list of my class. It looks like so:
IQueryable<ClaimsBySupplierAggregate> agg =
(from d in alliance.SupplierSearchByReviewPeriod
where d.ClientID == ClientID && ReviewPeriodIDs.Contains((int)d.ReviewPeriodID)
select new ClaimsBySupplierAggregate {
Amount = d.Amount,
StatusCategoryID = d.StatusCategoryID,
DeptName = d.DepartmentName,
APLReason = d.APLReason,
Area = d.AreaDesc,
StatusCategoryDesc = d.StatusCategoryDesc,
Agreed = d.Agreed
});
Later on in the application I select each variable and get the distinct values like this:
SupplierModel.APLReason = agg.Select(r => r.APLReason).Distinct().ToList();
SupplierModel.AreaDesc = agg.Select(r => r.Area).Distinct().ToList();
SupplierModel.DeptName = agg.Select(r => r.DeptName).Distinct().ToList();
SupplierModel.StatCatDes = aggg.Select(r => r.StatusCategoryDesc).Distinct().ToList();
Is there a way to do this in one LINQ statement?
You could using Aggregate, but you would need a complex object for the seed, which brings you to the same complexity of code. I think that for this particular case, using LInQ enters the Golden Hammer antipattern. Just use an old fashioned loop and four HashSets instead of Lists and you are done and the code is more readable and your intention clearer.
I think you can use this then customize the code follow your expectation.
Or you can use group by.
public class LinqTest
{
public int id { get; set; }
public string value { get; set; }
public override bool Equals(object obj)
{
LinqTest obj2 = obj as LinqTest;
if (obj2 == null) return false;
return id == obj2.id;
}
public override int GetHashCode()
{
return id;
}
}
List<LinqTest> uniqueIDs = myList.Distinct().ToList();
There is a contrived way to do this:
from d in alliance.SupplierSearchByReviewPeriod
where d.ClientID == ClientID && ReviewPeriodIDs.Contains((int)d.ReviewPeriodID)
group d by 0 into g
select new
{
APLReasons = g.Select(d => d.APLReason).Distinct(),
AreaDescs = g.Select(d => d.Area).Distinct(),
DeptNames = g.Select(d => d.DeptName).Distinct(),
StatusCategoryDescs = g.Select(d => d.StatusCategoryDesc).Distinct(),
}
The part group d by 0 into g creates one group of all items, from which you can subsequently query any aggregate you want.
BUT...
...this creates a very inefficient query with UNIONs and (of course) DISTINCTs. It's probably better (performance-wise) to simply get the flat data from the database and do the aggregations in subsequent code.
I have this piece of code
var myList = (from p in db.Full
where ((p.date_reception > begin & p.date_reception < end & !p.mc_host_class.Contains("NULL")) &
(!strListe.Contains(p.mc_host_class)))
group p by p.mc_host_class into g
orderby g.Count() descending
select new
{
hostclassx = g.Key,
countx = g.Count()
}).Take(10).ToList();
HttpContext.Current.Session["allList"] = myList;
i want to get two type of values from my session variable , before using session variable i used to do
object[] ys = myList.Select(a => (object)a.countx.ToString()).ToArray();
List<String> xs = new List<string>();
foreach (var x in myList.Select(i => i.hostclassx))
{
xs.Add(x);
}
I want to get the same type of variables(xs and ys) from my session variable
You have stored an anonymous object inside the session. Anonymous objects are not intended to be leaving the boundaries of the current method. So start by defining a model:
public class MyModel
{
public string HostClass { get; set; }
public int Count { get; set; }
}
and then project your query into this object (bear in mind that you might need to adjust the type of the HostClass property according to your needs - I have defined it as a string but in your particular model it might be some other type, it's just not clear what types of objects are involved in your queries from the code you have pasted so far):
...
orderby g.Count() descending
select new MyModel
{
HostClass = g.Key,
Count = g.Count()
}).Take(10).ToList();
Alright, now you've got a List<MyObject> stored inside your Session["allList"]. So in order to retrieve this value somewhere else in your code it's just a matter of casting back to this same type:
var list = (List<MyModel>)HttpContext.Current.Session["allList"];
And as a side note you seem to be using a & operator instead of && in your where predicates, maybe you didn't exactly wanted to use this. You seem to be confusing the logical AND operator and the binary AND operator.
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