How to convert this foreach loop into Linq code? - c#

I am new one with Linq and I would like to modify my old c# code to use Linq.
The idea of this code to select all tables where it's not set and reference’s field PrimaryTable equal "myTable"
foreach (Table table in dbServer.Tables)
{
if (!table.IsSet)
{
foreach (Reference refer in table.References)
{
if (refer.PrimaryTable == "myTable")
{
tables.Add(table);
}
}
}
}
After digging in internet I have got this code
var q = from table in dbServer.Tables
let refers = from refer in table.References
where refer.PrimaryTable == "myTable"
select refer.ForeignTable
where refers.Contains(table.Name)
select table;
But it does not work at all and I need your help to make it works.
Thanks in advance.

var tables = dbServer.Tables
.Where(t => !t.IsSet)
.SelectMany(t => t.References)
.Where(r => r.PrimaryTable == "myTable")
.ToList();
Assuming tables is a List<T>
EDIT: As the comment points out, this isn't the same as the original - it looks like what you actually want is this:
var tables = dbServer.Tables
.Where(t => !t.IsSet && t.References.Any(r => r.PrimaryTable == "myTable"))
.ToList();
This will give you all the tables which have a reference whose PrimaryTable is 'myTable' which assumes that there will only be one matching reference table. Otherwise you could have the same table added multiple times.

Just need to use two from's
var q = from table in dbServer.Tables
where !table.IsSet
from refer in table.References
where refer.PrimaryTable == "myTable"
select table;

EDIT
Actually, I'm a bit confused by this code. Are you sure it's doing what it's meant to do? In particular, what's throwing me off is the fact that you're enumerating over table.References, but then, when a certain condition holds for a particular Reference (i.e., refer.PrimaryTable == "myTable"), you're adding the Table (table) instead of the Reference (refer).
What this means is that if a Table has multiple Reference objects with PrimaryTable == "myTable", your tables collection might contain multiple copies of this Table. Is this correct?
I'm going to go out on a limb and guess that what you really want to check is simply that a Table has, in its References collection, any Reference object with PrimaryTable == "myTable". If that's the case, in your original code after tables.Add(table) I would have simply added break to avoid duplicates. (It may be that only one Reference in each collection would ever have the same PrimaryTable, which case you'd be fine; but you could still stop enumerating at this point. Unless of course you want the duplicates.)
In any case, Lee's code (and what I have below) is not duplicating this behavior. Rather, it's adding the Reference objects to a list (because the final ToList call is on an IEnumerable<Reference>).
It seems like, if what I've described above is the behavior you're after, you might want to do this:
var tables = dbServer.Tables
.Where(table => !table.IsSet)
.Where(
table => table.References.Any(refer => refer.PrimaryTable == "myTable")
).ToList();
ORIGINAL ANSWER
I'm going to expand on Lee's answer. Let's analyze it line by line.
// 1. enumerating over a collection
foreach (Table table in dbServer.Tables)
{
// 2. checking a condition
if (!table.IsSet)
{
// 3. enumerating over another collection
foreach (Reference refer in table.References)
{
// 4. checking a condition
if (refer.PrimaryTable == "myTable")
{
// 5. adding to a collection
tables.Add(table);
}
}
}
}
OK. So we've got:
Enumeration - simple -- that's where we start
Condition checking - we'll need a Where
Enumeration over another collection - SelectMany
Condition checking - Where again
Adding - most likely ToList (depends on what type of collection you want)
Here's what it comes out to:
var tables = dbServer.Tables // step 1
.Where(table => !table.IsSet) // step 2
.SelectMany(table => table.References) // step 3
.Where(refer => refer.PrimaryTable == "myTable") // step 4
.ToList(); // step 5
Make sense?

tables.AddRange(dbServer.Tables
.Where(t => !t.IsSet)
.SelectMany(t => table.References)
.Where(r => r.PrimaryTable == "myTable"));

Related

Adding items to the list inside foreach loop

epublic ActionResult ExistingPolicies()
{
if (Session["UserId"]==null)
{
return RedirectToAction("Login");
}
using(PMSDBContext dbo=new PMSDBContext())
{
List<Policy> viewpolicy = new List<Policy>();
var userid = Session["UserId"];
List<AddPolicy> policy= dbo.AddPolicies.Where(c => c.MobileNumber ==
(string)userid).ToList();
foreach(AddPolicy p in policy)
{
viewpolicy=dbo.Policies.Where(c => c.PolicyId ==p.PolicyId).ToList();
}
Session["Count"] = policy.Count;
return View(viewpolicy);
}
}
Here the policy list clearly has 2 items.But when I iterate through foreach,the viewpolicy list only takes the last item as its value.If break is used,it takes only the first item.How to store both items in viewpolicy list??
Regards
Surya.
You can iterate through policies and add them by one to list with Add, but I would say that often (not always, though) better option would be to just retrieve the whole list from DB in one query. Without knowing your entities you can do at least something like that:
List<AddPolicy> policy = ...
viewpolicy = dbo.Policies
.Where(c => policy.Select(p => p.PolicyId).Contains(c.PolicyId))
.ToList();
But if you have correctly set up entities relations, you should be able to do something like this:
var viewpolicy = dbo.AddPolicies
.Where(c => c.MobileNumber == (string)userid)
.Select(p => p.Policy) //guessing name here, also can be .SelectMany(p => p.Policy)
.ToList();
Of course; instead of adding to the list, you replace it with a whole new one on each pass of the loop:
viewpolicy=dbo.Policies.Where(c => c.PolicyId ==p.PolicyId).ToList()
This code above will search all the policies for the policy with that ID, turn it into a new List and assign to the viewpolicy variable. You never actually add anything to a list with this way, you just make new lists all the time and overwrite the old one with the latest list
Perhaps you need something like this:
viewpolicy.Add(dbo.Policies.Single(c => c.PolicyId ==p.PolicyId));
This has a list, finds one policy by its ID number (for which there should be only one policy, right? It's an ID so I figured it's unique..) and adds it to the list
You could use a Where and skip the loop entirely if you wanted:
viewpolicy=dbo.Policies.Where(c => policy.Any(p => c.PolicyId == p.PolicyId)).ToList();
Do not do this in a loop, it doesn't need it. It works by asking LINQ to do the looping for you. It should be converted to an IN query and run by the DB, so generally more performant than dragging the policies out one by one (via id). If the ORM didn't understand how to make it into SQL you can simplify things for it by extracting the ids to an int collection:
viewpolicy=dbo.Policies.Where(c => policy.Select(p => p.PolicyId).Any(id => c.PolicyId == id)).ToList();
Final point, I recommend you name your "collections of things" with a plural. You have a List<Policy> viewpolicy - this is a list that contains multiple policies so really we should call it viewPolicies. Same for the list of AddPolicy. It makes code read more nicely if things that are collections/lists/arrays are named in the plural
Something like:
viewpolicy.AddRange(dbo.Policies.Where(c => c.PolicyId ==p.PolicyId));

How to change the value of a variable within a child of a nested list

Being new rather new to entity framework, LINQ and C#, I am not sure how to change the value of a variable in a nested list. I am aware that LINQ is not meant to update objects, but I read somewhere that you can do it somehow and that's what I am trying here. I am happy with any solution.
Assume we have two lists, myData with data of the current month, and myDataBefore with the same list from the previous month. I want to update myData. More specifically I want to change (in SQL you would say UPDATE) the valuePrevMonth and deltaPercentage for all 1st-level children inside that list - everything else should remain untouched. I tried the following, but this does not have the desired effect.
var myChildren = myData.SelectMany(x => x.children).ToList();
foreach (var child in myChildren)
{
var valuePrevMonth = myDataBefore.SelectMany(x => x.children)
.Where(x => (x.PositionId == child.PositionId &&
x.SubPositionId == child.SubPositionId))
.Select(x => x.Value).FirstOrDefault();
// now comes the write-back step which I am probably messing up
myData.SelectMany(x => x.children)
.Where(x => (x.PositionId == child.PositionId &&
x.SubPositionId == child.SubPositionId))
.Select(x => {
x.previousValue = valuePrevMonth;
x.deltaPercentage = (x.Value - valuePrevMonth) / valuePrevMonth; // probably some IF-NULL check would not hurt
return x;
}).ToList();
}
The last .Select(..) is my current understanding how to update these two values of a given child object.
Some background
My nested list List<myData> is of following format (please allow me to use some kind of pseudocode for the definition):
myData
{
int? PositionId
string PositionName
int SubPositionId
string SubPositionName
myObject children
decimal? Value
decimal? previousValue
decimal? deltaPercentage
}
References
https://stackoverflow.com/a/14729289/8330162 - the quote that query means read-not-write comes from there
LINQ nested (inner) JOIN does not join children - another question of mine which lead me to this problem here
My first thought is that your second statement beginning with .SelectMany(x => x.Children) may be unnecessary; if I'm reading it right, the thing you want to update is already in the child variable, so you would just do:
child.previousValue = valuePrevMonth;
...
That said, it may matter whether your myObject type is a struct or a class; if it's a struct, updating its fields will not do what you want, because you would be operating on a copy. To update elements of a List of structs, you have to get the updated struct value that you want (e.g., by setting fields on a copy of the value), and then update the entire list element with list[x] = updatedValue;.

Replacing Include() calls to Select()

Im trying to eliminate the use of the Include() calls in this IQueryable definition:
return ctx.timeDomainDataPoints.AsNoTracking()
.Include(dp => dp.timeData)
.Include(dp => dp.RecordValues.Select(rv => rv.RecordKind).Select(rk => rk.RecordAlias).Select(fma => fma.RecordAliasGroup))
.Include(dp => dp.RecordValues.Select(rv => rv.RecordKind).Select(rk => rk.RecordAlias).Select(fma => fma.RecordAliasUnit))
.Where(dp => dp.RecordValues.Any(rv => rv.RecordKind.RecordAlias != null))
.Where(dp => dp.Source == 235235)
.Where(dp => dp.timeData.time >= start && cd.timeData.time <= end)
.OrderByDescending(cd => cd.timeData.time);
I have been having issues with the database where the run times are far too long and the primary cause of this is the Include() calls are pulling everything.
This is evident in viewing the table that is returned from the resultant SQL query generated from this showing lots of unnecessary information being returned.
One of the things that you learn I guess.
The Database has a large collection of data points which there are many Recorded values.
Each Recorded value is mapped to a Record Kind which may have a Record Alias.
I have tried creating a Select() as an alternative but I just cant figure out how to construct the right Select and also keep the entity hierarchy correctly loaded. I.e. the related entities are loaded with unnecessary calls to the DB.
Does anyone has alternate solutions that may jump start me to solve this problem.
Ill add more detail if needed.
You are right. One of the slower parts of a database query is the transport of the selected data from the DBMS to your local process. Hence it is wise to limit this.
Every TimeDomainDataPoint has a primary key. All RecordValues of this TimeDomainDataPoint have a foreign key TimeDomainDataPointId with a value equal to this primary key.
So If TimeDomainDataPoint with Id 4 has a thousand RecordValues, then every RecordValue will have a foreign key with a value 4. It would be a waste to transfer this value 4 a 1001 times, while you only need it once.
When querying data, always use Select and select only the properties you actually plan to use. Only use Include if you plan to update the fetched included items.
The following will be much faster:
var result = dbContext.timeDomainDataPoints
// first limit the datapoints you want to select
.Where(datapoint => d.RecordValues.Any(rv => rv.RecordKind.RecordAlias != null))
.Where(datapoint => datapoint.Source == 235235)
.Where(datapoint => datapoint.timeData.time >= start
&& datapoint.timeData.time <= end)
.OrderByDescending(datapoint => datapoint.timeData.time)
// then select only the properties you actually plan to use
Select(dataPoint => new
{
Id = dataPoint.Id,
RecordValues = dataPoint.RecordValues
.Where(recordValues => ...) // if you don't want all RecordValues
.Select(recordValue => new
{
// again: select only the properties you actually plan to use:
Id = recordValue.Id,
// not needed, you know the value: DataPointId = recordValue.DataPointId,
RecordKinds = recordValues.RecordKinds
.Where(recordKind => ...) // if you don't want all recordKinds
.Select(recordKind => new
{
... // only the properties you really need!
})
.ToList(),
...
})
.ToList(),
TimeData = dataPoint.TimeData.Select(...),
...
});
Possible imporvement
The part:
.Where(datapoint => d.RecordValues.Any(rv => rv.RecordKind.RecordAlias != null))
is used to fetch only datapoints that have recordValues with a non-null RecordAlias. If you are selecting the RecordAlias anyway, consider doing this Where after your select:
.Select(...)
.Where(dataPoint => dataPoint
.Where(dataPoint.RecordValues.RecordKind.RecordAlias != null)
.Any());
I'm not really sure whether this is faster. If your database management system internally first creates a complete table with all columns of all joined tables and then throws away the columns that are not selected, then it won't make a difference. However, if it only creates a table with the columns it actually uses, then the internal table will be smaller. This could be faster.
your problem is hierarchy joins in your query.In order to decrease this problem create other query for get result from relation table as follows:
var items= ctx.timeDomainDataPoints.AsNoTracking().Include(dp =>dp.timeData).Include(dp => dp.RecordValues);
var ids=items.selectMany(item=>item.RecordValues).Select(i=>i.Id);
and on other request to db:
var otherItems= ctx.RecordAlias.AsNoTracking().select(dp =>dp.RecordAlias).where(s=>ids.Contains(s.RecordKindId)).selectMany(s=>s.RecordAliasGroup)
to this approach your query do not have internal joins.

Linq Remove results with ALL statement

Hi I'm trying to use Linq to remove "all" entities from a list.
Problem: I'm searching for users that have certain certificates in my database. Thing is that it returns them row by row.... But what I need to check is: If the user holds all the required certificates. This should be checked against my int array.
This is my array: [3,5,16], now I want to delete all user who does not have all three of those from the list. Name of the array in code is mandatory!
The listitems I get back looks like this
listitem.CertificateValue
listitem.Uid
listitem.NameOfPerson
So basicly for this example Peter has three rows in the list, in this case all the rows needed to stay in the list. But Philip only has 2 rows and hence both of these should be deleted since he does not fullfill the total search criteria.
Also copyOfMandatoryis just to not mess with the original collection and cause an expection(collection size changed).
foreach (var item in copyOfMandatory)
{
if (!mandatoryusers.All(i => mandatory.Contains(i.CertificateValue)
|| i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
}
UPDATE
RemoveAll works like a charm it the if statement that does not work as expected.
Doing this it does not take away any part of the list, I began wiht && instead of || but whne doing that it kills everything but the last person it encounters as long as he/she fullfills the search criteria.
Anyone have a hint on how to do this?
I would try something like that
var uIdToRemove = mandatoryusers.GroupBy(m => m.Uid)
.Where(g => mandatory.Except(g.Select(s => s.CertificateValue)).Any())
.Select(g => g.Key).ToList();
mandatoryusers.RemoveAll(x => uidToRemove.Contains(x.Uid));
Your All call is not granular enough: it is trying to ensure that ALL entries exist at all times... Not that all entries PER USER exist.
Try converting each entry to a dictionary:
var dict = new Dictionary<int, List<ItemType>>();
foreach (var mandatoryItem in mandatoryItems)
{
List<ItemType> itemTypeValue = null;
if (!dict.TryGetValue(mandatoryItem.Uid, out itemTypeValue)
{
itemTypeValue = new List<ItemType>();
dict.Add(mandatoryItem.Uid, itemTypeValue);
}
itemTypeValue.Add(mandatoryItem);
}
Now you have all ItemType at the key of Uid. From here, use LINQ:
mandatoryusers = mandatoryusers.Where(i => dict[i.Uid].All(x => mandatory.Contains(x.CertificateValue));
Your if All criteria is off.
if (!mandatoryusers.All(i => mandatory.Contains(i.CertificateValue)
|| i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
It needs to be with an && not an || and you should call Any() instead of All()
if (!mandatoryusers.Any(i => mandatory.Contains(i.CertificateValue)
&& i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
Hopefully I understood what your logic and question correctly.
Your if statement isn't correct (as you stated) - it's attempting to check whether all items contain a certificate with an id in mandatory or where the userid is the current item. What you should be doing is filtering by userid first and then checking the certificates.
This isn't the way I would do it, though. I'd group the results by User and then check the certificates
var usersWithAllCertificates = mandatoryUsers.GroupBy(mu => mu.Uid)
//Select the ones that have all 3 certificates
.Where(g => g.Select(u => u.CertificateValue)
.Intersect(mandatory).Count() == 3)
.Select(g => g.ToList());
The Intersect operator will combine the lists and the result will be the items that are the same in both lists. So, if the user has all 3 certificates (3, 5 and 16) the result of the intersect will be 3 items. The usersWithAllCertificates object will include all the users you want. This is explicitely selecting the values you want instead of removing the ones you don't want, which imo is a better way of going about it. Note that this assumes each user is only in the list once (i.e. only has 3 certificates)

Get a distinct list

I want to select a distinct list.
The following code is not working:
public IQueryable<BusinessObjects.Order> GetByBusinessId(Guid Id)
{
rentalEntities db = DataContextFactory.CreateContext();
List<Rental.BusinessObjects.Order> transformedList = new List<BusinessObjects.Order>();
foreach (Rental.DataObjects.EntityModel.Order item in db.Orders.Where(x => x.BusinessID == BusinessId).ToList())
{
transformedList.Add(OrderMappers.ToBusinessObject(item));
}
return( transformedList.AsQueryable()).Distinct();
}
Try this:
return Rental.DataObjects.EntityModel.Order item in db.Orders
.Where(x => x.BusinessID == BusinessId)
.Distinct()
.Select(item => OrderMappers.ToBusinessObject(item));
This should move the distinct operation to the underlying database call as it's applied before the query is materialized - this is more efficient as the duplicate rows aren't retrieved from the database server. If for some reason you don't want to do that, then check your equals implementation (as mentioned by Sorin)
You may want to check how your business objects implement Equals(), my guess is they are are different even if they have (let's say) the same ID.
You might like to try the DistinctBy() extension method from the MoreLinq library. This lets you easily control the exact semantics of how two objects are compared for distinctness. For instance:
return transformedList.AsQueryable().DistinctBy(orderBO => orderBO.OrderId);
http://morelinq.googlecode.com/files/morelinq-1.0-beta.zip

Categories