Removing items from an object where linked model has 0 items - c#

How to I check if a nested model object, has any items.
Ie. if I have an object/viewmodel:
public class CarViewModel
{
public string Type { get; set; }
public long ID { get; set; }
public virtual IQueryable<Feature> Features { get; set; }
}
public class Feature
{
public string Offer { get; set; }
public decimal Rate { get; set; }
public virtual CarViewModel CarViewModel { get; set; }
}
...and it is populated as follows - so that 1 car object has 2 additional features, and the other car object, has no additional features:
[
{"Type":"SoftTop","ID":1,
"Features":
[{"Offer":"Alloys","Rate":"500"},{"Offer":"Standard","Rate":"100"}]},
{"Type":"Estate","ID":2,
"Features":[]}
]
So in my code, I had "Cars" populated with the data above:
foreach (var car in Cars)
{
if (!car.Features.Any())
{
car.Type = "Remove";
}
}
However, I get the message: This method is not supported against a materialized query result. at the if (!car.Features.Any()) line.
I got the same error when trying if (car.Features.Count()==0)
Is there a way of checking if the number of Features is 0?
Or is there a linq way of removing any items from the object, where the number of features is 0?
Thank you,
Mark
UPDATE
I changed the viewModel to use IEnumerable and then the following:
cars=cars.Where(x => x.Feature.Count()>0).ToList();
That seems to work - although I'm not 100% sure. If anyone can say whether this is a "bad" fix or not, I'd appreciate it.
Thanks, Mark

Try fetching the results first then checking the count
car.Features.ToList().Count

I don't think there any anything wrong with the fix - when you're using IQueryable<T> that came from a Linq to DB (L2S, Entity Framework, etc) you pretty much have to materialise it before you can use things like Any() or Count() when you ask for these things inside foreach.
As to why this is - I actually am not 100% certain and I believe that the error is a bit misleading in this respect, but I think that what it's complaining about is that neither Cars not car.Features() has actually been fully evaluated and run yet (i.e you are only starting to hit the database at the point when you go foreach ... in your code because it's IQueryable<T>).
However on a broader note I'd recommend you not use IQueryable<T> in your Viewmodels, much safer to use IEnumerable<T> - no chance of accidentally setting off a database access when rendering your view, for example.
And also when you are returning data from your DataLayer or wherever, a good rule of thumb is to materialise it as quickly as possible so as to be able to move on with an actual list of actual things as opposed to a "promise to go and look" for certain things in the database at some unspecificed point in the future :) So your DataLayers should only ever return IEnumerable<T>'s
You can always cast an IEnumerable to IQueryable if for some reason you need to...

Related

Should I use a set to prevent items being added more than once to the collection by mistake?

Please see the code below:
public class Customer
{
private readonly IHashSet<Order> _orders = new Set<Order>();
public string FirstName { get; set; }
public string LastName { get; set; }
public string Province { get; set; }
public IEnumerable<Order> Orders
{
get { foreach (var order in _orders) yield return order; }
}
internal void AddOrder(Order order)
{
_orders.Add(order);
}
}
I am trying to decide whether the _orders property should be a List (which I would normally use) or a Set. The only reason a duplicate order would be added is if the client code added a duplicate by mistake. Does that warrant the use of a Set? Is that a valid use case for a Set?
Sure, this is a valid use case, if duplicate orders are not allowed.
Since you expose IEnumerable<Order> instead of IList<Order> you don't seem to be interested in keeping the insertion order, or let the caller use it like a list or array. So a HashSet<T> is perfect here. The database is the best place to ensure that no duplicates are added, but it's not wrong to check that also at client-side. On this way you can handle bugs early and report them in your logs.
public class Customer
{
private readonly HashSet<Order> _orders = new HashSet<Order>();
public IEnumerable<Order> Orders => _orders;
// ...
internal void AddOrder(Order order)
{
_orders.Add(order);
}
}
Note that you should override Equals+GetHasCode in Order, implement IEquatable<Order> or pass a custom IEquatable<Comparer> to the HashSet constructor.
You have tagged your question with domain-driven-design tag, so the answer should be given in this context.
There is a rule in DDD called "make explicit what is implicit". If you don't want to have duplicates in your orders, in my opinion you should add some simple code that makes this rule explicit. It can be just a simple vetoIfOrderIsDupicated private method or something similar. It will give you a chance to communicate your team mates (or even you in the future) that there is a rule which should not be broken. If you just change this collection to Set, it doesn't carry any information WHY this is a Set. In the future someone from your team (or you personally) will refactor this code for example from performance perspective and will change Set to some high performance List, but will loose the rule.
So answering your question: I recommend to make the rule explicit. And collection can be Set if you want.
Additionally and not correlated to question, I don't know your domain, but it seems to me, that Order is another Aggregate Root. If yes, you shouldn't reference both Aggregate Roots. Instead you should reference IDs of AggR. So _orders should be collection of OrderID.

Removing an item from a list doesn't remove from the saved object in Entity Framework/MVC

I have an entity that needed a list of type int. Due to this being an internal tool that only I would use, I didn't want to spend a lot of time making a UI/view to edit the list and I sort of cheated.
So, I have the following class:
myitem.cs
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
virtual public ICollection<Size> Sizes {get;set;}
size.cs
public int ID { get; set; }
public int Size { get; set; }
Between the controller and view controller, I did some funky bits. I had a single text field in the view controller called "Sizes" and I then split the input on a comma to an array, and assign the list to Sizes.
This works perfectly and as expected.
string[] sizes = model.sizes.Split(',');
myitem.size = new List<sizes>();
foreach (string item in sizes)
{
myitem.size.Add( new sizes {Size=int.Parse(item)});
}
In the edit one, I find the object, and create a new text string that basically gets all of them and this also works.
In the edit saving controller, no matter what I try, it seems to append. So, I basically did the following:
MyItem myitem = db.myitems.find(id);
...auto mapper stuff from viewmodel to model...
myitem.sizes=null;
...call same bits as create to split and add to sizes...
db.savechanges();
However, I am now finding that whatever I try to do in edit, it simply adds to the list in addition to what is already there - I can't seem to find a way to remove it.
I have tried many different things (instead of = null, foreach and remove(), and a few others) without much luck.
In the end, I don't think this is the best approach at all as I am going to end up dropping the items and recreating them by the thousands for the sake of saving a few minutes, so, I am going to create a DBSet for sizes and do an ajax interface to list/delete/add them separate to the main model. (If there is an easy way, please let me know?!)
However, the fact that this didn't work has annoyed me and I was wondering if anyone knows why?

Exception on Inner LINQ query when calling ToList()

Yesterday I was working on a code refactor and came across an exception that I really couldn't find much information on. Here is the situation.
We have an a pair of EF entities that have a many to many relationship through a relation table. The objects in question look like this, leaving out the unnecessary bits.
public partial class MasterCode
{
public int MasterCodeId { get; set; }
...
public virtual ICollection<MasterCodeToSubCode> MasterCodeToSubCodes { get; set; }
}
public partial class MasterCodeToSubCodes
{
public int MasterCodeToSubCodeId { get; set; }
public int MasterCodeId { get; set; }
public int SubCodeId { get; set; }
...
}
Now, I attempted to run a LINQ query against these entities. We use a lot of LINQ projections into DTOs. The DTO and the query follow. masterCodeId is a parameter passed in.
public class MasterCodeDto
{
public int MasterCodeId { get; set; }
...
public ICollection<int> SubCodeIds { get; set; }
}
(from m in MasterCodes
where m.MasterCodeId == masterCodeId
select new MasterCodeDto
{
...
SubCodeIds = (from s in m.MasterCodeToSubCodes
select s.SubCodeId).ToList(),
...
}).SingleOrDefaultAsync();
The internal query throws the following exception
Expression of type 'System.Data.Entity.Infrastructure.ObjectReferenceEqualityComparer' cannot be used for constructor parameter of type 'System.Collections.Generic.IEqualityComparer`1[System.Int32]'
We have done inner queries like this before in other places in our code and not had any issues. The difference in this one is that we aren't new-ing up an object and projecting into it but rather returning a group of ints that we want to put in a list.
I have found a workaround by changing the ICollection on MasterCodeDto to IEnumerable and dropping the ToList() but I was never able to find out why I couldn't just select the ids and return them as a list.
Does anyone have any insight into this issue? Normally returning just an id field and calling ToList() works fine when it is not part of an inner query. Am I missing a restriction on inner queries that prevents an operation like this from happening?
Thanks.
Edit: To give an example of where this pattern is working I'll show you an example of a query that does work.
(from p in Persons
where p.PersonId == personId
select new PersonDto
{
...
ContactInformation = (from pc in p.PersonContacts
select new ContactInformationDto
{
ContactInformationId = pc.PatientContactId,
...
}).ToList(),
...
}).SingleOrDefaultAsync();
In this example, we are selecting into a new Dto rather than just selecting a single value. It works fine. The issues seems to stem from just selecting a single value.
Edit 2: In another fun twist, if instead of selecting into a MasterCodeDto I select into an anonymous type the exception is also not thrown with ToList() in place.
I think you stumbled upon a bug in Entity Framework. EF has some logic for picking an appropriate concrete type to materialize collections. HashSet<T> is one of its favorites. Apparently (I can't fully follow EF's source code here) it picks HashSet for ICollections and List for IEnumerable.
It looks like EF tries to create a HashSet by using the constructor that accepts an IEqualityComparer<T>. (This happens in EF'sDelegateFactory class, method GetNewExpressionForCollectionType.) The error is that it uses its own ObjectReferenceEqualityComparer for this. But that's an IEqualityComparer<object>, which can not be converted to an IEqualityComparer<int>.
In general I think it is best practice not to use ToList in LINQ queries and to use IEnumerable in collections in DTO types. Thus, EF will have total freedom to pick an appropriate concrete type.

Difference between == and .Equals() with Interfaces and LINQ

I recently got a "The mapping of interface member ..... is not supported" error, which I resolved based on this thread. To demonstrate:
public interface IMyInterface { string valueText { get; set; } }
public class MyData : IMyInterface
{
int ID { get; set;}
string valueText { get; set;}
}
public class MyOtherData : IMyInterface
{
long ID { get; set;}
string valueText { get; set;}
}
and
public static IEnumerable<T> GetByValue<T>(string value) : where T : class, IMyInterface, new()
{
using (var context = new DataContext())
{
// The important line
return context.GetTable<T>().Where(x => x.valueText == value);
}
}
Running this code, I'd get a NotSupportedException: "The mapping of interface member IMyInterface.valueText is not supported". However, if I replace the x.valueText == value with x.valueText.Equals(value), this works entirely as expected.
I've solved this in my code, but I want to understand why it behaves this way. Can anyone explain it?
Update: As per my comment below, the LINQ to SQL team closed this as a "Won't fix". I think that means it now counts as a known bug, but one that isn't going to be resolved any time soon. I'd still like to know why it behaves differently in the first place, though.
Apparently the decision to push the query upstream to the server is made based on an incomplete set of rules, and then LINQ-to-SQL finds a construct (an interface) that it can't deal with.
The method call isn't supported by LINQ-to-SQL, so it generates a query to retrieve all records and then uses LINQ-to-Objects to filter them. (Actually, based on your other thread, LINQ-to-SQL may make a special exception for object.Equals and knows how to convert that to SQL).
LINQ-to-SQL probably should fall back to the LINQ-to-Objects behavior when an interface is involved, but apparently it just throws an exception instead.

Finding an item in a List<T>

I have the follow example:
public class Commands
{
public int ID { get; set; }
public List<string> Alias { get; set; }
}
public class UserAccess
{
public int AccessID { get; set; }
// other stuff not needed for the question
public List<Commands> AllowedCommands { get; set; }
}
Now I wanted to implement on the UserAccess a way to return the command ID or NULL if no Alias were found on the list, see a dirty example of what I am saying below HasCommand:
public class UserAccess
{
public ID { get; set; }
// other stuff not needed for the question
public List<Commands> AllowedCommands { get; set; }
public Commands HasCommand(string cmd)
{
foreach (Commands item in this.AllowedCommands)
{
if (item.Alias.Find(x => string.Equals(x, cmd, StringComparison.OrdinalIgnoreCase)) != null)
return item;
}
return null;
}
}
My question is what would be the most efficient way to run or implement the HasCommand method ?
Or is there a better way to implement it into the UserAccess ?
Can be shortened a little bit
public Commands HasCommand(string cmd)
{
return AllowedCommands.FirstOrDefault(c => c.Alias.Contains(cmd, StringComparer.OrdinalIgnoreCase));
}
but it's pretty much the same thing.
public Commands HasCommand(string cmd)
{
return this.AllowedCommands.FirstOrDefault(item => item.Alias.Find(x => string.Equals(x, cmd, StringComparison.OrdinalIgnoreCase)) != null);
}
You do not need to use Where + FirstOrDefault. The FirstOfDefault can have condition.
Also, 3 suggestions for further improvement:
(1) I would encourage the use of IEnumerable instead of List, if possible.
(2) I would call "Commands" just "Command".
(3) I would make all commands be able to be easily referenced via a class like this:
public class Command {
public Command(int id, IEnumerable<string> aliases) {
Id = id;
Aliases = alias;
}
public int Id { get; set; }
public IEnumerable<string> Aliases { get; set; }
}
public class Commands {
public static readonly Command CommandNameHere1(yourIdHere1, yourAliasesHere1);
public static readonly Command CommandNameHere2(yourIdHere2, yourAliasesHere2);
//etc.
}
Assuming that by "efficient", you mean fast, anytime you are looking up a string in a collection of strings, and that collection is likely to contain more than a few entries, you should always use a hash lookup. Doing a simple scan of the list takes exponential time as the count of items goes up, while the count has little effect on a hash lookup. In .NET, this has traditionally been handled by the Dictionary class, which is commonly used to index a collection of objects with a key (which is often a string). However, the value can't be null, and this led to passing the same string in as both the key and value - rather ugly. Finally, .NET 4 provided HashSet, which you should use for such a case of only having a key and no value.
In your case, you have the (not uncommon) situation of needing a case-insensitive compare. The common solution for this is to lower-case the string keys when adding them to the dictionary (or HashSet). This tiny overhead on add is vastly outweighed by the savings on lookups, since all programmers should know and understand that case-insensitive compares are vastly slower than case-sensitive, especially with Unicode - the CPU can't just do a block compare of data, but must check each pair of characters specially (even using a table look-up, this is vastly slower).
If your Alias names can be in lower case, change them from List to HashSet. If not, use Dictionary where the key is added as lower case, and the value is the (mixed-case) Alias string. Assuming the use of Dictionary, your code would become:
public Commands HasCommand(string cmd)
{
foreach (Commands item in AllowedCommands)
{
if (item.Alias.ContainsKey(cmd))
return item;
}
return null;
}
Finally, also on the subject of performance, using LINQ is almost always going to result in slower performance - somewhere between a little slower and a lot slower, depending upon the situation. It does make nice, compact source for simple things, and I use it quite a bit myself, but if you're certain that performance is an issue for a piece of a code, you probably shouldn't use it (unless it's PLINQ, of course).
So, if you want as few lines of code as possible, use the other answer posted here. If you want speed, use mine.
It almost goes without saying, but when you're worried about the performance of some small chunk of code like this, just wrap it in a for loop and repeat it until it takes 5-10 seconds to execute - just add orders of magnitude as needed, whether it's 1,000 or 1,000,000 reps, and time it with System.Diagnostics.Stopwatch. Try alternative logic, and repeat the test. The 5-10 seconds is a minimum designed to mask the fluctuations caused by a managed environment and other stuff executing on the same machine (you should obviously also avoid running other apps during the test). Of course, for overall performance testing of a complicated application, a performance analyzer tool would be recommended.

Categories