Removing specific values from a Dictionary C# with Linq - c#

I have a dictionary which holds information from a parsed test run. The key is the name of the method and the value is a list of TestRunProperties. My dictionary contains all methods from a test run and I would like to remove the methods which failed during a test run. Is this possible to do with Linq?
TestRunProperties class:
public class TestRunProperties
{
public string computerName { get; set; }
public TimeSpan duration { get; set; }
public string startTime { get; set; }
public string endTime { get; set; }
public string testName { get; set; }
public string outcome { get; set; }
}
Dictionary:
//Key is the name of the method, value is the properties associated with each run
private static Dictionary<string, List<TestRunProperties>> runResults = new Dictionary<string, List<TestRunProperties>>();
I've tried this but I think I'm getting confused with the Where part:
runResults.Remove(runResults.Where(methodName => methodName.Value.Where(method => method.outcome.ToLower().Equals("failed"))));
I'm quite new to Linq and Lambda and I'm still trying to understand how to access data like this.

Just use a loop to remove the items you don't want. You can write an extension method to make it easier to call:
public static class DictionaryExt
{
public static void RemoveAll<K, V>(this IDictionary<K, V> dict, Func<K, V, bool> predicate)
{
foreach (var key in dict.Keys.ToArray().Where(key => predicate(key, dict[key])))
dict.Remove(key);
}
}
This usually will be more efficient than creating an entirely new dictionary, especially if the number of items being removed is relatively low compared to the size of the dictionary.
Your calling code would look like this:
runResults.RemoveAll((key, methodName) => methodName.Value.Where(method => method.outcome.ToLower().Equals("failed")));
(I chose the name RemoveAll() to match List.RemoveAll().)

You could create a new dictionary by filtering out the invalid ones:
var filtered = runResults.ToDictionary(p => p.Key, p => p.Value.Where(m => m.outcome.ToLower() != "failed").ToList());
Ok, grrrrrr was faster :-)

To be honest you're probably better off selecting a new dictionary from the existing one:
runResults.Select().ToDictionary(x => x.Key, x => x.Value.Where(x => x.Value.outcome != "failed"));
*editted to reflect list in the dictionary.
Actually, you can get rid of the ones with no successful results by doing this too:
runResults.Select(x => new { x.Key, x.Value.Where(x => x.Value.outcome != "failed")} ).Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value);

Related

Dynamically constructing a GET query with optional parameters in C#

I have a class that contains all the properties of a query I'm constructing could possibly have (most of which are optional)
For example:
public class QueryClass
{
public string Offset {get; set;}
public string Limit {get; set;}
public string Sort {get; set;}
}
Then in a BuildQuery() method I am constructing the query by doing this:
private string BuildQuery(QueryClass query)
{
var queryDictionary = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(query.Offset)
{
queryDictionary.Add("offset", query.Offset);
}
if (!string.IsNullOrEmpty(query.Limit)
{
queryDictionary.Add("limit", query.Limit);
}
if (!string.IsNullOrEmpty(query.Sort)
{
queryDictionary.Add("sort", query.Sort);
}
var content = new FormUrlEncodedContent(queryDictionary);
return content.ReadAsStringAsync().Result;
}
This works, but the issue is my actual QueryClass is significantly larger than this, there's got to be a better way to do this than to have a ton of IF statements for every optional property but I haven't been able to come up with a more elegant solution. I also don't care for adding the keys in the dictionary in this way, I probably need a new approach for how I structure the QueryClass.
If you don't mind taking the reflection hit, just project, filter nulls, then send to ToDictionary
Note : The assumptions here are, all the property names are your keys, and all the properties are convertible to string
var queryClass = new QueryClass()
{
Limit = "asdf",
Sort = "Bob"
};
var results = queryClass
.GetType()
.GetProperties()
.Select(x => (Value: x.GetValue(queryClass) as string, x.Name))
.Where(x => !string.IsNullOrWhiteSpace(x.Value))
.ToDictionary(
x => x.Name.ToLower(),
x => x.Value);
foreach (var (key, value) in results)
Console.WriteLine($"{key} : {value}");
Output
limit : asdf
sort : Bob
Add pepper and salt to taste
Or as an extension method
public static IDictionary<string, string> ConvertToDictionary<T>(this T source)
=> typeof(T).GetProperties()
.Select(x => (Value: x.GetValue(source) as string, x.Name))
.Where(x => !string.IsNullOrWhiteSpace(x.Value))
.ToDictionary(
x => x.Name.ToLower(),
x => x.Value!);

Converting a distinct list to dictionary not working

I'm trying to convert a list of objects to a dictionary using the following code:
var MyDictionary = MyList.Distinct().ToDictionary(i => i.ObjectId, i => i);
I know that a dictionary should not contain duplicate elements, hence the .Distinct(). Yet I still get the following Exception whenever there's a duplicate element:
An item with the same key has already been added.
MyList is a list of MyObject that looks like this:
public class MyObject{
public string ObjectId { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
Is there a better way to create a dictionary from a list of objects ? or am I doing something wrong?
If you want to compare on the ObjectId, you'll need to pass in a custom comparer to .Distinct(). You can do so like this:
class MyObjectComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
return x.ObjectId == y.ObjectId;
}
public int GetHashCode(MyObject obj)
{
return obj.ObjectId.GetHashCode();
}
}
var MyDictionary = MyList
.Distinct(new MyObjectComparer())
.ToDictionary(i => i.ObjectId, i => i);
You could use Group by and then select first from the List as below:
var MyDictionary = MyList.GroupBy(i => i.ObjectId, i => i).ToDictionary(i => i.Key, i => i.First());
Distinct works using the objects built in Equals and GetHashCode methods by default but your dictionary works only over the id. You need to pass in a IEqualityComparer in to distinct that does the comparison on Id to test if items are equal or make MyObject implment Equals and GetHashCode and have that compare on the Id.

List to Dictionary<Key, List<Value>> - C#

I have a List and MyClass is:
public class MyClass
{
public bool Selected { get; set; }
public Guid NoticeID { get; set; }
public Guid TypeID { get; set; }
}
My question is, how do i convert this list into a Dictionary<Guid, List<Guid>>, where the dictionary key is the GUID from the TypeID property, and the value is a list of all the NoticeID values corresponding to that TypeID. I have tried like so:
list.GroupBy(p => p.TypeID).ToDictionary(p => p.Key, p => p.ToList())
but this returns a Dictionary <Guid, List<MyClass>>, and I want a Dictionary<Guid, List<Guid>>.
Well, when you group you can specify the value you want for each element of the group:
var dictionary = list.GroupBy(p => p.TypeID, p => p.NoticeID)
.ToDictionary(p => p.Key, p => p.ToList());
However, I would strongly consider using a lookup instead of a dictionary:
var lookup = list.ToLookup(p => p.TypeID, p => p.NoticeID);
Lookups are much cleaner in general:
They're immutable, whereas your approach ends up with lists which can be modified
They express in the type system exactly what you're trying to express (one key to multiple values)
They make looking keys up easier by returning an empty sequence of values for missing keys, rather than throwing an exception

How to make expression treat value type as a reference type?

I wanted to store a collection of expressions accessing object's properties. For example:
class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Id);
list.Add(e => e.Name);
list.Add(e => e.Parent);
list.Add(e => e.Date);
list.Add(e => e.Value);
list.Add(e => e.Active);
StringBuilder b = new StringBuilder();
list.ForEach(f => b.AppendLine(f.ToString()));
Console.WriteLine(b.ToString());
Console.ReadLine();
}
This code outputs:
e => Convert(e.Id)
e => e.Name
e => e.Parent
e => Convert(e.Date)
e => Convert(e.Value)
e => Convert(e.Active)
It does add Convert to value types.
As far as in the end I wanted to use those expressions with LINQ to SQL, I need not to have that Convert in expressions, for them to be successfully translated to SQL.
How can I achieve this?
P.S.: expressions from this collection are later used as arguments to OrderBy and ThenBy methods.
If you create a function generic in the proeprty type you can avoid the Convert:
private static LambdaExpression GetExpression<TProp>
(Expression<Func<Entity, TProp>> expr)
{
return expr;
}
then you can change the type of list:
var list = new List<LambdaExpression>();
list.Add(GetExpression(e => e.Id));
list.Add(GetExpression(e => e.Name));
This will require you to create your OrderBy and ThenBy expressions using reflection e.g.
LambdaExpression idExpr = list[0];
Type keyType = idExpr.ReturnType;
var orderByMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Entity), keyType);
var ordered = (IQueryable<Entity>)
orderByMethod.Invoke(null, new object[] { source, idExpr });
I patched up a EF code first attempt at using your code like this
public class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
public class EntityContext : DbContext
{
public EntityContext()
: base(new SqlCeConnection("Data Source=Database.sdf;Persist Security Info=False;"),
contextOwnsConnection: true)
{
// Using a SQL Compact database as backend
}
public DbSet<Entity> Entities { get; set; }
}
and attempted some linq on the context
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Date);
list.Add(e => e.Name);
using (var c = new EntityContext())
{
//each time a new record is added
var data = new Entity
{
Name = string.Format("Data{0}", c.Entities.Count()),
Date = DateTime.Now
};
c.Entities.Add(data);
c.SaveChanges();
// sort by date
foreach (var e in c.Entities.OrderBy(list.First().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
// sort by name .. in reverse
foreach (var e in c.Entities.OrderByDescending(list.Last().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
}
Console.ReadLine();
}
There were no issues running the code.
UPDATE The same holds true for LINQ to SQL: I built a table in a local SQL Server with the same structure as the class, and tried to OrderBy it : no problem.
My answer is "You don't need to worry about that".
Thank's to the answer by Alex I found out for myself that, when ordering the data I can use two different methods, depending on the specified argument:
Queryable.OrderBy Method with Expression<Func<TSource, TKey>>
Enumerable.OrderBy Method with Func<TSource, TKey>
When Queryable.OrderBy is used, LINQ compiles the OrderBy clause into the SQL statement, executed over the database. So when I try to give it a Expression<Func<TEntity, object>> that looks like e => Convert(e.Field), LINQ throws an InvalidOperationException, saying Cannot order by type 'System.Object'.
When Enumerable.OrderBy is used, LINQ does not compile the OrderBy clause into the SQL query, but executes the current query and applies sorting on the enumerable of entities, returned by the query, in the program's memory. Here no problem with ordering by Func<TEntity, object>.
So I found two alternatives here:
Query the database without sorting and order the returned result set
Provide better expressions to LINQ, that it could compile the SQL query, and then apply sorting in the database layer; here the answer by Lee suggests one way..
In my exact case sorting is the last operation to execute, and I don't see much harm, if I order the result set in the programm's memory...I'm not going to expect huge amounts of data to be returned...
Though in a more common case, probably it's still better to do all possible operations in the database layer...
P.S.: SO: Order a linq query - a close discussion...

Linq Query to IEnumerable<T> Extension Method

Consider this,
class Item
{
public string ID { get; set;}
public string Description { get; set; }
}
class SaleItem
{
public string ID { get; set;}
public string Discount { get; set; }
}
var itemsToRemoved = (List<Item>)ViewState["ItemsToRemove"];
// get only rows of ID
var query = from i in itemsToRemoved select i.ID;
var saleItems= (List<SaleItem>)ViewState["SaleItems"];
foreach (string s in query.ToArray())
{
saleItems.RemoveItem(s);
}
How can I write this LINQ phrase using IEnumerable/List Extension methods
// get only rows of ID
var query = from i in items select i.ID;
thanks in advance.
That one's easy:
var query = items.Select(i => i.ID);
A select clause always corresponds to a call to Select. Some of the other operators end up with a rather more complex expansion :) If you work hard, you can get the compiler to do some very odd stuff...
You can find all the details of this and other query expression translations in section 7.16 of the C# specification (v3 or v4).
<plug>
You could also buy C# in Depth, 2nd edition and read chapter 11 if you really wanted to :)</plug>
You can use this:
var query = items.Select(i => i.ID);
A couple of other points:
Here you don't need the call to ToArray:
foreach (string s in query.ToArray())
Also if your list is large and you are removing a lot of items you may want to use List.RemoveAll instead of iterating. Every time you remove an item from a list all the other items after it have to be moved to fill the gap. If you use RemoveAll this only has to be done once at the end, instead of once for every removed item.
List<Item> itemsToRemove = (List<Item>)ViewState["ItemsToRemove"];
HashSet<string> itemIds = new HashSet<string>(itemsToRemove.Select(s => s.ID));
saleItems.RemoveAll(c => itemIds.Contains(c.ID));
public static class ItemCollectionExtensions
{
public static IEnumerable<int> GetItemIds(this List<Item> list)
{
return list.Select(i => i.ID);
}
}

Categories