Conditions inside Select clause in Linq - c#

I have a method that gets a list of objects and then returns all their properties values based on the attributes and types of the property.
I'm having difficulties with selecting the value because the Select depends on conditions.
What I did so far allows me to get the value in the ugliest way I have ever seen:
public IEnumerable<string> GetValues(List<ProperyInfo> objects)
{
var allValues = new List<string>();
foreach (var obj in objects)
{
// Get the PropertyInfo for the obj
var properties = GetPropertiesWithTheAttributes(obj);
var values = properties.Select(x => new
{
Value = x.GetValue(obj, null) == null
? string.empty
:
x.PropertyType.BaseType == typeof(Enum)
? Convert.ToInt32(x.GetValue(obj, null)).ToString()
: (x.GetValue(obj, null)).ToString(),
((ReportAttribute)
Attribute.GetCustomAttribute(x, typeof(ReportAttribute), false)).Order
})
.OrderBy(x => x.Order)
.Select(x => x.Value.ToString())
.ToList();
allValues.AddRange(values);
}
return allValues;
}
And in the code I published here I even removed the check for the DisplayDate property in the ReportAttribute, which verifies if the datetime property of the attribute would be displayed as date or datetime...
serenity now!

I would simply extract this into 2 methods, and get rid of the ternary operator:
// not sure how to name 'obj' and 'x' without more context
private static object GetValue(PropertyInfo obj, PropertyInfo x)
{
if (x.GetValue(obj, null) == null) return string.Empty;
if (x.PropertyType.BaseType == typeof (Enum))
return Convert.ToInt32(x.GetValue(obj, null));
return x.GetValue(obj, null);
}
private static int GetOrder(PropertyInfo x)
{
return ((ReportAttribute) Attribute.GetCustomAttribute(x, typeof(ReportAttribute), false)).Order;
}
So you can write:
public IEnumerable<string> GetValues(List<PropertyInfo> objects)
{
var allValues = new List<string>();
foreach (var obj in objects)
{
// Get the PropertyInfo for the obj
var properties = GetPropertiesWithTheAttributes(obj);
var values = properties.Select(x => new
{
Value = GetValue(obj, x),
Order = GetOrder(x)
})
.OrderBy(x => x.Order)
.Select(x => x.Value.ToString())
.ToList();
allValues.AddRange(values);
}
return allValues;
}
If you really want to inline this, you can change the lambda expression in your Select to a statement:
.Select(x =>
{
object value;
if (x.GetValue(obj, null) == null) value = string.Empty;
else if (x.PropertyType.BaseType == typeof (Enum))
value = Convert.ToInt32(x.GetValue(obj, null));
else value = x.GetValue(obj, null);
return new
{
Value = value,
((ReportAttribute)
Attribute.GetCustomAttribute(x, typeof (ReportAttribute), false)).
Order
};
})

Related

How do you call a function to an Linq List query that uses the exist function

I have this method with a linq statement below. I'm not a fan of multiple if statement and I'm trying to find what is the best way to not have these if statement and have a private method.
My field values is being set as such:
var fieldValues = await GetFields // then it's being passed to my method.
public static AppraisalContactBorrower BuildCoBorrower(List<LoanFieldValue> fieldValues) {
var coborrower = new AppraisalContactBorrower();
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.COBORRNAME")) {
coborrower.Name = fieldValues.First(v => v.FieldId == "CX.OS.AO.COBORRNAME").Value;
}
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.BORRCONTACTZIP")) {
borrower.Zip = fieldValues.First(v => v.FieldId == "CX.OS.AO.BORRCONTACTZIP").Value;
}
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.BORRCONTACTZIP")) {
borrower.Zip = fieldValues.First(v => v.FieldId == "CX.OS.AO.BORRCONTACTZIP").Value;
}
What I'm trying to do is instead of this:
coborrower.Name = fieldValues.First(v => v.FieldId == "CX.OS.AO.COBORRNAME").Value;
Is having something similar to this.
if (fieldValues.Exists(f => f.FieldId == "CX.OS.AO.BORRCONTACTZIP")) {
coborrower.Name = SETVALUE("CX.OS.AO.BORRCONTACTZIP")}
First, try using Enumerable.ToDictionary to have the field values grouped by FieldId, then use IDictionary.TryGetValue to get the existing values:
public static AppraisalContactBorrower BuildCoBorrower(List<LoanFieldValue> fieldValues) {
var groupedFieldValues = fieldValues.ToDictionary(f => f.FieldId)
var coborrower = new AppraisalContactBorrower();
if (groupedFieldValues.TryGetValue("CX.OS.AO.COBORRNAME", out var name)) {
coborrower.Name = name.Value;
}
if (groupedFieldValues.TryGetValue("CX.OS.AO.BORRCONTACTZIP", out var zip)) {
borrower.Zip = zip.Value;
}
}
Using Dictionary makes it faster to check the appropriate field existence as it is O(1) and with TryGetValue you combine two operations into one (existence check + obtaining the value).
Your two last statements are almost identitical. The equivalent of :
if (groupedFieldValues.TryGetValue("CX.OS.AO.COBORRNAME", out var name)) {
coborrower.Name = name.Value;
}
is:
coborrower.Name = fieldValues.FirstOrDefault(v => v.FieldId == "CX.OS.AO.COBORRNAME")
?? coborrower.Name;
In the original code, coborrower.Name is not updated if the field doesn't exist in the list.

How to find out duplicate Elements in Xelement

I am trying to find out the duplicate Elements in XElement , and make a generic function to remove duplicates .Something like:
public List<Xelement>RemoveDuplicatesFromXml(List<Xelement> xele)
{ // pass the Xelement List in the Argument and get the List back , after deleting the duplicate entries.
return xele;
}
the xml is as follows:
<Execute ID="7300" Attrib1="xyz" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
<Execute ID="7301" Attrib1="xyz" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
<Execute ID="7302" Attrib1="xyz1" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
I want get duplicates on every attribute excluding ID ,and then delete the one having lesser ID.
Thanks,
You can implement custom IEqualityComparer for this task
class XComparer : IEqualityComparer<XElement>
{
public IList<string> _exceptions;
public XComparer(params string[] exceptions)
{
_exceptions = new List<string>(exceptions);
}
public bool Equals(XElement a, XElement b)
{
var attA = a.Attributes().ToList();
var attB = b.Attributes().ToList();
var setA = AttributeNames(attA);
var setB = AttributeNames(attB);
if (!setA.SetEquals(setB))
{
return false;
}
foreach (var e in setA)
{
var xa = attA.First(x => x.Name.LocalName == e);
var xb = attB.First(x => x.Name.LocalName == e);
if (xa.Value == null && xb.Value == null)
continue;
if (xa.Value == null || xb.Value == null)
return false;
if (!xa.Value.Equals(xb.Value))
{
return false;
}
}
return true;
}
private HashSet<string> AttributeNames(IList<XAttribute> e)
{
return new HashSet<string>(e.Select(x =>x.Name.LocalName).Except(_exceptions));
}
public int GetHashCode(XElement e)
{
var h = 0;
var atts = e.Attributes().ToList();
var names = AttributeNames(atts);
foreach (var a in names)
{
var xa = atts.First(x => x.Name.LocalName == a);
if (xa.Value != null)
{
h = h ^ xa.Value.GetHashCode();
}
}
return h;
}
}
Usage:
var comp = new XComparer("ID");
var distXEle = xele.Distinct(comp);
Please note that IEqualityComparer implementation in this answer only compare LocalName and doesn't take namespace into considerataion. If you have element with duplicate local name attribute, then this implementation will take the first one.
You can see the demo here : https://dotnetfiddle.net/w2DteS
Edit
If you want to
delete the one having lesser ID
It means you want the largest ID, then you can chain the .Distinct call with .Select.
var comp = new XComparer("ID");
var distXEle = xele
.Distinct(comp)
.Select(z => xele
.Where(a => comp.Equals(z, a))
.OrderByDescending(a => int.Parse(a.Attribute("ID").Value))
.First()
);
It will guarantee that you get the element with largest ID.
Use Linq GroupBy
var doc = XDocument.Parse(yourXmlString);
var groups = doc.Root
.Elements()
.GroupBy(element => new
{
Attrib1 = element.Attribute("Attrib1").Value,
Attrib2 = element.Attribute("Attrib2").Value,
Attrib3 = element.Attribute("Attrib3").Value,
Attrib4 = element.Attribute("Attrib4").Value,
Attrib5 = element.Attribute("Attrib5").Value
});
var duplicates = group1.SelectMany(group =>
{
if(group.Count() == 1) // remove this if you want only duplicates
{
return group;
}
int minId = group.Min(element => int.Parse(element.Attribute("ID").Value));
return group.Where(element => int.Parse(element.Attribute("ID").Value) > minId);
});
Solution above will remove elements with lesser ID which have duplicates by attributes.
If you want return only elements which have duplicates then remove if fork from last lambda

Lambda expression or Linq to fetch filtered result out of keyvaluepair from list?

I don't know how to write expression/query to fetch the result from 2 level deep List containing KeyValuePair<object, object>
For example:
IList<ITaskData> taskDataList //1st Level
here IList contains collection of ITaskData and ITaskData contains
IList<KeyValuePair<object, object>> TaskParams { get; set; } //2nd Level
So suppose TaskParams have below key value pairs
Key : Location
Values: Stockroom, Salesfloor
Key : Iteration
Values : 1, 2
So, I need to fetch the List of TaskData which contains TaskParams values Stockroom and 1.
I can do easily by foreach loop but I wanted to use Linq / Lambda which is one liner and more easily maintainable.
Thanks a lot for support. Please let me know if you need more clarification.
Working code by foreach loop: I am getting desire output in taskDataListType1
IList<ITaskData> taskDataListType1 = new List<ITaskData>();
IList<KeyValuePair<object, object>> taskParams = null;
bool iteration = false;
bool location = false;
foreach (ITaskData taskData in taskDataList)
{
taskParams = taskData.TaskParams;
if (taskParams != null)
{
foreach (KeyValuePair<object, object> keyValuePair in taskParams)
{
if (keyValuePair.Key.ToString().Equals("ITERATION", StringComparison.OrdinalIgnoreCase))
{
if (int.Parse(keyValuePair.Value.ToString()) == 1)
{
iteration = true;
}
}
else if (keyValuePair.Key.ToString().Equals("LOCATION", StringComparison.OrdinalIgnoreCase))
{
if (keyValuePair.Value.ToString() == "StockRoom")
{
location = true;
}
}
if (iteration == true && location == true)
{
taskDataListType1.Add(taskData);
}
}
}
}
Strange but if I put below logic its not working I mean I am not getting any values in tasks1
IList<ITaskData> taskDataListType1 = new List<ITaskData>();
foreach (TaskData td in taskDataList)
{
var tasks1 = taskParams.Where(kvp => kvp.Key != null
&& kvp.Value != null
&& kvp.Key.ToString().Equals("LOCATION", StringComparison.OrdinalIgnoreCase)
&& kvp.Value.ToString() == "StockRoom"
&& kvp.Key.ToString().Equals("ITERATION", StringComparison.OrdinalIgnoreCase)
&& int.Parse(kvp.Value.ToString()) == 1
);
}
Output of above query is screenshot below:
If you insist on using object in KeyValuePair, then your example would look like this:
IList<ITaskData> taskDataList = new List<ITaskData>
{
new ITaskData
{
TaskParams = new List<KeyValuePair<object,object>>
{
new KeyValuePair<object, object>("Location", "Stockroom"),
new KeyValuePair<object, object>("Location", "Salesfloor"),
new KeyValuePair<object, object>("Iteration", 1),
new KeyValuePair<object, object>("Iteration", 2)
}
},
new ITaskData
{
TaskParams = new List<KeyValuePair<object,object>>
{
new KeyValuePair<object, object>("Location", "Stockroom"),
new KeyValuePair<object, object>("Location", "Salesfloor"),
new KeyValuePair<object, object>("Iteration", 101),
new KeyValuePair<object, object>("Iteration", 2)
}
}
};
var result = taskDataList.Where(td =>
td.TaskParams.Any(tp => ((string)tp.Key == "Location") && ((string)tp.Value == "Stockroom")) &&
td.TaskParams.Any(tp => (string)tp.Key == "Iteration" && (int)tp.Value == 1)
);
As you can see, you need to cast object to an exact type, so this approach is very error-prone, and can easily cause run-time exceptions if you key,value collection will have items with type different from what you expect.
If you need to filter by location or iteration, define them as properties inside your TaskParams class, then your query will become more clear, strongly typed and less error-prone. See the example below:
public class TaskParamsType
{
public IList<string> Locations;
public IList<int> Iterations;
}
public class ITaskDataNew
{
public TaskParamsType TaskParams { get; set; }
}
var result = taskDataList.Where(td =>
td.TaskParams.Locations.Contains("Stockroom") &&
td.TaskParams.Iterations.Contains(1)
);
Try this:
var results =
taskDataList
.Where(td => td.TaskParams != null)
.Where(td =>
td.TaskParams.Any(kvp =>
kvp.Key != null
&& kvp.Key.ToString().Equals("LOCATION", StringComparison.OrdinalIgnoreCase)
&& kvp.Value != null
&& kvp.Value.Equals("Stockroom"))
&& td.TaskParams.Any(kvp =>
kvp.Key != null
&& kvp.Key.ToString().Equals("ITERATION", StringComparison.OrdinalIgnoreCase)
&& kvp.Value != null
&& kvp.Value.Equals(1)))
.ToList();
I have tested this code against this data:
IList<ITaskData> taskDataList = new List<ITaskData>();
var taskData = new TaskData();
taskData.TaskParams.Add(new KeyValuePair<object, object>("Location", "Stockroom"));
taskData.TaskParams.Add(new KeyValuePair<object, object>("Location", "Salesfloor"));
taskData.TaskParams.Add(new KeyValuePair<object, object>("Iteration", 1));
taskData.TaskParams.Add(new KeyValuePair<object, object>("Iteration", 2));
taskDataList.Add(taskData);
Let's suppose you have the following code which returns a List<KeyValuePair<object, object>> matching the logical condition:
public class ITaskData
{
public List<KeyValuePair<object, object>> keyValuePairs { get; set; }
}
class Program
{
private static List<ITaskData> list = new List<ITaskData>();
private static void Main()
{
List<KeyValuePair<object, object>> result = new List<KeyValuePair<object, object>>();
foreach (var a in list)
foreach (var b in a.keyValuePairs)
if (b.Value.ToString().Contains("Stockroom")) result.Add(b);
// Here I make .ToString().Contains("Stockroom")
// You can add any required logics here
}
}
You can make it in LINQ:
List<KeyValuePair<object, object>> result =
(from a in list
from b in a.keyValuePairs
where b.Value.ToString().Contains("Stockroom")
select b)
.ToList();
Or in LINQ method chain:
List<KeyValuePair<object, object>> result =
(list
.SelectMany(a => a.keyValuePairs, (a, b) => new {a, b})
.Where(t => t.b.Value.ToString().Contains("Stockroom"))
.Select(t => t.b)
).ToList();
However, in my private opinion, in your case the solution with foreachs looks more elegant and readable.
Of course, this code will throw a NullReferenceException as keyValuePairs are not initialized. I don't initialize it as it is an example and you have your own ITaskData class with proper initialization.
It should be something like this:
var tasks = taskDataList.Where(
i => i.TaskParams.Any(x => x.Key == "Location" && x.Value.Contains("Stockroom")) &&
i.TaskParams.Any(x => x.Key == "Iteration" && x.Values.Contains(2)));
The above code is just to explain the logic. You need to cast the object to the right type (if you know them) or user another comparison method.

Dynamic Linq Contains to filter a List

I have this Method
public static IEnumerable<T> Filter<T>(IEnumerable<T> source, string searchStr)
{
var propsToCheck = typeof(T).GetProperties().Where(a => a.PropertyType == typeof(string));
var filter = propsToCheck.Aggregate(string.Empty, (s, p) => (s == string.Empty ? string.Empty : string.Format("{0} OR ", s)) + string.Format("{0} == #0", p.Name).ToLower());
var filtered = source.AsQueryable().Where(filter, searchStr);
return filtered;
}
Which takes List and a search string and finds any matches in the list. However this only works for 100% matches, how can I make this case insensitive and use contains instead of a 100% match ?
Constructing a dynamic LINQ query doesn't look like the best option here. Filtering with a delegate would do better:
public static IEnumerable<T> Filter<T>(IEnumerable<T> source, string searchStr)
{
var searchStrLower = searchStr.ToLower();
var propsToCheck = typeof(T).GetProperties().Where(a => a.PropertyType == typeof(string) && a.CanRead);
return source.Where(obj => {
foreach (PropertyInfo prop in propsToCheck)
{
string value = (string)prop.GetValue(obj);
if (value != null && value.ToLower().Contains(searchStrLower)) return true;
}
return false;
});
}

Adapt function to included Field Names C# 2012.net 3.5

Is their anyway of changing the below to included all fields names only and values one thing i noticted that when testing this it also brought other information about the entitiy back im only wanting the fields that have been entered or changed by the user??
public static string ObjectToNotes(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj", "Value can not be null or Nothing!");
}
StringBuilder sb = new StringBuilder();
Type t = obj.GetType();
PropertyInfo[] pi = t.GetProperties();
for (int index = 0; index < pi.Length; index++)
{
sb.Append(pi[index].GetValue(obj, null));
if (index < pi.Length - 1)
{
sb.Append(Environment.NewLine);
}
}
return sb.ToString();
}
Right now this will save out the values for the entity only passed
As you can see from the image above the code is getting the values ok and fields but just not any drop downs related text
Help
Need more help with this how do i get the value of reference lookup values using the above method its only priting out the entity reference not the actual text value can this be done
Assuming by entered by the user you mean not having a string representation that is null or empty then try the following:
var properties = t.GetProperties();
var values = properties.Select(p => p.GetValue(obj, null)).Where(v => v != null && !string.IsNullOrEmpty(p.ToString());
var result = string.Join(Environment.NewLine, values);
In order to determine whcih fields have changed, you will need to pass two objects. One representing the entity in its pre-changed state and the other in its post-changed state and compare the properties:
var properties = t.GetProperties();
var before = properties.Select(p => new { property = p, value = p.GetValue(prechange, null) }).Where(v => v.value != null && !string.IsNullOrEmpty(p.value.ToString()).ToDictionary(p => p.property.Name, p => p.value);
var after = properties.Select(p => new { property = p, value = p.GetValue(postchange, null) }).Where(v => v.value != null && !string.IsNullOrEmpty(p.value.ToString()).ToDictionary(p => p.property.Name, p => p.value);
// You can then compare the keys / values of before and after dictionaries here

Categories

Resources