Using Linq or Lambda to compare items in nested collections - c#

I have an object which looks something like this
Object
Name
ID
PropertyGroupList[]
PropertyGroupListItem
PropertyList[]
PropertyListItem
Tag
Type
PropertyListItem
Tag
Type
PropertyGroupListItem
PropertyList[]
PropertyListItem
Tag
Type
PropertyListItem
Tag
Type
Each of those objects has a PropertyListItem where the Tag is "Revision". I need to compare the value of "Revision" with all of the other objects stored in the list and return the item where "Revision" has the highest value.
I think I can build a way with nested for loops but I thought it would be a better approach to get the Object by using a Linq or Lambda expression.
I've been trying to find a way to do this by myself but I feel like everything I did is completely wrong. I'd be more than happy if someone could help me and give a little explanation about it. Thanks a lot!
EDIT:
Sample code:
public MdsObject GetSoftwareObjectByName(string sPackageName)
{
GetObjectListRequest getObjectListReq = new GetObjectListRequest();
InitializeRequest(getObjectListReq);
//TODO: Are there more characters which need to be escaped in an LDAP query to DSM?
sPackageName = sPackageName.Replace("(", "\\(");
sPackageName = sPackageName.Replace(")", "\\)");
getObjectListReq.LdapQuery = "<LDAP://rootDSE>;(Name:IgnoreCase=" + sPackageName + ");;subtree";
getObjectListReq.MaxResults = -1;
GetObjectListReply getObjectListReply = AdministrationService.GetObjectList(getObjectListReq);
switch (getObjectListReply.ObjectList.Length)
{
case 0:
{ throw new ApplicationException("GetSoftwareObjectByName failed. Could not find '" + sPackageName + "'"); }
case 1:
{
MdsObject incompleteObjectFromLdap = getObjectListReply.ObjectList[0];
return GetSoftwareObjectById(incompleteObjectFromLdap.ID);
}
//more than one object was returned -> check revisions
default:
{
List<MdsObject> ListReturnedObjects = new List<MdsObject>();
for (int i = 1; i <= getObjectListReply.ObjectList.Length; i++)
{
MdsObject incompleteObjectFromLdap = getObjectListReply.ObjectList[i-1];
ListReturnedObjects.Add(GetSoftwareObjectById(incompleteObjectFromLdap.ID));
}
**Here I need to filter the objects**
throw new ApplicationException("GetSoftwareObjectByName failed. Software name '" + sPackageName + "' is not unique!");
}
}
}

First Start off by flatening your nested hierarchy down to an IEnumerable
var q = MyObject.PropertyGroupList
.SelectMany(item=>item.PropertyList); // For every Item in the GroupList, Flatten it and return the individual PropertyListItem
Then find the one with the highest revision
var q2 = q.OrderbyDescending(item=>item.Tag) // Order them by tag starting with the largest
.First(); // And get the first and thus biggest one.

Related

How to fix 'No property or field exists in type' error?

I got this error when trying to sort any columns that are not in the Order table, if I use OrderBy("Customer.CompanyName" + " " + sortDir) the error will gone but all the columns will become unable to sort. The OrderBy method used below come from here.
What is the cause of the problem ?
public ActionResult WebGrid(int page = 1, int rowsPerPage = 10, string sortCol = "OrderID", string sortDir = "ASC")
{
List<Order> res;
using (var nwd = new NorthwindEntities())
{
var _res = nwd.Orders
.OrderBy(sortCol + " " + sortDir)
.Select(o => new Order
{
OrderID = o.OrderID,
OrderDate = o.OrderDate,
CompanyName = o.Customer.CompanyName,
FirstName = o.Employee.FirstName,
//......
//......
//......
});
The class you provided the link to is marked as internal and it can't be used outside the assembly it was defined in, so you can't use it in your code.
This API supports the product infrastructure and is not intended to be
used directly from your code. Provides functionality to create new
classes from values in a LinqDataSource control.
So What you're actually trying to use is OrderBy inside Queryable class which is part of System.Linq which can be used as following:
.OrderBy(x=> x.sortCol + " " + x.sortDir)
If you're trying to order by two columns, then you can use:
.OrderBy(x=> x.sortCol).ThenBy(x=> x.sortDir)
If you want to dynamically specify the OrderBy expression, you can either do a switch statement for each possible parameter, or follow this SO Answer to build a dynamic Expression Tree.

Dynamic Lambda condition on nested array

I want to perform a Dynamic Lambda in a collection with an array of strings[] on it:
public class ClassStudentsViewModel
{
public string[] Disciplines { get; set; }
public TeacherName { get; set; }
}
This is what I'm trying:
The source is a collection of ClassStudentsViewModel and the values is an array of strings with one string. When executed, it throws me this exception:
No property or field 'y' exists in type 'String'
After some searching, I have found this question which is almost the same problem and the OP ended changing the source code of Dynamic.cs, that isn't a nice option for me. I wonder what I'm trying isn't supported or might be a bug. The problem is that the above mantioned question was asked almost 4 years ago.
The following snippet works nice:
classStudents.AsQueryable().Where(x => x.Disciplines.Any(y => y == "Turma 2")).ToList();
How can I get rid of that error?
UPDATE:
A little context of what I'm trying: My controller receives a viewModel with a collection of filters sent by the 3rd party grid, which contains basically a value and a operator, like eq, gt etc... A method loops all those filters and transforms on lambda operators, like eq to == or contains to .Contains(). In a simple string property like TeacherName(updated viewModel above), the dynamic filters works, e.g. if the predicate in the screenshot is: "TeacherName.Contains(#0)" it works well.
UPDATE 2:
This code generates the predicate:
public static string ToLambdaOperator(string field, string oper, int index, string sufix = null)
{
var result = String.Empty;
switch (oper)
{
case "eq":
case "neq":
case "gte":
case "gt":
case "lte":
case "lt":
result = string.Format(field + ToLinqOperator(oper) + "#" + index);
break;
case "startswith":
result = field + ".StartsWith(" + "#" + index + ")";
break;
case "endswith":
result = field + ".EndsWith(" + "#" + index + ")";
break;
case "contains":
result = field + ".Contains(" + "#" + index + ")";
break;
case "doesnotcontain":
result = "!" + field + ".Contains(" + "#" + index + ") || " + field + ".Equals(String.Empty)";
break;
}
if (!String.IsNullOrEmpty(sufix))
{
result += sufix;
}
return result;
}
// Use example
var operator = "eq";
var paramCounter = -1;
var predicate = ToLambdaOperator("Disciplines.Any(y => y", operator, ++paramCounter, ")");
The predicate above will result: Disciplines.Any(y => y == #0). With the operator contains will result in this: Disciplines.Any(y => y.Contains(#0)).
I think what you are trying to do is to generate an expression tree based on the arguments provided. Here are some examples about how to do that.
https://gist.github.com/afreeland/6733381
How to: Use Expression Trees to Build Dynamic Queries (C# and Visual Basic)

XML linq need detail info on exception

I am using xml linq on my project. I am dealing with very large xml's for easy understanding purpose I have mentioned small sample xml.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<StackOverflowReply xmlns="http://xml.stack.com/RRAND01234">
<processStatus>
<statusCode1>P</statusCode1>
<statusCode2>P</statusCode2>
<statusCode3>P</statusCode3>
<statusCode4>P</statusCode4>
</processStatus>
</StackOverflowReply>
</soap:Body>
Following is C# xml linq
XNamespace x = "http://xml.stack.com/RRAND01234";
var result = from StackOverflowReply in XDocument.Parse(Myxml).Descendants(x + "Security_AuthenticateReply")
select new
{
status1 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode1").Value,
status2 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode2").Value,
status3 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode3").Value,
status4 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode4").Value,
status5 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode5").Value,
};
Here I am getting exception like "Object reference not set to an instance of an object.". Because the tag
<statusCode5>
was not in my xml.In this case I want to get detail exception message like "Missing tag statusCode5". Please guide me how to get this message from my exception.
There's no easy way (that I'm aware of) to find out exactly what element(s) was/were missing in a LINQ to XML statement. What you can do however is use (string) on the element to handle missing elements - but that can get tricky if you have a chain of elements.
That wouldn't work in your current code:
status5 = (string)StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode5")
Becuase (string) will only work on first element, and the second one is the one that is missing.
You could change your LINQ to focus only on the subnodes, like this:
XNamespace x = "http://xml.stack.com/RRAND01234";
var result = from StackOverflowReply in XDocument.Parse(Myxml).Descendants(x + "processStatus")
select new
{
status1 = (string)StackOverflowReply.Element(x + "statusCode1"),
status2 = (string)StackOverflowReply..Element(x + "statusCode2"),
status3 = (string)StackOverflowReply..Element(x + "statusCode3"),
status4 = (string)StackOverflowReply.Element(x + "statusCode4"),
status5 = (string)StackOverflowReply.Element(x + "statusCode5"),
};
However, if your XML is complex and you have different depths (nested elements), you'll need a more robust solution to avoid a bunch of conditional operator checks or multiple queries.
I have something that might help if that is the case - I'll have to dig it up.
EDIT For More Complex XML
I've had similar challenges with some XML I have to deal with at work. In lieu of an easy way to determine what node was the offending node, and not wanting to have hideously long ternary operators, I wrote an extension method that worked recursively from the specified starting node down to the one I was looking for.
Here's a somewhat simple and contrived example to demonstrate.
<SomeXML>
<Tag1>
<Tag1Child1>Value1</Tag1Child1>
<Tag1Child2>Value2</Tag1Child2>
<Tag1Child3>Value3</Tag1Child3>
<Tag1Child4>Value4</Tag1Child4>
</Tag1>
<Tag2>
<Tag2Child1>
<Tag2Child1Child1>SomeValue1</Tag2Child1Child1>
<Tag2Child1Child2>SomeValue2</Tag2Child1Child2>
<Tag2Child1Child3>SomeValue3</Tag2Child1Child3>
<Tag2Chidl1Child4>SomeValue4</Tag2Child1Child4>
<Tag2Child1>
<Tag2Child2>
<Tag2Child2Child1>
<Tag2Child2Child1Child1 />
<Tag2Child2Child1Child2 />
</Tag2Child2>
</Tag2>
</SomeXML>
In the above XML, I had no way of knowing (prior to parsing) if any of the children elements were empty, so I after some searching and fiddling I came up with the following extension method:
public static XElement GetChildFromPath(this XElement currentElement, List<string> elementNames, int position = 0)
{
if (currentElement == null || !currentElement.HasElements)
{
return currentElement;
}
if (position == elementNames.Count - 1)
{
return currentElement.Element(elementNames[position]);
}
else
{
XElement nextElement = currentElement.Element(elementNames[position]);
return GetChildFromPath(nextElement, elmenentNames, position + 1);
}
}
Basically, the method takes the XElement its called on, plus a List<string> of the elements in path order, with the one I want as the last one, and a position (index in the list), and then works it way down the path until it finds the element in question or runs out of elements in the path. It's not as elegant as I would like it to be, but I haven't had time to refactor it any.
I would use it like this (based on the sample XML above):
MyClass myObj = (from x in XDocument.Parse(myXML).Descendants("SomeXML")
select new MyClass() {
Tag1Child1 = (string)x.GetChildFromPath(new List<string>() {
"Tag1", "Tag1Child1" }),
Tag2Child1Child4 = (string)x.GetChildFromPath(new List<string>() {
"Tag2", "Tag2Child1", "Tag2Child1Child4" }),
Tag2Child2Child1Child2 = (string)x.GetChildFromPath(new List<string>() {
"Tag2", "Tag2Child2", "Tag2Child2Child1",
"Tag2Child2Child1Child2" })
}).SingleOrDefault();
Not as elegant as I'd like it to be, but at least it allows me to parse an XML document that may have missing nodes without blowing chunks. Another option was to do something like:
Tag2Child2Child1Child1 = x.Element("Tag2") == null ?
"" : x.Element("Tag2Child2") == null ?
"" : x.Element("Tag2Child2Child1") == null ?
"" : x.Element("Tag2Child2Child1Child2") == null ?
"" : x.Element("Tag2")
.Element("Tag2Child2")
.Element("Tag2Child2Child1")
.Element("Tag2Child2Child1Child2").Value
That would get really ugly for an object that had dozens of properties.
Anyway, if this is of use to you feel free to use/adapt/modify as you need.

Procedurally create a dynamic object for Dapper

I've seen many posts about creating ExpandoObject objects and such, but it does not work in my case. I need to create an object like
var someObj = new {
term1 = "someValue",
term2 = "other",
...
};
Basically, we are using Dapper and we need to create a query dynamically, where the WHERE clause is fabricated from a given array of arguments. We are not generalizing queries! It's a single method receiving a variable number of arguments and we need to check OR each value on a single column.
Right now, the only viable solution is to revert and directly use System.Data.SqlClient.SqlConnection, or is there any way to make this work?
Update:
This is what most likely should work, but doesn't :
string inWhere = null;
dynamic inTerms = new ExpandoObject();
IDictionary<string, object> inTermsDict = inTerms;
if (!(string.IsNullOrEmpty(filter.Term) || string.IsNullOrWhiteSpace(filter.Term))) {
inWhere = "(" + string.Join(" OR ", filter.Terms.Select((t, i) => "{0} LIKE #p" + i)) + ")";
int termIndex = 0;
foreach (string term in filter.Terms) {
inTermsDict.Add("p" + (termIndex++), term);
}
}
// ...
var rows = db.Query("SELECT * FROM {table} WHERE {baseCondition}" +
(string.IsNullOrEmpty(inWhere) ? "" : string.Format(" AND " + inWhere, "columnName")),
inTerms as object);
Just to answer my own question, as we found the proper solution earlier today.
Simply put, we found the IDynamicParameters And this class simply solves everything, acting as a Dictionary.
var inTerms = new Dapper.DynamicParameters();
inTerms.Add("#p" + (termIndex++), somveValue);
Everyone's happy!

replacing variable in elements of a List

A more simple example might be:
List <myElement> Elements;
Elements.Add(my1);
Elements.Add(my2);
my1 and my2 eache have a variable of type string named myString
Now I want to change the value of my1.myString. But if I change it the value my2.myString gets changed aswell.
Hopefully it's a bit clearer now
I'm using a List with several Elements within it in C#. The List has as type a self-defined class with several variables in it.
Now I want to change in one list-element the value of a variable. But unfortunately the value gets replaced not only on this but in all elements of this list.
Any advice on how to fix this?
MyProjectElement File1 = this.Project.Elements[0];
MyProjectElement File2 = this.Project.Elements[1];
MyProject my1 = (MyProject)File1;
MyProject my2 = (MyProject)File2;
PageCount_F1 = my1.PageCount;
PageCount_F2 = my2.PageCount;
if (PageCount_F1 != PageCount_F2)
MessageBox.Show("The 2 files need to have the same file length", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
else
{
my1.IncludeAllPages = false;
my2.IncludeAllPages = false;
for(int i=1; i <= PageCount_F1; i++)
{
StringBuilder value1 = new StringBuilder();
StringBuilder value2 = new StringBuilder();
value1.Append("" + i);
value2.Append("" + (PageCount_F2-i+1));
MyProject my1new = new MyProject();
MyProject my2new = new MyProject();
my1new.Pages = value1.ToString();
my2new.Pages = value2.ToString();
my1.Pages = my1new.Pages;
my2.Pages = my2new.Pages;
this.Project.Elements.Add((myProjectElement)my1);
this.Project.Elements.Add((myProjectElement)my2);
((MyProject)this.Project.Elements[1]).Pages.Remove(0);
((MyProject)this.Project.Elements[i]).Pages.Remove(0);
((MyProject)this.Project.Elements[1]).Pages = "" + 1;
((MyProject)this.Project.Elements[PageCount_F2 - i + 1]).Pages = "" + (PageCount_F2 - i + 1);
((MyProject)this.Project.Elements[i-1]).Pages.Remove(0);
((MyProject)this.Project.Elements[i]).Pages.Remove(0);
((MyProject)this.Project.Elements[i - 1]).Pages = "" + i;
((MyProject)this.Project.Elements[i]).Pages = "" + (PageCount_F2 - i + 1);
}
You need to specify what the condition is to make a change. For example:
myList.ForEach(x => if(whateverCondition) x.myString = "blah" );
But you really need to ascertain what that condition is.
The problem is that in the code that you've put up you're changing your values in a loop that steps through every element and the loop starts indexing the List with index 1, whereas the first element in index 0.
If you're only after changing one element then select that element. Moo-Juice has posted a good suggestion (+1 btw).
p.s. don't post links to external stores with your code, many users that sit behind corporate firewalls can't access them.

Categories

Resources