How to .Select() and change a single property at the same time? - c#

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");

Related

QueryOver ProjectionList with different root entity types

I'm having issues trying to reuse ProjectionLists in NHibernate QueryOvers. I can't work out how to reuse things for different root entities.
Object model is roughly represented as:
Breakfast one to many Pastry many to zero-or-one Coffee
The two separate queries are roughly:
session.QueryOver<Breakfast>()
.Where(b => b.Id == searchId)
.Inner.JoinQueryOver(b => b.Pastries, () => pastry)
.Left.JoinAlias(p => p.Coffee, () => coffee)
.Select(projections)
.TransformUsing(Transformers.AliasToBean<TargetDto>())
.List<TargetDto>();
session.QueryOver<Coffee>()
.Where(c => c.Id == searchId)
.Inner.JoinQueryOver(c => c.Pastries, () => pastry)
.Select(projections)
.TransformUsing(Transformers.AliasToBean<TargetDto>())
.List<TargetDto>();
The common projections I'm trying to reuse looks like this:
var projections = Projections.ProjectionList()
.Add(Projections.Property(() => pastry.Name, () => target.PastryName))
.Add(Projections.Property(() => coffee.Name, () => target.CoffeeName));
These projections, using the aliases, work fine for the first query (root: Breakfast), because they're not trying to pull off properties that are on that root entity. On the second query (root: Coffee), it explodes saying it can't find 'coffee.Name' on Coffee, because it doesn't like the alias. The QueryOver(() => coffee) syntax doesn't help because it doesn't actually register 'coffee' as an alias, it just uses it for type inference. Oh bloody hell that was the problem. There's a stupid piece of application infrastructure that is breaking the alias syntax to not actually use the alias version underneath.
The second query wants the projections to look like:
var projections = Projections.ProjectionList()
.Add(Projections.Property(() => pastry.Name, () => target.PastryName))
.Add(Projections.Property<Coffee>(c => c.Name, () => target.CoffeeName));
However this is now incompatible with the first query.
Is there any way of making this work, so the I can project properties without knowing what the root entity type is?
I think all you need to do is assign the coffee alias in the session.QueryOver<Coffee> call:
Coffee coffee = null;
session.QueryOver<Coffee>(() => coffee)
/* etc */
The below might be completely unrelated to what you're doing, but I figured I'd include it in case anyone else is writing code that passes around QueryOver aliases.
I'd add a word of caution-- reusing aliases across different queries like this can be a little dangerous.
NHibernate takes the expression () => coffee and grabs the name of the alias you're using from the expression (in this case, "coffee") and then uses it in the generated SQL as an alias. This means that depending on how your code is structured, shared projections like this could break if alias names change.
For example, say you had the following method to return some shared projections:
public ProjectionList GetSharedProjections()
{
Coffee coffee = null;
TargetDTO target;
var projections = Projections.ProjectionList()
.Add(Projections.Property(() => coffee.CoffeeName)
.WithAlias(() => target.CoffeeName));
return projections;
}
Then you had some code calling your helper method:
session.QueryOver<Coffee>(() => coffee)
.Select(GetSharedProjections());
Everything will work fine-- as long as your aliases match. The second anyone changes either of the aliases though, the query will fail.
You might be tempted to pass in an alias to a method like this:
public ProjectionList GetSharedProjections(Coffee coffeeAlias)
{
/* Same as above except with "coffeeAlias"
}
And then pass in your alias:
session.QueryOver<Coffee>(() => coffee)
.Select(GetSharedProjections(coffee));
But this won't work either. Remember that NHibernate is grabbing the name of the alias and using it directly in the generated SQL. The above code will try to use both "coffee" and "coffeeAlias" in the generated SQL and will fail.
One way to properly do this (without just hoping nobody changes alias names) is to pass around expressions and use those to reconstruct property names with the correct aliases.
You'd create a helper method that builds property access using an alias and a property:
public static PropertyProjection BuildProjection<T>(
Expression<Func<object>> aliasExpression,
Expression<Func<T, object>> propertyExpression)
{
string alias = ExpressionProcessor.FindMemberExpression(aliasExpression.Body);
string property = ExpressionProcessor.FindMemberExpression(propertyExpression.Body);
return Projections.Property(string.Format("{0}.{1}", alias, property));
}
Then, you could change the GetSharedProjections method to take an alias in the form of an expression:
public ProjectionList GetSharedProjection(Expression<Func<Coffee>> coffeeAlias)
{
TargetDTO target = null;
var projections = Projections.ProjectionList()
.Add(BuildProjection<Coffee>(coffeeAlias, c => c.CoffeeName))
.WithAlias(() => target.CoffeeName);
}
Now, calling your method would look like this:
session.QueryOver<Coffee>(() => coffee)
.Select(GetSharedProjections(() => coffee));
When someone changes your alias name you're covered. You can also safely use this method among many queries without worrying about what the name of the alias variable actually is.
Disclaimer: The following is a link to my personal blog
You can find more information about building QueryOver queries this way here.

Logic behind updating object List using linq

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();

EF: how to reuse same filter in both standard query and selectMany

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.

Insert Linq query part inside existing query

Having the following variable which stores an IQueryable:
var mainQuery = session
.Query<Employee>()
.Select(e => new
{
e.Name,
e.Address
});
And a method which takes that IQueryable as parameter.
public DataTable GetData(IQueryable query)
{
...
}
How can I write code inside GetData() that adds OrderBy() before the Select()?
Final value of query should look like it was built using following Linq expression:
var query = session
.Query<Employee>()
.OrderBy(e => e.Age)
.Select(e => new
{
e.Name,
e.Address
});
This is required because if I add the OrderBy() after Select() I will only be able to sort by the members of the anonymous type (name, address). If an employee would also have and age, I could not sort by it when placing OrderBy() after Select().
Thanks for your help!
UPDATE:
GetData has no knowledge of the structure of the query except that it ends with a Select.
And it has to have that exact signature public DataTable GetData(IQueryable query) - no extra parameters.
What is required it to modify the existing query inside the method and add OrderBy before the Select.
When there will be a correct answer I will accept and vote for it.
Why not just apply the select() after the call to GetData()?
var mainQuery = session.Query<Employee>();
this.GetData(mainQuery);
mainQuery.OrderBy(x => x.Age)
.Select(x => new
{
x.Name,
x.Address
});
Linq expression trees are immutable (source). You have to create a copy of the tree in parts to modify it.
Update: Keep in mind your pattern is trying to separate data access from presentation (or at least that is how it reads). Ordering is a matter of presentation. You may have multiple clients wanting to use GetData to fetch data but each of those clients might want the data to be sorted differently. Your original query projected after the call to GetData anyway so it makes sense to order with the projection.
Now if you REALLY want to order inside the method without changing its contract, you have to walk the expression tree of the linq query and rebuild it from scratch injecting the ordering in the right place. Expression trees are immutable and cannot be modified in-place.
Consider creating ViewModel or DTO for Employee and not to pass anonymous objects around. But anyway you can pass selector into GetData and apply it after sorting Employee
var mainQuery = session.Query<Employee>();
GetData(mainQuery, e => new { e.Name, e.Address });
//object is used because you create anonymous objects and pass them around
public DataTable GetData(IQueryable query,
Expression<Func<Employee, object>> selector)
{
return query.OrderBy(e => e.Age)
.Select(selector)
//...
}

Using a params array of linq Expressions

In EF, when you want to include a navigation property in the result of a query, you use Include(). Since some queries require calling this more than once, I tried to create a generic wrapper around this concept:
public IQueryable<T> FindAll<P>(params Expression<Func<T, P>>[] predicates) where P : class {
var entities = AllEntities();
foreach (var p in predicates) entities = entities.Include(p);
return entities;
}
And call it as such:
var customers = FindAll(q => q.Orders, q => q.Invoices, q => q.Contacts);
Questions:
The function compiles, but when I call it: "The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly." What am I doing wrong?
If I get it to work, can I call Include() the first time: var customers = FindAll(q => q.Orders, q => q.Invoices); and then again later in a separate step: customers = customers.Include(p => p.Invoices); or will that result in poor performance such that I should do the includes in one go?
EDIT:
JonSkeet's answer is correct, of course, and yet there is this solution which seems to do what I want. Not sure why it works however. Is it because of the Aggregate() function?
Fundamentally, I think you've got a problem - assuming that q.Orders, q.Invoices and q.Contacts return different types, you can't express what you want to happen. You probably want something like:
entities.Include<Parent, Order>(p => p.Order);
entities.Include<Parent, Invoice>(p => p.Invoices);
entities.Include<Parent, Contact>(p => p.Contacts);
... so you want a different value for P for each predicate. But you're calling a single method, providing a single type argument for P.
It's not clear that this is really giving you much benefit anyway... couldn't you just write:
var customers = AllEntities().Include(q => q.Orders)
.Include(q => q.Invoices)
.Include(q => q.Contacts);
I'd say that's clearer and it gets round the "multiple type parameters" problem.

Categories