Update the property of list items without "loop" and "if" - c#

I am updating the property of the list items.
class Response
{
public string Name { get; set; }
public int Order { get; set; }
}
Here I want to update the Order of a List<Response> variable. As of now, I am looping through each item of the list and updating it.
List<Response> data = FromDb();
foreach (var item in data)
{
if(item.Name.Equals("A"))
{
item.Order=1;
}
if(item.Name.Equals("B"))
{
item.Order=2;
}
//Like this I have arround 20 conditions
}
The above code is working fine, but the problem is the Cognitive Complexity of the method is more than the allowed.
I tried something like below
data.FirstOrDefault(x => x..Equals("A")).Order = 1;
data.FirstOrDefault(x => x..Equals("B")).Order = 2;
//and more ...
In this code also null check is not in place, So if the searching string is not present in the list then again it will break.
If I add null check condition then again the complexity getting higher.
So here I want without any for loop or if, If I can update the Order of the list by using linq/lamda or anything else.

I don't know how you measure Cognitive Complexity and how much of it is allowed to be pushed out into other functions, but something like this makes the ordering quite declarative?
[Fact]
public void TestIt()
{
var data = FromDb().Select(SetOrder(
("A", 1),
("B", 2)
));
}
static Func<Response, Response> SetOrder(params (string Name, int Order)[] orders)
{
var orderByKey = orders.ToDictionary(x => x.Name);
return response =>
{
if (orderByKey.TryGetValue(response.Name, out var result))
response.Order = result.Order;
return response;
};
}
Addendum in response to comment:
In order to have a default value for unmatched names, the SetOrder could be changed to this:
static Func<Response, Response> SetOrder(params (string Name, int Order)[] orders)
{
var orderByKey = orders.ToDictionary(x => x.Name);
return response =>
{
response.Order =
orderByKey.TryGetValue(response.Name, out var result)
? result.Order
: int.MaxValue;
return response;
};
}

Related

Linq C#: Get value from list of class objects if in list of strings

I've been trying to get the first matching value from a list of objects within a list of objects that does not exist in a list of strings, but can't seem to find just the right syntax.
public class SO_Response
{
private List<SalesOrderDto> _headers = new List<SalesOrderDto>();
public List<SalesOrderDto> Header
{
get { return _headers; }
set { _headers = value; }
}
}
public class SalesOrderDto
{
public SalesOrderDto(string order, string resultMessage)
{
Order = order;
ResultMessage = resultMessage;
}
public string Order { get; }
public string ResultMessage { get; }
}
Use in method:
var result = new List<IRMS_SO_Response>();
//result = .....
var matches = new HashSet<string>() { "Good Record", "Order is canceled", "status = Cancelled" };
--> Need Linq statement to get first value from result where header.ResultMessage not in matches.
I've tried lots of variations, but never the right one.
var returnMessage = (from x in result
where x.Header.FirstOrDefault(message => !message.ResultMessage.Contains(matches))
I'm not sure that I fully understand what you're looking for. Assuming that you're looking for the first ResultMessage value which is not in the matches set, I believe that the following will do what you want.
var firstNonMatch = result
.SelectMany(t => t.Header.Select(h => h.ResultMessage)) // convert list of responses into list of nested headers
.FirstOrDefault(t => !matches.Contains(t)); // pick the first header that doesn't match (or null if no match)

MongoDB Driver C#, Search by nested property using filter definition

I need help with building filter for MongoCollection of class A when I have filters for class B
public class A
{
public string ExampleAProperty { get; set; }
public B NestedB { get; set; }
public ICollection<B> NestedBCollection { get; set; }
}
public class B
{
public string ExampleBProperty { get; set; }
}
public class SearchClass
{
public async Task<ICollection<A>> SearchAsync(IMongoCollection<A> collection)
{
// this is just simple example of possible dozens predefined filters for B class.
// see FilterProvider logic used now
// var bFilter = Builders<B>.Filter.Eq(x => x.ExampleBProperty, "Example");
// in need filter A where B meets provided filters
var cursor = await collection.FindAsync(
Builders<A>.Filter.And(
// predefined filters are easy to reuse with array of elements
Builders<A>.Filter.ElemMatch(x => x.NestedBCollection, FilterProvider.SearchValue("Oleh")),
// but i did not found how to do this with single nested element
Builders<A>.Filter.Eq(x => x.NestedB, FilterProvider.SearchValue("Oleh")) // how?
)
);
return await cursor.ToListAsync();
}
}
// statics is not good but just for working example :)
public static class FilterProvider
{
public static FilterDefinition<B> SearchValue(string? value)
{
var builder = Builders<B>.Filter;
// if value is null show all
if (string.IsNullOrEmpty(value))
{
return builder.Empty;
}
// if value is "Oleh" search for "Amir"
if (value == "Oleh")
{
value = "Amir";
}
// any other additional logic to compose proper filter
// this could be search by serveral properties and so on
// however just for example i will search by hashed value :)
value = value.GetHashCode().ToString();
return builder.Eq(x => x.ExampleBProperty, value);
}
}
Please DON'T
use IMongoQueryable
propose to filter nested element by duplicate code (like x => x.NestedB.ExampleBProperty = "something")
UPDATED: example for Amir with explanation why p.2 is not than case and why code will be is duplicated if you his approach :)
As you may see we have complex (but very simple in current example) filter of data in B class. If you will use your approach - logic of composing filter for B (specified in FilterProvider.SearchValue) will be duplicated.
Thank you
You can easily do this:
public async Task<ICollection<A>> SearchAsync(IMongoCollection<A> collection, string search)
{
var bFilter = Builders<B>.Filter.Eq(x => x.ExampleBProperty, search);
var cursor = await collection.FindAsync(
Builders<A>.Filter.And(
Builders<A>.Filter.ElemMatch(x => x.NestedBCollection, bFilter),
Builders<A>.Filter.Eq(x => x.NestedB.ExampleBProperty, search)
)
);
return await cursor.ToListAsync();
}

Check which elements are on one list comparing to another list LINQ

I have two lists, one of all languages and another subset of languages that the site has, the idea is to return all the languages but change the property of a boolean if the element of the subset corresponds to the list of all languages.
DTO of language:
public class DTOLanguage
{
public bool HaveLanguage { get; set; }
public int IdLanguage { get; set; }
//Other properties...
}
Method that returns all languages:
public List<DTOLanguage> GetLanguages()
{
var result = repository.RepSite.GetLanguages().Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage,
CodName = x.CodName,
Name = x.Name
}).ToList();
return result;
}
Method that returns the subset of languages:
public List<DTOLanguage> GetLanguagesById(int idSite)
{
var result = repository.RepSite.GetLanguagesById(idSite).Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage
}).ToList();
return result;
}
The GetLanguagesById is called in the DataAccess layer, so what Im thinking is that this method should receive another parameter (what GetLanguages returns) and make some fancy LINQ there.
I know that I can filter (example):
SubsetOfLanguages.Where(lg => lg.IdLanguage == AllLanguagesItem.IdLanguage)
{
AllLanguagesItem.HaveLanguage = True;
}
But Im not really sure as how it should be.
Thanks in advance.
You could use Contains extension method this way:
var languages=GetLanguages();
var subsetids=repository.RepSite.GetLanguagesById(idSite).Select(x =>x.IdLanguage);//Select just the id value
foreach(var l in languages.Where(l=>subsetids.Contains(l.IdLanguage)))
{
l.HaveLanguage = true;
}
You could do this:
var allLanguages = GetLanguages();
var subset = SubsetOfLanguages
.Where(lg => allLanguages.Any(a => lg.IdLanguage == a.IdLanguage))
.ToArray();
foreach(var item in subset)
{
item.HaveLanguage = True;
}

Why isn't my object being updated

I'm doing something wrong because after the loop executed myData still contains objects with blank ids. Why isn't the myData object being updated in the following foreach loop, and how do I fix it?
I thought it could be that I wasn't passing the object by reference, but added a ref keyword and also moved to the main method and I'm still showing the object not being updated.
Additional Information
The user object in the foreach loop is being updated, but the myData list does not reflect the updates I see being applied to the user object.
** Solution **
I was not creating a List but an Enumerable which was pulling the json each time I went through myData in a foreach list. Adding a ToList() fixed my issue.
public class MyData
{
public string ID { get; set; }
public Dictionary<string, string> Properties { get; set; }
}
int index = 0;
// Does not allow me to up, creates an IEnumerable
//IEnumerable<MyData> myData = JObject.Parse(json)["Users"]
// .Select(x => new MyData()
// {
// ID = x["id"].ToString(),
// Properties = x.OfType<JProperty>()
// .ToDictionary(y => y.Name, y => y.Value.ToString())
// });
//Works allows me to update the resulting list.
IEnumerable<MyData> myData = JObject.Parse(json)["Users"]
.Select(x => new MyData()
{
ID = x["id"].ToString(),
Properties = x.OfType<JProperty>()
.ToDictionary(y => y.Name, y => y.Value.ToString())
}).ToList();
foreach (var user in myData) // Also tried myData.ToList()
{
if (string.IsNullOrEmpty(user.ID))
{
user.ID = index.ToString();
user.Properties["id"] = index.ToString();
}
index++;
}
public class MyData
{
public MyData()
{
this.Properties = new Dictionary<string,string>();
}
public string ID { get; set; }
public Dictionary<string, string> Properties { get; set; }
}
public static void Main(string[] args)
{
IEnumerable<MyData> myDataList = new List<MyData>();
int index = 0; // Assuming your starting point is 0
foreach (var obj in myDataList)
{
if (obj != null && string.IsNullOrEmpty(obj.ID))
{
obj.ID = index.ToString();
// Checks if the Properties dictionary has the key "id"
if (obj.Properties.ContainsKey("id"))
{
// If it does, then update it
obj.Properties["id"] = obj.ID;
}
else
{
// Else add it to the dictionary
obj.Properties.Add("id", obj.ID);
}
}
index++;
}
I believe the reason why your objects are not updating because it's probably still referring to the memory block before your objects were changed. Perhaps. The easiest way (that I can think of, there are thousands of smarter programmers than me) is to create a new list and have it contain all of your updated objects.
Edit
I updated the code above with the code that I have. I created a method to set a small amount of objects to test:
private static IEnumerable<MyData> GetMyData()
{
return new List<MyData>()
{
new MyData(),
new MyData() {ID = "2"},
new MyData() {ID = "3"},
new MyData()
};
}
I was able to view my changes and then go through a foreach loop to view my changes. If the ID of the object is Null or Empty, then it steps into the if check and adds the current index to the ID as you know.
Now for my question: Which "id" is blank? The "id" in the dictionary or is it the ID of the model? Are all of your (Model).ID blank? As the updated code of yours, if your dictionary doesn't have "id" as a key, it's going to throw an exception saying it doesn't exist so you will need to do a check to make sure it does exist or add it if it doesn't.

Element Metrics with Custom collection in C#

I am trying to figure out the best way to organise a bunch of my data classes, given I need to be able to access some metrics on them all at some point.
Here's a snippet of my OR class:
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
}
Not every OR I initialise will have values for all properties. I want to be able to 'collect' thousands of these together in such a way that I can easily obtain a count of how many OR objects had a value set. For example:
OR a = new OR() { reference = "a" }
OR b = new OR() { reference = "b", title = "test" }
OR c = new OR() { reference = "c", title = "test", status = status.CLOSED }
Now these are somehow collected in such a way I can do (pseudo):
int titleCount = ORCollection.titleCount;
titleCount = 2
I would also want to be able gather metrics for the enum type properties, for example retrieve a Dictionary from the collection that looks like:
Dictionary<string, int> statusCounts = { "CLOSED", 1 }
The reason for wanting access to these metrics is that I am building two collections of ORs and comparing them side-by-side for any differences (they should be identical). I want to be able to compare their metrics at this higher level first, then break-down where precisely they differ.
Thanks for any light that can be shed on how to accomplish this. :-)
... to 'collect' thousands of these
Thousands is not a huge number. Just use a List<OR> and you can get all your metrics with Linq queries.
For example:
List<OR> orList = ...;
int titleCount = orList
.Where(o => ! string.IsNullOrEmpty(o.title))
.Count();
Dictionary<status, int> statusCounts = orList
.GroupBy(o => o.status)
.ToDictionary(g => g.Key, g => g.Count());
The existing answers using Linq are absolutely great and really elegant, so the idea presented below is just for posterity.
Here is a (very rough) reflection-based program that will alow you to count the "valid" properties in any collection of objects.
The validators are defined by you in the Validators dictionary so that you can easily change what is a valid/invalid value for each property. You may find it useful as a concept if you end up with objects having tons of properties and don't want to have to write inline linq metrics on the actual collection itself for every single property.
You could weaponise this as a function and then run it against both collections, giving you a basis to report on the exact differences between both since it records the references to the individual objects in the final dictionary.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace reftest1
{
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
public int foo { get; set; }
}
//creates a dictionary by property of objects whereby that property is a valid value
class Program
{
//create dictionary containing what constitues an invalid value here
static Dictionary<string,Func<object,bool>> Validators = new Dictionary<string, Func<object,bool>>
{
{"reference",
(r)=> { if (r ==null) return false;
return !String.IsNullOrEmpty(r.ToString());}
},
{"title",
(t)=> { if (t ==null) return false;
return !String.IsNullOrEmpty(t.ToString());}
},
{"status", (s) =>
{
if (s == null) return false;
return !String.IsNullOrEmpty(s.ToString());
}},
{"foo",
(f) =>{if (f == null) return false;
return !(Convert.ToInt32(f.ToString()) == 0);}
}
};
static void Main(string[] args)
{
var collection = new List<OR>();
collection.Add(new OR() {reference = "a",foo=1,});
collection.Add(new OR(){reference = "b", title = "test"});
collection.Add(new OR(){reference = "c", title = "test", status = status.CLOSED});
Type T = typeof (OR);
var PropertyMetrics = new Dictionary<string, List<OR>>();
foreach (var pi in GetProperties(T))
{
PropertyMetrics.Add(pi.Name,new List<OR>());
foreach (var item in collection)
{
//execute validator if defined
if (Validators.ContainsKey(pi.Name))
{
//get actual property value and compare to valid value
var value = pi.GetValue(item, null);
//if the value is valid, record the object into the dictionary
if (Validators[pi.Name](value))
{
var lookup = PropertyMetrics[pi.Name];
lookup.Add(item);
}
}//end trygetvalue
}
}//end foreach pi
foreach (var metric in PropertyMetrics)
{
Console.WriteLine("Property '{0}' is set in {1} objects in collection",metric.Key,metric.Value.Count);
}
Console.ReadLine();
}
private static List<PropertyInfo> GetProperties(Type T)
{
return T.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
}
}
}
You can get the title count using this linq query:
int titleCount = ORCollection
.Where(x => !string.IsNullOrWhiteSpace(x.title))
.Count();
You could get the count of closed like this:
int closedCount = ORCollection
.Where(x => x.status == status.CLOSED)
.Count();
If you were going to have larger collections or you access the values a lot it might be worth creating a custom collection implementation that stores the field counts, it could then increment/decrement these values as you add and remove items. You could also store a dictionary of status counts in this custom collection that gets updated as you add and remove items.

Categories