How to change a value without new projection in LINQ? - c#

I have the following LINQ query:
var source = from node in MyNods
select new
{
Id = node.Id,
Name = node.Name,
ParentId = node.ParentId, // Nullable
};
In the query above, the ParentId is nullable. Now I need a new result which is match the first one but with small change that if ParentId is null I want it to be 0.
I wrote this:
var source2 = from s in source
select new
{
Id = s.Id,
Name = s.Name,
ParentId = s.ParentId ?? 0, // Just change null values to 0
};
Can I implement that with a simpler way (I mean without the new projection) ?
Edit: The new projection is the same of the first one and both ParentId are nullable.

LINQ isn't ideal for executing side-effects on an existing collection. If that's what you want to do, you'd be better off doing:
foreach(var node in MyNods)
{
if(!node.ParentId.HasValue)
node.ParentId = 0;
}
If that's not the case, you're going to have to project. Your existing query is perfectly fine; the only way I can think of shortening it is:
var source2 = from s in source
select new
{
s.Id, s.Name,
ParentId = s.ParentId ?? 0
};
EDIT:
It appears you're trying to create an instance of a different type (i.e. with virtually the same properties as the source but with one specific property being non-nullable), so you can't escape creating instances of the new type and copying properties over. You might want to consider writing a 'real' (non-anonymous) type that represents what you want and get the source-type to provide a conversion-method. Then you can do:
var source2 = source.Select(s => s.ToNonNullableParentVersion());
EDIT:
From your edit, it now appears that you don't need a different type to represent the projected data since the 'coalesced' property is still meant to be nullable. If you don't want to mutate the existing collection and you don't like your current query, your best bet would still be to write a conversion method in the source-type.

Related

Is it possible to remove columns in select query

As the question suggests I want to remove the columns from a select query where in that column are empty.
var query = from a in ...
select new
{
A =(decimal?)null,
B =(decimal?)null,
C = a.Amount1
};
var query2 = from b in ...
select new
{
A = b.Amount2,
B = b.Amount3,
C = (decimal?)null
};
var query3 = query.Concat(query2);
Output:
query3=[0]{A=null, B=null, C=100.00}
[1]{A=100.00, B=50.25, C=null}
Expected Result:
query3=[0]{C=100.00}
[1]{A=100.00, B=50.25}
You can't do this. The result set has to contain items of the same type and even if fields are null they still have to be there.
You could not show them in your UI, but exactly how you do that will depend on the UI.
You can't. A class has a predefined set of fields (not to speak about ExpandoObject which has some compiler tricks going on). This is the same for anonymous type, which you use.
You can't just hide or remove fields which are not filled. What if you iterate over the instances and try to retrieve item.C, which was null and thus removed? That would normally give you a compiler error. How would .NET resolve that?
The only other thing you can do is put in two different types in your list (a list of objects, so untyped), a very bad idea in my opinion. Keep it like this. You could add an indicator which type the row is, to be able to test it easily.
So:
select new
{
Type = "A", // or "B"
A =(decimal?)null,
B =(decimal?)null,
C = a.Amount1
};
A LINQ query typically outputs objects of a common type, so each object has to have the same base type (and the columns associated. The only way to concatenate objects of different types is to cast them to object:
var query = from a in ...
select new
{
C = a.Amount1
};
var query2 = from b in ...
select new
{
A = b.Amount2,
B = b.Amount3,
};
var query3 = query.Cast<object>().Concat(query2.Cast<object>());
but since they're anonymous you'll have to use dynamic to access them since you won;t be able to cast back to the original type, so you'll end up with something like this:
Console.WriteLine(((dynamic)(query3[0])).C);
Console.WriteLine(((dynamic)(query3[1])).A);
or at best:
dynamic list = query3.ToList();
Console.WriteLine(list[0].C);
Console.WriteLine(list[1].A);
but in any case you'll lose compile-time type safety.

Navigational Properties remaining null, even after filtering them out

I'm hitting a brick wall here, things are happening and they seem counter-intuitive.
I have a collection of entities, around 40k, which I am trying to convert to view models.
I am using the following code to do this.
var ents= (from ent in entities.ents
where ent != null && ent.Prop1 != null && ent.Prop2 != null
select ent).ToList();
ConcurrentBag<VmEnt> filtered = new ConcurrentBag<VmEnt>();
Parallel.ForEach(ents.AsParallel(), ent =>
{
var vm = new VmEnt
{
Name = ent.Name,
Prop1 = new VmProp1
{
Id = ent.Prop1.Id,
Name = ent.Prop1.Name,
},
Prop2= new VmProp2
{
Id = ent.Prop2.Id,
Images = ent.Prop2.Images.Select(y => y.ImageUrl).ToList()
},
Prop3= ent.Prop3.Select(y => new VmProp3
{
Id = y.Id,
Name = y.Name
},
};
filtered.Add(vm);
});
The issue I'm having is that in the parallel loop there are null references for ent.Prop1, even after these should have been filtered out. Am I missing something really simple? Or have I done something wrong?
Is entities an EntityFramework context?
Are Prop1, Prop2 and Prop3 navigational properties?
If this is the case you need to load them also.
You can do this by using the Include method of the DbSet in the linq query (eager loading), using the Load method to load them later or you can enable lazy loading so that these navigational properties are loaded when you access the property. I personally prefer the first two methods depending on the use case.
Also you can simplify this code by creating the view model on the select of the linq query. This way you wont need to create a list just for iteranting it... you can even use AsParallel to create the view model objects.

Assign a column value if it throws an exception in Linq query

I have a query where one property is a path "/Primary/secondary/tertiary/.../.../"
My task is to split this path by the slashes, so every sub-path can be assigned as a property in the query result.
The problem is, that the length varies. Some paths have a post-split array length of 1, some of 7. So I need to have 7 different category columns:
var result = MySource.Where(ms => ...)
.Select(ms => new {
ID = ms.ID,
Name = ms.Name,
Category1 = ms.Path.Split('/')[0],
Category2 = ms.Path.Split('/')[1] //exception
.... //exception
Category7 = ms.Path.Split('/')[6] //exception
});
After the path gets split, the resulting array is of various length (1 - 7) leading into an ArgumentOutOfRangeException. How can I circumvent this exceptions?
I have tried using the nullcoalescence operator ms.Path.Split('/')[1] ?? "N/A", which did not help, because there is no result but an exception thrown. Because of this every shorthand-if statement will fail as well.
Is there a way to catch the exception (wrap in try catch block?) so I can assign a default value if the array is out of bounds?
Your modeling seems a little broken. Instead of a flattened set of properties, populate a single collection. Something like this:
Select(ms => new {
ID = ms.ID,
Name = ms.Name,
Categories = ms.Path.Split('/')
})
Going a step further, you can create an actual (non-anonymous) model to hold this information, encapsulating the logic of category range checking. Something like:
Select(ms => new SomeObject(
ms.ID,
ms.Name,
ms.Path.Split('/')
))
Then in SomeObject you can have all sorts of logic, for example:
In the constructor you can perform input checking on the values, including the count of categories supplied, to ensure the object is valid.
You can keep the collection of categories private and expose properties for 1-7 if you really need to, which internally perform this check. (Though I really don't recommend that. It creates an unnecessary point of change for something that's already handled by a collection, indexing values.) Something like:
public string Category1
{
get
{
if (categories.Length < 1)
return string.Empty;
return categories[0];
}
}
Maybe throw an exception instead of returning an empty string? Maybe do something else? The point is to encapsulate this logic within an object instead of in a LINQ query or in consuming code.
you can do
Category7 = ms.Path.Split('/').ElementAtOrDefault(6) ?? "N/A",
see demo: https://dotnetfiddle.net/4nTBhq
ElementAtOrDefault return the element at index (for example 6, like [6]) but if out of bound return null.
optimized, without calling Split multiple times:
.Select(ms => {
var categories = ms.Path.Split('/');
return new {
ID = ms.ID,
Name = ms.Name,
...
Category7 = categories.ElementAtOrDefault(6),
};
})

How to Seed a table with Index Unique Data Annotation based on 2 columns with one nullable

I want to create a table where data uniqueness is based on multiple columns (2 or 3) but one of them can be null.
For example:
FRUIT WEIGHT UNIT
Apple
Apple 1 Kg
Apple 2 Kg
Orange
Orange 1 Kg
Orange 2 Kg
will all be considered as unique entries.
Can it be done with EF 6.1 Data Annotations?
I beleive I achieved that like this:
[Required]
[Index("UniqueIndx", 1)]
public string Name { get; set; }
[Index("UniqueIndx", 2)]
public float? Weight { get; set; }
[Index("UniqueIndx", 3, IsUnique = true)]
public FormulUnit? Unit { get; set; }
which produces:
public override void Up()
{
AlterColumn("MyDb.Fruits", "Weight", c => c.Single());
AlterColumn("MyDb.Fruits", "Unit", c => c.Int());
CreateIndex("MyDb.Fruits", new[] { "Name", "Weight", "Unit" },
unique: true, name: "UniqueIndx");
}
From my understanding of Up method created by migration is that uniqueness is based on all 3 columns, not just last one I wrote that annotation to. This is ok for me, this is actually what I want.
I still have the problem of seeding that table. I'm getting errors on AddOrUpdate method like:
System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Nullable`1[System.Single]' and 'System.Single'.
for:
context.Fruits.AddOrUpdate(
p => new {p.Name, p.Weight, p.Unit}, fr1, fr2, fr3
);
Am I missing something?
Thanks
I just debugged the EF source code, and the code that throws the error is inside AddOrUpdate.
Here is part of the code excerpt that causes error.
var matchExpression
= identifyingProperties.Select(
pi => Expression.Equal(
Expression.Property(parameter, pi.Single()),
Expression.Constant(pi.Last().GetValue(entity, null))))
Or you can replicate the error by doing this.
var fr1 = new Fruit { Weight = 1 };
var epe = Expression.Property(Expression.Constant(fr1), "Weight");
var ec = Expression.Constant(fr1.Weight);
var ee = Expression.Equal(epe, ec);
I'm not sure why Float? is not acceptable by Expression.Equal, perhaps somebody else can explain.
But, if you can just use Add or manually checking whether to add or update, instead of AddOrUpdate, that will work.
UPDATE 2:
Below is what I thought my mistake and gave a possible sollution. But then again I was wrong. Although below code adds new records to databse with index based on 3 criteria, because it creates new objects of anonymous type during process, you loose foreign key relations.
If you have a parent class, let's call it Grocery which holds many Fruits, seed method will not update those relations with given code below.
Back to work agin...
My logic is wrong.
If we have a unique index based on multiple criteria, how one can tell which record to update? A record with one different criteria out of 3 might be very well a new record, or maybe it's an old one to be updated but that is not something compiler can tell.
Each record need to be added manualy.
Thank you Yuliam.
UPDATE:
For those of you who fall into same dilema, here is how I solved this situation (with help of other Stackoverflow posts)
First, you must know that Fruit entity has a base entity with ID, so that's why you don't see it here.
Then to understand better here are steps needed to take:
Get your DbSet from context into an anonymous type list, stripped
from properties that are not concern for comparison.
Put that anonymous type list into a strongly typed list (of your
entity type, in this example: Fruit)
Make a new list where you only select from your seed objects that don't exist in database.
Add all of those new objects to context (and then context.save() )
var SeedFruits = new List { fr1, fr2, fr3 };
var result = from a in context.Fruits
select new { a.Name, a.Weight, a.Unit };
List DBFruitsList = result.AsEnumerable()
.Select(o => new Fruit
{
Name = o.Name,
Weight = o.Weight,
Unit = o.Unit
}).ToList();
var UniqueFruitsList =
from ai in SeedFruits
where !DBFruitsList.Any(x => x.Name == ai.Name && x.Weight == ai.Weight && x.Unit == ai.Unit)
select ai;
foreach (Fruit fruittoupdate in UniqueFruitsList)
{
context.Fruits.Add(fruittoupdate);
}

LINQ - Add property to results

Is there a way to add a property to the objects of a Linq query result other than the following?
var query = from x in db.Courses
select new
{
x.OldProperty1,
x.OldProperty2,
x.OldProperty3,
NewProperty = true
};
I want to do this without listing out all of the current properties of my object. There are many properties, and I don't want to have to update this code whenever I may change my class.
I am still learning with LINQ and I appreciate your suggestions.
Add it with partial classes:
public partial class Courses
{
public String NewProperty { get; set; }
}
Then you can assign it after you've created the object.
I suppose you could return a new object composed of the new property and the selected object, like this:
var query = from x in db.Courses
select new
{
Course = x,
NewProperty = true
};
eking's answer will be the most straightforward approach.
If that doesn't work for you (because you need to pass the results around or whatever), and assuming the class you're dealing with already defines the property you want to set, you could create a copy constructor or factory method that takes an existing instance plus the value of the property you want to set:
var query = from x in db.Courses
select new Course(x, valueOfNewProperty);
Alternatively, if Course doesn't define the property, you could subclass it and use the same approach:
var query = from x in db.Courses
select new CourseWithExtraProperty(x, valueOfNewProperty);
(obviously, pick a better name for your subclass)
Again, though, unless you really need to do this, stick with eking's solution.
ServiceStack has a built-in way to handle this with the PopulateWith method.
Here's a code example.
foreach (var item in results)
{
var test1 = new ItemDto().PopulateWith(item);
test1.extraField1 = "extra";
response.Add(test1);
}`
And if you're not using ServiceStack, you can always use AutoMapper.
CreateMap<Foo, Bar>().ForMember(x => x.ExtraBarProperty, opt => opt.Ignore());
If you are looking to dynamically add a property to an object this could be a solution.
This is what has worked for me, I also had a concern and it was what happened with those domain objects that had many properties, the maintainability for any changes in the object was absurd, I managed to build an implementation with LINQ - ExpandObject - Reflection, which helped to keep my object dynamic and only add the additional properties that my view logic required.
var expandedModel = db.Courses.Select(x =>
{
dynamic expandObject = new ExpandoObject();
expandObject.NewProperty= $"PropertyValue";
foreach (var property in x.GetType().GetProperties())
{
((IDictionary<string, object>)expandObject).Add(property.Name, property.GetValue(x));
}
return expandObject;
}).ToList();

Categories

Resources