I have a list of POCO objects, why is it that the following code:
elements.Where(x => x.Param1 == "M").Select(x => x.Param2= "").ToList();
(TL;DR; sets param2 = "" on every element which param1 equals M)
updates the enumerable while this one:
elements.Where(x => x.Param1 == "M").Select(x => x.Param2= "");
does not update it?
Notice that i'm doing neither elements = elements.Where... nor var results = elements.Where...
Your second code snippet without ToList is just a query. You need to iterate to actually execute it. Calling ToList executes the original query and since in your Select you are modifying a property of an object, you see the effect (or a side effect) in your original list. It is related to parameter passing in C#. Since your lambda expression in Select is an anonymous method which is receiving a parameter object of the list. Later when you modify one of it's property you see the effect.
Similarly if you try to set the object to null you will not see the side effect.
.Select(x => x = null).ToList();
Related
Im facing a very strange problem. I have a list and am trying to update one of its inner structure field from the following for loop.
foreach (var item in AppUserDetail )
{
int AppUserID = item.AppUserId;
List<int> list = templateCategory.AppUserTemplateCategorySecurityGroupXrefs.Where(a => a.AppUserId == AppUserID).Select(b => b.SecurityGroupId).ToList();
item.AppUserSecurityGroupXrefs.Where(a => list.Contains(a.SecurityGroupId)).Select(b => b.SecurityGroup).Select(c => c.Selected = true);
bool s = item.AppUserSecurityGroupXrefs.Where(a => list.Contains(a.SecurityGroupId)).Select(b => b.SecurityGroup).Select(c => c.Selected).FirstOrDefault();
}
return AppUserDetail;
On the third line in loop, as you can see Im changing a value to True using select itself. When I run the application the value looks unchanged on the returning object.
But if we execute the same line in immediate window by putting a break point inside the loop, it update the Data and it reflects at the returning object AppUserDetail. Am really getting confused why its only update from Immediate window, not from direct code execution.
item.AppUserSecurityGroupXrefs.Where(a =>
list.Contains(a.SecurityGroupId)).Select(b =>
b.SecurityGroup).Select(c => c.Selected = true);
item.AppUserSecurityGroupXrefs.Where(a => list.Contains(a.SecurityGroupId)).Select(b => b.SecurityGroup).Select(c => c.Selected = true);
Select is lazy, since nothing enumerates the enumerable it returns nothing will be executed. The immediate window implicitly enumerates the result to display it, but that will not normally happen in the built code.
(And even if it were, the purpose of Select is to return a new object – a projection of the object passed in – not to modify something. Therefore weirdness is likely.)
I think you really want an Enumerable.ForEach extension which is designed to process the results of a LINQ expression. Your own implementation is very easy.
I want to query an array of objects "sortedData", where each object has two values (ItemId, Sort), for a specific ItemId and set the 'Sort' value. Like this below but this isn't the correct linq syntax.
var sortedData = db.Fetch<object>("SELECT ItemId, Sort FROM CollectionItems WHERE CollectionId = #0", collectionId);
dataWithSort = db.Fetch<OrganizationForExportWithSort>(TpShared.DAL.StoredProcedures.GetOrganizationsForTargetListUI(clientId, organizationIdList));
foreach(OrganizationForExportWithSort export in dataWithSort)
{
export.Sort = sortedData.Select("Sort").Where(sortedData.ItemId == export.Id);
}
As I understand it, you want the Sort property from the item that matches that particular ID. This being the case, you have a few problems with what you've written:
"Where" and "Select" both take Lambda expressions, not property names and expressions, so the code snippet you provide shouldn't compile.
"Where" and "Select" both return collections (even if there's only one item that actually matches the "Where" filter; in fact, even if no items in the collection match the condition in the "Where" clause it'll still return a collection, albeit an empty one). Think of LINQ Select more in terms of running a transform on a collection and LINQ "Where" as applying a filter to one.
As a general rule for LINQ queries, if possible, you should actually run "where" before "select" (filter first, then apply some kind of transform to the remaining items).
In this case, I think you actually just want one item, so you can actually use "FirstOrDefault" instead of "Where." This will leave you with a single .NET object. This is analogous to the TOP 1 restriction in SQL. Once you have the .NET object you can retrieve the property from the object itself.
Try this:
foreach(OrganizationForExportWithSort export in dataWithSort)
{
export.Sort = sortedData.FirstOrDefault(data => data.ItemId == export.Id)?.Sort;
}
The "?" is a new C# feature that will try to call .Sort on the object if (and only if) the query succeeded in finding an item with that ID. If it doesn't it'll just return null.
Have you tried Linq sorting?
var sortedData = db.Fetch<object>("SELECT ItemId, Sort FROM CollectionItems WHERE CollectionId = #0", collectionId);
dataWithSort = db.Fetch<OrganizationForExportWithSort>(TpShared.DAL.StoredProcedures.GetOrganizationsForTargetListUI(clientId, organizationIdList));
// create a list ordered by fields
var sorted = dataWithSort.OrderBy(o => o.SomeField).ThenBy(o => o.OtherField);
The o in the lambda stands for object...
I will add my voice to the chorus of folks saying to read up on some good linq tutorials. Start Here
I've started with the following statement.
return part
.Descendants("DataMapping")
.Select(element=>serializer.Deserialize(element.CreateReader()) as Mapping);
Then, it turned out that a property in Mapping isn't set via the serializer so I had to update it manually. Luckily, it's the same value all the way, so the following resolved my issue.
return part
.Descendants("DataMapping")
.Select(element => serializer.Deserialize(element.CreateReader()) as Mapping)
.Select(element => new Mapping(element, "blopp"));
However, I can't stop thinking that it's a bad design. I'm creating an object twice, to begin with. Also, I had to add a custom constructor, increasing the complexity of the code base.
I haven't found any methods like .ForEach(e=>e.X = ":)" and I wonder if it's at all possible to LINQ through a serializably created objects and set a value of a field at the same time.
I'd like the expression to behave like the below example shows. I can't use a second Select() because then it'd produce an array of String type and I need to keep the original type, only slightly affecting its properties.
return part
.Descendants("DataMapping")
.Select(element => serializer.Deserialize(element.CreateReader()) as Mapping)
.SomeCommand(element => element.Extra = "blopp"));
You can always use a statement lambda where expression lambda doesn't suit your needs:
return part
.Descendants("DataMapping")
.Select(element =>
{
var obj =serializer.Deserialize(element.CreateReader()) as Mapping);
obj.Extra = "blopp";
return obj;
});
An alternative would be creating a list and using ForEach method:
return part
.Descendants("DataMapping")
.Select(element => serializer.Deserialize(element.CreateReader()) as Mapping)
.ToList()
.ForEach(element => element.Extra = "blopp");
var Result = addressContext.Address_Lookup
.Where(c => c.Address_Full.ToUpper().Contains(term.ToUpper())
|| c.Address_Full.ToUpper().Contains(TermModified.ToUpper()))
.Select(e => new {
id = e.Address_ID,
label = e.Address_Full,
value = e.Address_Full })
.ToList();
To ensure search will be non-case sensitive I am using ToUpper().
I am searching for something like Jimmy (with a capital J). jimmy (all lower case) doesnt catch? why?
Since you're using entity-framework, a linq-to-sql framework, you're actually trying to make the database perform a .ToUpper rather than performing one in-memory as you would if running through an IEnumerable. If the query translation in your framework doesn't support the function, it either won't be used or throw an Exception.
You can generally predict such behaviour by checking whether you're calling a function against an IQueryable object, which queues all calls as an expression tree for translation, or an IEnumerable, which uses foreach and yield returns to handle evaluation. Since the Linq functions are extension methods, polymorphism doesn't apply here.
If you're not worried about the performance hit of getting EVERY entry from that table in-memory, add a .AsEnumerable() call, and your functionwill evaluate on localized data.
var Result = addressContext.Address_Lookup
.AsEnumerable()
.Where(c => c.Address_Full.ToUpper().Contains(term.ToUpper())
|| c.Address_Full.ToUpper().Contains(TermModified.ToUpper()))
.Select(e => new
{
id = e.Address_ID,
label = e.Address_Full,
value = e.Address_Full
})
.ToList();
I am new to EF and LINQ.
The following two pieces of code work:
dbContext.Categories.Where(cat => [...big ugly code for filtering...] );
&
dbContext.Products.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories.Where(
cat => [...big ugly code for filtering...] );
But I want somehow to create only one, reusable, expression or delegate for my filter. I have the following:
private static Expression<Func<Category, bool>> Filter(filter)
{
return cat => [...big ugly code for filtering...] ;
}
but I cannot use it in SelectMany.
I am aware that:
Where clause of standard query accepts Expression<Func<Category,bool>> and returns IQueryable<Category>
Where clause of SelectMany accepts Func<Category,bool> and returns IEnumerable<Category>.
What is the best way to accomplish this? Are any tricks here?
PS: I want in the end to get all categories of a product.
It looks like you're trying to use SelectMany as a filter. SelectMany is used to flatten a collection of collections (or a collection of a type that contains another collection) into one flat collection.
I think what you want is:
dbContext.Products.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories)
.Where(filter);
In which case you can reuse the same expression to filter.
EDIT
Based on your updated question it looks like you are applying Where to an IEnumerable<T> property, so the compiler is binding to IEnumerable.Where which takes a Func instead of an Expression.
You should be able to just call AsQueryable() on your collection property to bind to IQueryable.Where():
dbContext.Products.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories
.AsQueryable()
.Where(filter);
The next option would be to compile the expression to turn it into a Func:
dbContext.Products.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories
.Where(filter.Compile());
But it wouldn't surprise me if the underlying data provider isn't able to translate that to SQL.
All you need to do is call the Filter function before executing the query and store it in a local variable. When the query provider sees a method it attempts to translate that method into SQL, rather than executing the method and using the result. Whenever it encounters a local variable it doesn't attempt to translate it into SQL but rather evaluates the variable to its value, and then uses that value in the query.
As for the problems that you're having due to the fact that the relationship collection isn't an IQueryable, it's probably best to simply approach the query differently and just pull directly from the categories list instead:
var filter = Filter();
dbContext.Categories.Where(filter)
.Where(cat => cat.Product.PROD_UID == 1234);
After analyzing in more detail the SQL generated by EF, I realized that having the filter inside SelectMany is not more efficient.
So, both suggestions (initial one from DStanley and Servy's) should be ok for my case (many-to-many relation between Categories and Products)
/* 1 */
dbContext.Products.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories)
.Where( Filter ); // reuseable expression
this will result into a INNER JOIN
/* 2 */
dbContext.Categories.Where( Filter ) // reuseable expression
.Where(c => c.Products.Where(prod.PROD_UID == 1234).Any());
this will result into a EXISTS (sub-select)
The execution plan seems to be absolutely identical for both in my case; so, I will choose for now #2, and will keep an eye on performance.