C# Reading multiple elements with same name using LINQ to XML - c#

Is there any better way doing this?
I have to get both property values.
XML always has only these 2 properties.
My xml:
<Template name="filename.txt">
<Property name="recordSeparator">\r\n</Property>
<Property name="fieldCount">16</Property>
</Template>
Linq:
var property = from template in xml.Descendants("Template")
select new
{
recordDelim = template.Elements("Property").Where(prop => prop.Attribute("name").Value == "recordSeparator")
.Select(f => new { f.Value }),
fieldCount = template.Elements("Property").Where(prop => prop.Attribute("name").Value == "fieldCount")
.Select(f => new { f.Value })
};

"Better way" depends on what exactly are you trying to achieve - performance, simplicity, etc.?
I guess I would create a class that contains what you are trying to get with anonymous classes.
public class Item {
public String Separator { get; set; }
public int FieldCount { get; set; }
}
and then I would modify the LINQ to:
var templates = from template in xml.Descendants("Template")
let children = template.Elements("Property")
select new Item() {
Separator = children.First(tag=>tag.Attribute("name").Value == "recordSeparator").Value,
FieldCount = Int32.Parse(children.First(tag=>tag.Attribute("name").Value == "fieldCount").Value)
};
List<Item> items = templates.ToList();
Note that this will cause NullReference exception in case your Template tag does not contain two Property tags, each with specified attributes.
Also it will throw an exception in parsing the integer from a FieldCount if it's not a number.
Idea:
If the xml generated is your own, and you can change it's format, why not do something like:
<Template>
<Name>filename.txt</Name>
<RecordSeparator>\r\n</RecordSeparator>
<FieldCount>16</FieldCount>
</Template>
It's easier to read and to parse, and it's a little bit shorter.
In the end, I think this is how I would do it:
Having this class:
public class Item
{
public String FileName { get; set; }
public String Separator { get; set; }
public int FieldCount { get; set; }
}
and this private method:
private Item GetItemFromTemplate(XElement node)
{
return new Item() {
FileName = node.Element("Name").Value,
Separator = node.Element("RecordSeparator").Value,
FieldCount = Int32.Parse(node.Element("FieldCount").Value)
};
}
I could do in code:
XDocument doc = XDocument.Load("myfile.txt");
List<Item> items = (from template in doc.Elements("Template")
select GetItemFromTemplate(template)).ToList();

This one is a little more efficient:
var properties =
from template in xml.Descendants("Template")
let propertyNodes = template.Elements("Property")
.Select(arg => new { Name = arg.Attribute("name").Value, Value = arg.Value })
select
new
{
recordDelim = propertyNodes.Single(arg => arg.Name == "recordSeparator").Value,
fieldCount = propertyNodes.Single(arg => arg.Name == "fieldCount").Value
};
If you have always one Template node:
var propertyNodes = xml.XPathSelectElements("/Template/Property")
.Select(arg => new { Name = arg.Attribute("name").Value, arg.Value })
.ToList();
var properties =
new
{
recordDelim = propertyNodes.Single(arg => arg.Name == "recordSeparator").Value,
fieldCount = propertyNodes.Single(arg => arg.Name == "fieldCount").Value
};

Related

Linq with expect: Only primitive types or enumeration types are supported in this context

I have this exeption: Unable to create a constant value of type ....ViewModels.Yarn.FilterNameDto'. Only primitive types or enumeration types are supported in this context.
ViewModels:
public class YarnListViewModel
{
public YarnListFilter YarnListFilter { get; set; }
public IEnumerable<FilterNameDto> FilterNames { get; set; }
}
public class YarnListFilter
{
public int? BrandId { get; set; }
public string ProductName { get; set; }
}
public class FilterNameDto
{
public string ProductName { get; set; }
}
In Contoller:
List<FilterNameDto> nlnames = new List<FilterNameDto>
{
new FilterNameDto { ProductName = "Farouche" },
new FilterNameDto { ProductName = "La Parisienne" },
...
};
var filternamesdb = _context.YarnFullAdmins
.Where(n => n.ProductName != null)
.GroupBy(n => n.ProductName)
.Select(n => n.FirstOrDefault());
if (yarnListFilter.BrandId > 0)
filternamesdb = filternamesdb.Where(b => b.BrandId == yarnListFilter.BrandId);
// Until here everything works fine
var filternamesdblist = filternamesdb.Select(n => new FilterNameDto
{
ProductName = n.ProductName,
}).Except(nlnames).ToList(); // I remove the names who are in the nlnames list
nlnames.AddRange(filternamesdblist); // And I add them so they come out first
var filternames = filternamesdblist;
if (yarnListFilter.BrandId == null || yarnListFilter.BrandId == 1)
filternames = nlnames;
var viewModel = new YarnListViewModel
{
FilterNames = filternames
};
return View(viewModel);
.Exept is my problem!
View:
#Html.DropDownListFor(f => f.YarnListFilter.ProductName
, new SelectList(Model.FilterNames, "ProductName", "ProductName")
,"Filter by Name"
, new { #class = "form-control", #onchange = "this.form.submit();" })
My goal is to have some of the items (referenced in nlnames List) who are actually in the query result (everywhere in this list) to come out first. So, I thought I remove them from the list and then add them so they are first listed.
Or is there (and I'm sure there is) a much better way to achieve this?!?!?
To resume it clear and short:
The database returns Aaa, Bbb, Ccc, Ddd, Eee
And I want Bbb, Ddd to be first!
Thanks in advance for your help.
The problem is your object nlnames can't be translated into something which SQL Server understands. In order to get around this you could call .ToList() before the .Except()
var filternamesdblist = filternamesdb
.Select(n => new FilterNameDto
{
ProductName = n.ProductName,
})
.ToList()
.Where(n => !nlnames.Any(nl => nl.ProductName == n.ProductName))
.ToList();
Alternatively, you could change the type of nlnames to List<string> which can be understood by SQL Server:
var nlnames = new List<string> { "Farouche", "La Parisienne" };
var filternamesdblist = filternamesdb
.Where(n => !nlnames.Contains(n.ProductName))
.Select(n => new FilterNameDto
{
ProductName = n.ProductName,
})
.ToList();

C# LINQ Filter list of complex objects by sub-list using a list of values

I want to return a list of active groups that are discounted in requested states. The list of groups each have a list of states which include the state abbrev and a discount flag.
filter criteria:
string[] states //list of state abbreviations
List to filter:
public class WorksiteGroup
{
public long Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public bool IsDiscontinued { get; set; }
public List<WorksiteGroupState> ActiveStates { get; set; } = new List<WorksiteGroupState>();
}
public class WorksiteGroupState
{
public string StateAbbrev { get; set; }
public bool IsDiscountApplied { get; set; }
}
Again, I want to return a list of WorksiteGroup with the full structure above where IsDiscontinued is false and have an ActiveState where StateAbbrev matches any of the filter criteria (states[]) and IsDiscountApplied is true for that state.
Let's do this step by step and then we can merge operations where necessary.
I want to return a list of WorksiteGroup with the full structure above
where IsDiscontinued is false
source.Where(e => !e.IsDiscontinued);
and have an ActiveState where StateAbbrev matches any of the filter
criteria (states[])
now let's take the previous pipeline and chain this criterion into it.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(a => states.Contains(a.StateAbbrev)))
and IsDiscountApplied is true for that state.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(s => states.Contains(s.StateAbbrev) && s.IsDiscountApplied));
for efficiency let's swap the Contains call to be after s.IsDiscountApplied e.g.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(s => s.IsDiscountApplied && states.Contains(s.StateAbbrev)));
You can try this using Linq:
string[] states = new string[] { "abbrev1", "abbrev2" };
var list = new List<WorksiteGroup>();
var item = new WorksiteGroup();
item.Name = "Test1";
item.IsDiscontinued = false;
var subitem = new WorksiteGroupState();
subitem.IsDiscountApplied = true;
subitem.StateAbbrev = "abbrev1";
item.ActiveStates.Add(subitem);
list.Add(item);
item = new WorksiteGroup();
item.Name = "Test2";
item.IsDiscontinued = true;
subitem = new WorksiteGroupState();
subitem.IsDiscountApplied = true;
subitem.StateAbbrev = "abbrev1";
item.ActiveStates.Add(subitem);
list.Add(item);
var result = list.Where(wg => wg.IsDiscontinued == false
&& wg.ActiveStates.Where(state => state.IsDiscountApplied == true
&& states.Contains(state.StateAbbrev)).Any());
foreach ( var value in result )
Console.WriteLine(value.Name);
Console.ReadKey();
You can play with items and add more to see results.
sudo-code but would something like below work, im sure you could do this is one line but
var worksiteGroup = Populate();
var filteredWorkSiteGroup = worksiteGroup .Where(x=>x.IsDiscontinued == false);
filteredWorkSiteGroup.ActiveStates = filteredWorkSiteGroup.ActiveStates
.Where(x=> states.Contains(x.StateAbbrev)
&& x.IsDiscountApplied == true);

How can I set the value of a parameter inside of an object?

I have this list that I am checking and then creating another list, where the definition is not equal to null.
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
public class WebWordForm
{
public string definition { get; set; }
public string partOfSpeech { get; set; }
public int sourceId { get; set; }
public List<string> synonyms { get; set; }
public List<string> typeOf { get; set; }
public List<string> hasTypes { get; set; }
public List<string> derivation { get; set; }
public List<string> examples { get; set; }
}
Is there a simple way that I could also set the sourceId in my rootObject.webWordForms list to the value of 2?
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.Select(w => new WebWordForm{
definition = w.definition,
partOfSpeech = w.partOfSpeech,
sourceId = 2,
synonyms= w.synonyms,
typeOf = w.typeOf,
hasTypes = w.hasTypes,
derivation = w.derivation,
examples = w.examples
}).ToList();
You could use List ForEach method to do this, but please mind this is nothing different than looping.
var list = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
list.ForEach(x=> x.sourceId =2);
Use .ForEach() on new, this would iterate on list new and update the param.
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.ForEach(n=>n.sourceId = 2);
Note - If the queried final list is your return you need to do above operations before returning anything.
While I am suggesting ForEach(), many articles focus on avoiding it.
An alternative,
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.All(n=>{
n.sourceId = 2;
return true;
});

Create expression tree to assign to property of list

This has been plaguing me for days now....
If I have a list of my own object SearchResults and SearchResults contains multiple lists of objects, all of which have a match (bool) property, How can I recreate an expression tree to achieve the following:
//searchResults is a List<SearchResults>
searchResults[i].Comments = searchResults[i].Comments.Select(p1 =>
{
p1.Match = ListOfStringVariable.All(p2 =>
{
string value = (string)typeof(CommentData).GetProperty(propertyName).GetValue(p1);
return value.Contains(p2);
});
return p1;
}).OrderByDescending(x => x.Match);
....
public class SearchResults
{
public IEnumerable<CommentData> Comments { get; set; }
public IEnumerable<AdvisorData> Advisors { get; set; }
}
public class CommentData
{
public string CommentText { get; set; }
public bool Match { get; set; }
}
public class AdvisorData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Match { get; set; }
}
The expression tree is needed as I won't know the property at compile-time that needs to be assigned, whether it is Comments, Advisors, etc (As this is a simplification of a larger problem). The above example is just for Comments, so how could the same code be used to assign to Advisors as well without having a conditional block?
Many thanks
Update:
So far using reflection we have the below from StriplingWarrior
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}
However orderedItems is of type System.Linq.OrderedEnumerable<IHaveMatchProperty,bool> and needs to be cast to IEnumerable<AdvisorData>. The below throws error:
'System.Linq.Enumerable.CastIterator(System.Collections.IEnumerable)' is a 'method' but is used like a 'type'
var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new[] {propertyType});
var result = castMethod.Invoke(null, new[] { orderedItems });
where propertyType is type AdvisorData
First, make your types implement this interface so you don't have to do quite so much reflection:
public interface IHaveMatchProperty
{
bool Match { get; set; }
}
Then write code to do something like this. (I'm making a lot of assumptions because your question wasn't super clear on what your intended behavior is.)
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}

Combining multiple linq queries and concatenated results into a smaller query

Can I restructure the following into a more compact linq query, ideally without the introduction of a helper function?
var revPerUnitChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Rev/Unit"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"),};
var costPerUnitChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Cost/Unit"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"), };
var numUnitsChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Units"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"), };
var changes =
revPerUnitChanges
.Concat(costPerUnitChanges
.Concat(numUnitsChanges))
.Where(c => c.From != c.To);
Start out by creating a helper class to hold onto the data. (Your code doesn't have any problems using anonymous types, but if you want to refactor sections into methods it'll be much easier with a named class.)
public class MyClass //TODO give better name
{
public MyClass(DataRow row, string field) //You could have a public static generate method if it doesn't make sense for this to be a constructor.
{
Field = field;
From = row.Field<decimal>(field, DataRowVersion.Original);
To = row.Field<decimal>(field, DataRowVersion.Current);
ItemIds = row.Field<string>("ItemIds");
}
public string Field { get; set; }
public decimal From { get; set; }
public decimal To { get; set; }
public string ItemIds { get; set; }
}
Now that we have that out of the way the query is fairly straightforward.
var changes = dataTable.GetChanges(DataRowState.Modified)
.AsEnumerable()
.Select(row => new[]{ //create 3 new items for each row
new MyClass(row, "Rev/Unit"),
new MyClass(row, "Cost/Unit"),
new MyClass(row, "Units"),
})
.SelectMany(item => item) //flatten the inner array
.Where(item => item.From != item.To);

Categories

Resources