Assign a column value if it throws an exception in Linq query - c#

I have a query where one property is a path "/Primary/secondary/tertiary/.../.../"
My task is to split this path by the slashes, so every sub-path can be assigned as a property in the query result.
The problem is, that the length varies. Some paths have a post-split array length of 1, some of 7. So I need to have 7 different category columns:
var result = MySource.Where(ms => ...)
.Select(ms => new {
ID = ms.ID,
Name = ms.Name,
Category1 = ms.Path.Split('/')[0],
Category2 = ms.Path.Split('/')[1] //exception
.... //exception
Category7 = ms.Path.Split('/')[6] //exception
});
After the path gets split, the resulting array is of various length (1 - 7) leading into an ArgumentOutOfRangeException. How can I circumvent this exceptions?
I have tried using the nullcoalescence operator ms.Path.Split('/')[1] ?? "N/A", which did not help, because there is no result but an exception thrown. Because of this every shorthand-if statement will fail as well.
Is there a way to catch the exception (wrap in try catch block?) so I can assign a default value if the array is out of bounds?

Your modeling seems a little broken. Instead of a flattened set of properties, populate a single collection. Something like this:
Select(ms => new {
ID = ms.ID,
Name = ms.Name,
Categories = ms.Path.Split('/')
})
Going a step further, you can create an actual (non-anonymous) model to hold this information, encapsulating the logic of category range checking. Something like:
Select(ms => new SomeObject(
ms.ID,
ms.Name,
ms.Path.Split('/')
))
Then in SomeObject you can have all sorts of logic, for example:
In the constructor you can perform input checking on the values, including the count of categories supplied, to ensure the object is valid.
You can keep the collection of categories private and expose properties for 1-7 if you really need to, which internally perform this check. (Though I really don't recommend that. It creates an unnecessary point of change for something that's already handled by a collection, indexing values.) Something like:
public string Category1
{
get
{
if (categories.Length < 1)
return string.Empty;
return categories[0];
}
}
Maybe throw an exception instead of returning an empty string? Maybe do something else? The point is to encapsulate this logic within an object instead of in a LINQ query or in consuming code.

you can do
Category7 = ms.Path.Split('/').ElementAtOrDefault(6) ?? "N/A",
see demo: https://dotnetfiddle.net/4nTBhq
ElementAtOrDefault return the element at index (for example 6, like [6]) but if out of bound return null.
optimized, without calling Split multiple times:
.Select(ms => {
var categories = ms.Path.Split('/');
return new {
ID = ms.ID,
Name = ms.Name,
...
Category7 = categories.ElementAtOrDefault(6),
};
})

Related

Handling nonexistant JSON tokens in JSON.Net how to return default instead of throwin exception

I have a problem, under http://www.pathofexile.com/api/public-stash-tabs link there is a huge API that returns a JSON string. Many of fields in this JSON are optional, that means they only appear if there is value present.
So theoretical "Item1" can have "abyssJewel" property but
"item2" doesnt have to have "abyssJewel" property
When i try to query this JSON with JSON.Linq like this
AbyssJewel = (bool)item["abyssJewel"];
in the case of Item1 everything is good and it returns proper value
but in case of Item2 i get exception "InvalidOperationException, Cannot access child value on Newtonsoft.Json.Linq.JProperty"
I understand its because for Item2 no abyssJewel property in JSON exists so it throws exception.
My question is this, how can i handle it so that instead of throwing exception it would return a default or null value for this particular field?
I have tried playing with Activator but couldnt make anything working on my own. Any tips?
im instantiating it like this:
apiPages.Add(new Page
{
Next_Change_Id = (string)jsonObject["next_change_id"],
Stashes = jsonObject["stashes"].Select(stash => new Stash
{
AccountName = (string)stash["accountName"],
StashName = (string)stash["stash"],
StashType = (string)stash["stashType"],
Public = (bool)stash["public"],
LastCharacterName = (string)stash["lastCharacterName"],
UniqueId = (string)stash["id"],
Items = stash.Select(item => new Item
{
AbyssJewel = (bool)item["abyssJewel"],
...tl;dr...
Instead of casting directly you should try to use the TryParse() method from the Boolean class, if something goes wrong it must return false. See here
Hope it will fix your problem.

C# Converting List to Enumerable and Selecting With LINQ

I have a question related to an Enumerable and list of integers. I have the below code that is throwing an error stating "'int' does not contain a definition for 'Field' and has some invalid arguments." I'm sure this is something easy, but was wondering if anyone could help out. Thanks!
public static IList<Site> GetSiteFromSites(DataTable data)
{
var linqRegions = Enumerable.Empty<int>();
IList<Site> sites = data.AsEnumerable().Select(r =>
{
return new Site()
{
id = r.Field<string>("id"),
name = r.Field<string>("name"),
address_line1 = r.Field<string>("address_line1"),
address_line2 = r.Field<string>("address_line2"),
post_code = r.Field<string>("post_code"),
county = r.Field<string>("county"),
city = r.Field<string>("city"),
phone_number = r.Field<string>("phone_number"),
regions = linqRegions.Where(u => u.Field<int>("regions") == r.Field<int>("regions")).ToList().Select(z => z.Field<int>("regions")).ToList()
//error is thrown for above line of code "regions"
};
}).ToList();
return sites;
}
The variable linqRegions is an empty enumerable of ints.
In the line where the error is thrown, you are calling the Where() method where you try to get the Field property of each element. Because each element is an int, it throws the 'int' does not contain a definition for 'Field' error.
Change your line with the error to this:
regions = linqRegions.Where(u => u == r.Field<int>("regions")).ToList()
This will always return zero elements (because linqRegions is empty), so don't forget to populate it. And even then, the line doesn't make any sense because it will always return a list of the same values (values equal to r.Field<int>("regions")).

How to ignore Symbols in string comparison and dictionary lookup

It looks like I have a problem with the symbols encodings between string built by my program and string retrieved from another datasource.
Here is a .NET Fiddle and here is the explanation:
var context = new List<Foo>
{
new Foo { Name = "SoW.Probing.4GEPCCore.CaptSite[1].S1U" },
new Foo { Name = "SoW.Probing.4GEPCCore.CaptSite[2].S1U" },
new Foo { Name = "SoW.Probing.2G3GPSCore.CaptSite[1].GnGpU" },
new Foo { Name = "SoW.Probing.2G3GPSCore.CaptSite[2].GnGpU" }
};
var nameToCheckPresence = GetStringFromAnotherDataSource(); // the value of the string is for example: "SoW.Probing.4GEPCCore.CaptSite.S1U"
nameToCheckPresence = nameToCheckPresence.Replace("CaptSite", "CaptSite[1]");
var foo = context.FirstOrDefault(f => f.Name == nameToCheckPresence); // Should return an object since one object does have that name
My problem is that foo is null. It works if I use this code line:
var foo = context .FirstOrDefault(f => CultureInfo.CurrentCulture.CompareInfo.Compare(f.Name, nameToCheckPresence , CompareOptions.IgnoreSymbols) == 0);
So clearly, I have a problem with symbols encoding (the .? the [ ]?). My true problem is that later, I am doing the same thing with a dictionary. The hashcode of the strings are different and the dictionary lookup also failed:
var dictionary = context.ToDictionary(f => f.Name);
var foo = dictionary[nameToCheckPresence]; // Should return the object but failed and throw a KeyNotFoundException
Is there a way to change the string symbols encoding in a global manner in the application? (WPF application in my case)
As the context can be very large, it is planed to use a Dictionary also in the first place. So if you provide me a solution that only works with Dictionary, it is not a problem.
Just for the record, the datasource is a SQLite database in which is a copy of the data of a MySQL database filled by another WPF application (running on the same computer, no specific culture setup). Finally, the nameToCheckPresence is extracted from a larger string by ANTLR4CS.
This is not a satisfactory answer, but that's all I find to solve the problem. Instead of looking into the dictionary through the indexor, I am doing a linq query:
dictionary.FirstOrDefault(pair => CultureInfo.CurrentCulture.CompareInfo.Compare(pair.Key, localFactName, CompareOptions.IgnoreSymbols) == 0).Value;
But doing this, I lost all the benefit of the dictionary access complexity. If anyone has a better solution, I will take it!

C# Linq Filter IEnumerable dynamic property and value

public class Sample
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime EffectiveDate { get; set; }
}
IEnumerable<Sample> sampleList;
//Populate the list
Now I want to filter the list some times by "Id" property, sometimes "Description" property. Just want to pass the property name (filterColumn) and property value (filterValue) both as strings.
I tried the following:
IEnumerable<Sample> result = sampleList.Where(x => x.GetType().GetProperty(filterColumn).Name == filterValue);
and
string whereQuery = string.Format(" {0} = \"{1}\"", filterColumn, filterValue);
IEnumerable<Sample> result = sampleList.AsQueryable().Where(whereQuery);
The second option works, if i pass the filterColumn as "Description", but throws incompatible '=' operator between string and int error when "Id" is passed as filterColumn and some filterValue like "1".
Appreciate any help. Thanks
Your first approach can work. Expanding on Jon Skeet's comment, here's the adjusted statement.
IEnumerable<Sample> result = sampleList.Where(
x => x.GetType().GetProperty(filterColumn).GetValue(x, null).Equals(filterValue)
);
To put a little context around this, you would have to allow for the differing data types. You can do this at least two ways: use a generic method or use the object data type. For illustrative purposes, I'll use the object approach.
public IEnumerable<Sample> GetFiltered(
IEnumerable<Sample> samples, string filtercolumn, object filtervalue
{
return samples.Where(
x => x.GetType().GetProperty(filtercolumn).GetValue(x, null).Equals(filtervalue)
);
}
IEnumerable<Sample> sampleList;
var byId = GetFiltered(sampleList, "Id", 100);
var byDescription = GetFiltered(sampleList, "Description", "Some Value");
This example is not really safe as there is no type checking to ensure that the property value will be of the same data type that you are passing in. For example, there is nothing stopping you from passing "Description" and 100 as parameters. You can't do a meaningful comparison between an integer and a string so you will always come up with an empty result. The Equals method does not throw an exception, it just sees that the two objects are different. As Jon pointed out, you'll always want to use Equals in this case rather than the "==" operator. The Equals method is intended to compare content while "==" compares references. Example:
Console.WriteLine(12 == 12);
// True
object a = 12;
object b = 12;
Console.WriteLine(a == b);
// False - because, due to boxing, a and b are separate objects
// that happen to contain the same value. (Check out "boxing"
// if this doesn't make sense.)
Console.WriteLine(a.Equals(b));
// True - because the Equals method compares content (value)
Also, note that strings have some special behaviors when using the "==" operator. The important thing to remember is that there is a difference between references (containers) and content. You want to compare content and that means Equals. (I have noticed that the Immediate window in Visual Studio is inconsistent in its results regarding strings when using "==". I suspect this is because string references can be, but are not always, optimized in that window.)
You state that your second approach works. I have not seen this type of filter string in a standard IEnumerable.Where method. So I am guessing that you are using some extensions. Your example does not work as shown. The DataTable class uses filter strings that match your usage. In general, a filter string has to be constructed in different ways based on data type. For example, a string requires the quotes (which you have) but an integer value does not use quotes.
Another option that you have is to set up a dictionary with the required operations.
public IEnumerable<Sample> GetFiltered(
IEnumerable<Sample> samples, string property, string value)
{
var map = new Dictionary<string, Func<string, Func<Sample, bool>>>()
{
{ "Description", v => s => s.Description == v },
{ "Id", v => s => s.Id == int.Parse(v) },
};
return samples.Where(map[property](value));
}
The advantage here is that you can perform a more complex comparison, such as adding custom filters by ranges of values, or those containing more than one property.

RavenDB - Index sorting for Alphanumeric property

I am trying to index an alphanumeric property that should be sorted as follows:
WS1-1-14-1
WS1-1-15-1
WS1-1-15-2
WS1-1-15-3
I created an index that removes the non numeric characters of the property and set the SortOptions to Int so i can order then as above.
Here is my index definition:
Map = fixtures => from f in fixtures
select new
{
FixtureNumber = f.FixtureNumber.Replace("-", "").Replace("WS", ""),
};
Sort(x => x.FixtureNumber, SortOptions.Int);
The sorting works fine when i do and OrderBy on the Index:
session.Query<Fixture, Fixtures_All>.OrderBy(x => x.FixtureNumber).ToList()
But my problem is that when i do a dynamic Query on my Fixture document its like the query is still using my index:
session.Query<Fixture>.FirstOrDefault(x => x.FixtureNumber == "WS1-1-1");
Returns no results, which i would expect it would as im not querying my index. But the following does work, which i find strange:
var fixNo = "WS1-15-1".Replace("-", "").Replace("WS", "");
session.Query<Fixture>.FirstOrDefault(x => x.FixtureNumber == fixNo);
Why is my index affecting this dynamic index query ?
Also is there a better way to index my property without resorting to string replace to make it an integer ?
Thanks
Note:
If i just use the default string comparer on the SortOptions the results get sorted like so:
WS1-1-15-1
WS1-1-15-10
WS1-1-15-2
WS1-1-15-3
...
Raven will always look for an index on the given type and for an indexed property (by name) on that type when doing an dynamic index. If it can't find one, it will auto-generate one first.
When you issue the following query:
session.Query<Fixture>.FirstOrDefault(x => x.FixtureNumber == "WS1-1-1");
Raven will find that there is indeed an index on Fixture on the property FixtureNumber defined by the Fixtures_All index. The problem here is that the stored FixtureNumber inside the index is altered (stripped dash and letters) so Raven will use that to map your query (it doesn't know that it's been altered).
If you look at the index in the Raven studio you can select "Index Entries" to show what the actual index looks like on disk:
This is why you don't get any results when issue the query.
If you want to use an altered property in the index you can do the following:
Map = fixtures => from f in fixtures
select new
{
ShortenFixtureNumber = f.FixtureNumber.Replace("-", "").Replace("WS", ""),
FixtureNumber = f.FixtureNumber
};
Sort("ShortenFixtureNumber", SortOptions.Int);
Then you can create something I like to call a "Query Model" (not sure if that's the right name):
public class FixtureQueryModel
{
public string FixtureNumber { get; set; }
public int ShortenFixtureNumber { get; set; }
}
With this, I can query both by the index using the "Query model" and also query directly on the type:
var fixture = session.Query<FixtureQueryModel, Fixtures_All>()
.Where(x => x.FixtureNumber.StartsWith("WS1"))
.OrderBy(x => x.ShortenFixtureNumber)
.As<Fixture>()
.ToList();
var fixture2 = session.Query<Fixture>()
.FirstOrDefault(x => x.FixtureNumber == "WS1-1-14-1");
However, this might seem rather complicated and in your case I think the default string sort comparer would work directly on an un-altered FixtureNumber, but hopefully this post provides some help into what's going on with indexes.

Categories

Resources