Dynamic linq filter children using pivot - c#

I have business objects that look like the following:
class Project
{
public int ID
{
get;set;
}
public string ProjectName
{
get;set;
}
public IList<ProjectTag> ProjectTags
{
get;set;
}
}
class ProjectTag
{
public int ID
{
get;set;
}
public int ProjectID
{
get;set;
}
public string Name
{
get;set;
}
public string Value
{
get;set;
}
}
Example Data:
Project:
ID ProjectName
1 MyProject
ProjectTags:
ID ProjectID Name Value
1 1 Name 1 Value 1
2 1 Name 2 Value 2
3 1 Name 3 Value 3
Basically it's a way for our users to define their own columns on the Project. As a result, it's important to remember that I don't know the names of the ProjectTag entries at design time.
What I'm trying to accomplish is to give our users the ability to select projects based on search criteria using System.Linq.Dynamic. For instance, to select just the project in my example above, our users could enter this:
ProjectName == "MyProject"
The more complicated aspect is applying a filter to the ProjectTags. Our application currently allow users to do this in order to filter Projects by their ProjectTags:
ProjectTags.Any(Name == "Name 1" and Value == "Value 1")
That works, but starts to get a bit messy for end users to use. Ideally I'd like to write something that would let them do the following:
Name 1 == "Value 1"
Or if necessary (due to white space in the name), something like the following...
[Name 1] == "Value 1"
"Name 1" == "Value 1"
For lack of a better explanation, it seems like I want to do the equivalent of a SQL pivot on the ProjectTags, and then still be able to execute a where clause against that. I've looked at some of the questions on StackOverflow about pivots and dynamic pivoting, but I haven't found anything too useful.
I've also been thinking about looping through all the ProjectTag Names and building a dynamic query using a left join on each. I guess something like this:
select
Project.*,
Name1Table.Value [Name 1],
Name2Table.Value [Name 2],
Name3Table.Value [Name 3]
from
Project
left join ProjectTag Name1Table on Name = 'Name 1'
left join ProjectTag Name2Table on Name = 'Name 2'
left join ProjectTag Name3Table on Name = 'Name 3'
And then take that query and apply a where clause to it. But I'm not really sure how to do that in Linq as well as dealing with the white space in the name.
I also came across ExpandoObject. I thought possibly I could convert Project to an ExpandoObject. Then loop through all known ProjectTag names, adding each name to the ExpandoObject and, if that Project had a ProjectTag for that name, use that ProjectTag value as the value, else empty string. For example...
private static object Expand(
Project project,
List<string> projectTagNames)
{
var expando = new ExpandoObject();
var dictionary = (IDictionary<string, object>) expando;
foreach (var property in project.GetType()
.GetProperties())
{
dictionary.Add(property.Name, property.GetValue(project));
}
foreach (var tagName in projectTagNames)
{
var tagValue = project.ProjectTags.SingleOrDefault(p => p.Name.Equals(tagName));
dictionary.Add(tagName, tagValue?.Value ?? "");
}
return expando;
}
The exciting thing about this solution is I have an object that looks exactly like I think it should prior to filtering with a where clause. It even seems to accommodate spaces in the property name.
Then of course I found out that dynamic linq doesn't work nicely with ExpandoObject, and so it can't find the dynamic properties. I guess that's because it essentially has a type of Object which isn't going to define any of the dynamic properties. Maybe it's possible to generate a type at run time that matches? Even if that works, I don't think it can account for spaces in the Name.
Am I trying to accomplish too much with this functionality? Should I just tell the users to use syntax like ProjectTags.Any(Name == "Name1" and Value == "Value1")? Or is there some way to trick dynamic linq into understanding ExpandoObject? Seems like having a way to override the way dynamic linq resolves property names would be very handy.

How about using a translator to convert tag references?
I assume that tag names containing spaces will be surrounded by brackets ([]) and that Project field names are a known list.
public static class TagTranslator {
public static string Replace(this string s, Regex re, string news) => re.Replace(s, news);
public static string Surround(this string src, string beforeandafter) => $"{beforeandafter}{src}{beforeandafter}";
public static string SurroundIfMissing(this string src, string beforeandafter) => (src.StartsWith(beforeandafter) && src.EndsWith(beforeandafter)) ? src : src.Surround(beforeandafter);
public static string Translate(string q) {
var projectFields = new[] { "ID", "ProjectName", "ProjectTags" }.ToHashSet();
var opREStr = #"(?<op>==|!=|<>|<=|>=|<|>)";
var revOps = new[] {
new { Fwd = "==", Rev = "==" },
new { Fwd = "!=", Rev = "!=" },
new { Fwd = "<>", Rev = "<>" },
new { Fwd = "<=", Rev = ">=" },
new { Fwd = ">=", Rev = "<=" },
new { Fwd = "<", Rev = ">" },
new { Fwd = ">", Rev = "<" }
}.ToDictionary(p => p.Fwd, p => p.Rev);
var openRE = new Regex(#"^\[", RegexOptions.Compiled);
var closeRE = new Regex(#"\]$", RegexOptions.Compiled);
var termREStr = #"""[^""]+""|(?:\w|\.)+|\[[^]]+\]";
var term1REStr = $"(?<term1>{termREStr})";
var term2REStr = $"(?<term2>{termREStr})";
var wsREStr = #"\s?";
var exprRE = new Regex($"{term1REStr}{wsREStr}{opREStr}{wsREStr}{term2REStr}", RegexOptions.Compiled);
var tq = exprRE.Replace(q, m => {
var term1 = m.Groups["term1"].Captures[0].Value.Replace(openRE, "").Replace(closeRE, "");
var term1q = term1.SurroundIfMissing("\"");
var term2 = m.Groups["term2"].Captures[0].Value.Replace(openRE, "").Replace(closeRE, "");
var term2q = term2.SurroundIfMissing("\"");
var op = m.Groups["op"].Captures[0].Value;
if (!projectFields.Contains(term1) && !term1.StartsWith("\"")) { // term1 is Name, term2 is Value
return $"ProjectTags.Any(Name == {term1q} && Value {op} {term2})";
}
else if (!projectFields.Contains(term2) && !term2.StartsWith("\"")) { // term2 is Name, term1 is Value
return $"ProjectTags.Any(Name == {term2q} && Value {revOps[op]} {term1})";
}
else
return m.Value;
});
return tq;
}
}
Now you just translate your query:
var q = "ProjectName == \"Project1\" && [Name 1] == \"Value 1\" && [Name 3] == \"Value 3\"";
var tq = TagTranslator.Translate(q);

Related

Setting up collation for diacritic-insensitive search with MongoDB C#/.NET Driver

I'm trying to implement diacritics-insensitive search with MongoDB C# driver, e.g. when I search for "Joao", it should return all the results containing both "João" and "Joao" (if any).
The following command works on MongoDBCompass, i.e. if I run it against my MongoDB collection (currently only containing a document with "João", none with "Joao"), it will return the correct document:
db.profiles.find({FirstName:"Joao"}).collation({locale:"pt", strength: 1})
However, when I try to transpose it to C#, it won't work (e. g. doesn't return any result if I search for "Joao", only if I search for "João"):
private IFindFluent<ProfessionalProfile, ProfessionalProfile> BuildProfessionalLocationFilter(BaseQuery criteria)
{
FilterDefinition<ProfessionalProfile> filter = FilterDefinition<ProfessionalProfile>.Empty;
if (!string.IsNullOrEmpty(criteria.SearchWords))
{
var searchWords = criteria.SearchWords.ToLower().Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
FilterDefinition<ProfessionalProfile> searchWordFilter = FilterDefinition<ProfessionalProfile>.Empty;
foreach (string searchWord in searchWords)
{
var newFilter = Builders<ProfessionalProfile>.Filter.And(Builders<ProfessionalProfile>.Filter.Where(profile =>
profile.FirstName.ToLower().Contains(searchWord) ||
profile.LastName.ToLower().Contains(searchWord) ||
profile.Description.ToLower().Contains(searchWord) ||
profile.Email.ToLower().Contains(searchWord) ||
profile.Facebook.ToLower().Contains(searchWord) ||
profile.Instagram.ToLower().Contains(searchWord) ||
profile.LinkedIn.ToLower().Contains(searchWord) ||
profile.Locations.Any(location =>
location.Name.ToLower().Contains(searchWord) ||
location.District.Name.ToLower().Contains(searchWord) ||
location.Council.Name.ToLower().Contains(searchWord)) &&
!profile.IsDeleted &&
profile.WizardStep == 6));
if (searchWordFilter == FilterDefinition<ProfessionalProfile>.Empty)
searchWordFilter = newFilter;
else
searchWordFilter = searchWordFilter | newFilter;
}
filter = filter & searchWordFilter;
}
IFindFluent<ProfessionalProfile, ProfessionalProfile> findFluent = _professionalCollection.Find(filter);
findFluent.Options.Collation = new Collation("pt", strength: CollationStrength.Primary);
//IFindFluent<ProfessionalProfile, ProfessionalProfile> findFluent = _professionalCollection.Find(filter, new FindOptions() { Collation = new Collation("pt", strength: CollationStrength.Primary) } );
return findFluent;
}
Please note that I've also tried the commented line above, with the same (predictable) results.
What could be missing? Thanks.
EDIT:
As asked in the comment below by #Đĵ ΝιΓΞΗΛψΚ, I'm adding some information about the profile collection collation.
Mongo shell command:
db.getCollectionInfos({ name: 'profiles' })
Ouput:
[
{
name: 'profiles',
type: 'collection',
options: {},
info: {
readOnly: false,
uuid: UUID("4606f027-03b1-45e8-bf7f-6c99461db042")
},
idIndex: { v: 2, key: [Object], name: '_id_', ns: 'dev.profiles' }
}
]
it took a bit of investigating since documentation on the issue is scarse, but i think i figured out what's happening.
profile.FirstName.ToLower().Contains(searchWord) gets translated by the driver to a $regex query.
from what i can see, the regex search in mongodb is not collation aware. so you can't use regex functionality to do diacritic insensitive searches.
however, the solution to your requirement is to create a Text Index containing all of the fields you want to search in and utilize that index to do a diacritic & case insensitive search for your search words. it will also be the most efficient way to achieve your requirement.
the one limitation of using a text index is that it won't let you search for partial matches of words such as Jo. mongodb fulltext search only works on complete words unfortunately.
here's a test program (using mongodb.entities library for brevity):
using MongoDB.Driver;
using MongoDB.Entities;
using System.Threading.Tasks;
namespace TestApplication
{
public class ProfessionalProfile : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsDeleted { get; set; }
public int WizardStep { get; set; }
}
public static class Program
{
private static async Task Main()
{
await DB.InitAsync("test", "localhost");
await DB.Index<ProfessionalProfile>()
.Key(p => p.FirstName, KeyType.Text)
.Key(p => p.LastName, KeyType.Text)
.CreateAsync();
await new[] {
new ProfessionalProfile{ FirstName = "João", LastName = "Balboa", IsDeleted = false, WizardStep = 6},
new ProfessionalProfile{ FirstName = "Joao", LastName = "Balboa", IsDeleted = false, WizardStep = 6},
}.SaveAsync();
IAggregateFluent<ProfessionalProfile> aggregateFluent =
DB.FluentTextSearch<ProfessionalProfile>(
searchType: Search.Full,
searchTerm: "Joao Mary John",
caseSensitive: false,
diacriticSensitive: false)
.Match(p => !p.IsDeleted && p.WizardStep == 6);
var result = await aggregateFluent.ToListAsync();
}
}
}
i'm sure you won't have much trouble translating that to mongo driver code.

Is it possible to count the number of condition a row have satisfied in linq to Entity Framework

I was wondering if there is a way to count the number of conditions a row has satisfied in order to sort the return value by the one which qualified most of the condition.
Let's say the table is something like this:
and let's say the condition is size=M or color=blue or size<40
What I expect as a result is something like this:
Id3 (3 conditions)
Id4 (2 conditions)
Id1 (1 condition)
Id2 (1 condition)
Here is one way to implement conditional aggregation using linq.
Please note I've had to tweak your condition a bit because you wrote size=M or color=blue or size<40 - but size is either a string or a number, can't really be both - so I figured this is a typo and it was supposed to be size=M or color=blue or value<40.
Also, since you didn't provide an MCVE, I did that for you (Please try to create an MCVE for your next questions)
So let's start with a simple class for the data:
class Data
{
public Data(string id, string size, int value, string color)
{
Id = id;
Size = size;
Color = color;
Value = value;
}
public string Id {get;set;}
public string Size {get;set;}
public int Value {get;set;}
public string Color {get;set;}
public override string ToString()
{
return string.Format("Id = {0}, Size = {1}, Value = {2}, Color = {3}", Id, Size, Value, Color);
}
}
Now, let's create a list of that class and populate it with the sample data you've (kind of) provided:
var sampleData = new List<Data>()
{
new Data("Id1", "L", 35, "red"),
new Data("Id2", "L", 65, "blue"),
new Data("Id3", "M", 34, "blue"),
new Data("Id4", "S", 32, "blue"),
new Data("Id5", "S", 55, "green")
};
Since I didn't want to write the conditions twice, I've decided to first select a new anonymous type that contain the Data class and another property I've called ConditionsMatched to hold the number of conditions this data actually matches.
Then all I had to do is to filter the result of this select to return only those instances where the ConditionsMatched is larger than 0:
var result = sampleData
.Select(d => new
{
Data = d,
ConditionsMatched =
(d.Size == "M" ? 1 : 0) +
(d.Color == "blue" ? 1 : 0) +
(d.Value < 40 ? 1 : 0)
})
.Where(a => a.ConditionsMatched > 0);
The result is an IEnumerable<AnonymousType> that contains only the data that match at least one condition.
You can see a live demo on rextester.

Given a list of several of the same object, group and combine them based on field value

Sorry for the incoherent title. I don't know how to concisely explain my problem, which is why I didn't really know how to look it up. I'll explain using an example...
Let's say I have a class:
public class cas
{
public string name { get; set; }
public int num { get; set; }
}
With that class, I make several objects and stick them into a list. For the sake of example, I will make 4:
var list = new List<cas>
{
new cas { name = "firstname", num = 1 },
new cas { name = "firstname", num = 2 },
new cas { name = "lastname", num = 3 },
new cas { name = "lastname", num = 4 }
};
Is there a way to take this List and combine any objects with the same name field?
So, the new list would be 1 object with:
name = "firstname", num = 3,
name = "lastname", num = 7
There's the obvious "long" way to do it, but it would be clunky and expensive (go through the list several times to find like-objects). I was wondering if I was missing any clean way of doing it. I intentionally made a simple example so that the answer would be a proof of concept rather than writing my code for me. My actual problem is more complex than this, but I can't figure out this one aspect of it.
Using Linq, you have a GroupBy Method and a Select Method:
list = list.GroupBy(x=> x.name)
.Select(x=> new cas() { name = x.Key, num = x.Sum(y=> y.num) }).ToList();
Or using Elegant query-syntax:
list = (from item in list
group item by item.name into grouping
select new cas()
{
name = grouping.Key,
num = grouping.Sum(x => x.num)
}).ToList();
Note that to use these methods, you have to add using System.Linq at the top of your source file.
You can use linq, you would have to group them on name property and then sum on the num property of each group like:
var result = list.GroupBy(x=>x.name)
.Select(g=> new cas
{
name = g.Key,
num = g.Sum(x=>x.num)
});

Trying to get NetSuite Country list with enumeration value linked to code and name

I am implementing a integration with NetSuite in C#. In the external system I need to populate a list of countries that will match NetSuite's country list.
The NetSuite Web Service provides an enumeration call Country
public enum Country {
_afghanistan,
_alandIslands,
_albania,
_algeria,
...
You can also get a list of country Name and Code (in an albeit not so straight forward way) from the web service. (See: http://suiteweekly.com/2015/07/netsuite-get-all-country-list/)
Which gives you access to values like this:
Afghanistan, AF
Aland Islands, AX
Albania, AL
Algeria, DZ
American Samoa, AS
...
But, as you can see, there is no way to link the two together. (I tried to match by index but that didn't work and sounds scary anyway)
NetSuite's "help" files have a list. But this is static and I really want a dynamic solution that updates as NetSuites updates because we know countries will change--even is not that often.
Screenshot of Country Enumerations from NetSuite help docs
The only solutions I have found online are people who have provided static data that maps the two sets of data. (ex. suiteweekly.com /2015/07/netsuite-complete-country-list-in-netsuite/)
I cannot (don't want to) believe that this is the only solution.
Anyone else have experience with this that has a better solution?
NetSuite, if you are reading, come on guys, give a programmer a break.
The best solution I have come up with is to leverage the apparent relationship between the country name and the enumeration key to forge a link between the two. I am sure others could improve on this solution but what I would really like to see is a solution that isn't a hack like this that relies on an apparent pattern but rather on that is based on an explicit connection. Or better yet NetSuite should just provide the data in one place all together.
For example you can see the apparent relationship here:
_alandIslands -> Aland Islands
With a little code I can try to forge a match.
I first get the Enumeration Keys into an array. And I create a list of objects of type NetSuiteCountry that will hold my results.
var countryEnumKeys = Enum.GetNames(typeof(Country));
var countries = new List<NetSuiteCountry>();
I then loop through the list of country Name and Code I got using the referenced code above (not shown here).
For each country name I then strip all non-word characters from the country name with Regex.Replace, prepend an underscore (_) and then convert the string to lowercase. Finally I try to find a match between the Enumeration Key (converted to lowercase as well) and the matcher string that was created. If a match is found I save all the data together the countries list.
UPDATE: Based on the comments I have added additional code/hacks to try to deal with the anomalies without hard-coding exceptions. Hopefully these updates will catch any future updates to the country list as well, but no promises. As of this writing it was able to handle all the known anomalies. In my case I needed to ignore Deprecated countries so those aren't included.
foreach (RecordRef baseRef in baseRefList)
{
var name = baseRef.name;
//Skip Deprecated countries
if (name.EndsWith("(Deprecated)")) continue;
//Use the name to try to find and enumkey match and only add a country if found.
var enumMatcher = $"_{Regex.Replace(name, #"\W", "").ToLower()}";
//Compares Ignoring Case and Diacritic characters
var enumMatch = CountryEnumKeys.FirstOrDefault(e => string.Compare(e, enumMatcher, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) == 0);
//Then try by Enum starts with Name but only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => e.ToLower().StartsWith(enumMatcher));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 1 : ");
enumMatch = matches.First();
}
}
//Then try by Name starts with Enum but only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => enumMatcher.StartsWith(e.ToLower()));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 2 : ");
enumMatch = matches.First();
}
}
//Finally try by first half Enum and Name match but again only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => e.ToLower().StartsWith(enumMatcher.Substring(0, (enumMatcher.Length/2))));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 3 : ");
enumMatch = matches.First();
}
}
if (enumMatch != null)
{
var enumIndex = Array.IndexOf(CountryEnumKeys, enumMatch);
if (enumIndex >= 0)
{
var country = (Country) enumIndex;
var nsCountry = new NetSuiteCountry
{
Name = baseRef.name,
Code = baseRef.internalId,
EnumKey = country.ToString(),
Country = country
};
Debug.WriteLine($"[{nsCountry.Name}] as [{nsCountry.EnumKey}]");
countries.Add(nsCountry);
}
}
else
{
Debug.WriteLine($"Could not find Country match for: [{name}] as [{enumMatcher}]");
}
}
Here is my NetSuiteCountry class:
public class NetSuiteCountry
{
public string Name { get; set; }
public string Code { get; set; }
public string EnumKey { get; set; }
public Country Country { get; set; }
}
Let me start off with a disclaimer that I'm not a coder, and this is the first day I've tried to look at a C# program.
I need something similar for a Javascript project where I need the complete list of Netsuite company names, codes and their numeric values and when reading the help it seemed like the only way was through webservices.
I downloaded the sample application for webservices from Netsuite and a version of Visual Studio and I was able to edit the sample program provided to create a list of all of the country names and country codes (ex. Canada, CA).
I started out doing something similar to the previous poster to get the list of country names:
string[] countryList = Enum.GetNames(typeof(Country));
foreach (string s in countryList)
{
_out.writeLn(s);
}
But I later got rid of this and started a new technique. I created a class similar to the previous answer:
public class NS_Country
{
public string countryCode { get; set; }
public string countryName { get; set; }
public string countryEnum { get; set; }
public string countryNumericID { get; set; }
}
Here is the new code for getting the list of company names, codes and IDs. I realize that it's not very efficient as I mentioned before I'm not really a coder and this is my first attempt with C#, lots of Google and cutting/pasting ;D.
_out.writeLn(" Attempting to get Country list.");
// Create a list for the NS_Country objects
List<NS_Country> CountryList = new List<NS_Country>();
// Create a new GetSelectValueFieldDescription object to use in a getSelectValue search
GetSelectValueFieldDescription countryDesc = new GetSelectValueFieldDescription();
countryDesc.recordType = RecordType.customer;
countryDesc.recordTypeSpecified = true;
countryDesc.sublist = "addressbooklist";
countryDesc.field = "country";
// Create a GetSelectValueResult object to hold the results of the search
GetSelectValueResult myResult = _service.getSelectValue(countryDesc, 0);
BaseRef[] baseRef = myResult.baseRefList;
foreach (BaseRef nsCountryRef in baseRef)
{
// Didn't know how to do this more efficiently
// Get the type for the BaseRef object, get the property for "internalId",
// then finally get it's value as string and assign it to myCountryCode
string myCountryCode = nsCountryRef.GetType().GetProperty("internalId").GetValue(nsCountryRef).ToString();
// Create a new NS_Country object
NS_Country countryToAdd = new NS_Country
{
countryCode = myCountryCode,
countryName = nsCountryRef.name,
// Call to a function to get the enum value based on the name
countryEnum = getCountryEnum(nsCountryRef.name)
};
try
{
// If the country enum was verified in the Countries enum
if (!String.IsNullOrEmpty(countryToAdd.countryEnum))
{
int countryEnumIndex = (int)Enum.Parse(typeof(Country), countryToAdd.countryEnum);
Debug.WriteLine("Enum: " + countryToAdd.countryEnum + ", Enum Index: " + countryEnumIndex);
_out.writeLn("ID: " + countryToAdd.countryCode + ", Name: " + countryToAdd.countryName + ", Enum: " + countryToAdd.countryEnum);
}
}
// There was a problem locating the country enum that was not handled
catch (Exception ex)
{
Debug.WriteLine("Enum: " + countryToAdd.countryEnum + ", Enum Index Not Found");
_out.writeLn("ID: " + countryToAdd.countryCode + ", Name: " + countryToAdd.countryName + ", Enum: Not Found");
}
// Add the countryToAdd object to the CountryList
CountryList.Add(countryToAdd);
}
// Create a JSON - I need this for my javascript
var javaScriptSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
string jsonString = javaScriptSerializer.Serialize(CountryList);
Debug.WriteLine(jsonString);
In order to get the enum values, I created a function called getCountryEnum:
static string getCountryEnum(string countryName)
{
// Create a dictionary for looking up the exceptions that can't be converted
// Don't know what Netsuite was thinking with these ones ;D
Dictionary<string, string> dictExceptions = new Dictionary<string, string>()
{
{"Congo, Democratic Republic of", "_congoDemocraticPeoplesRepublic"},
{"Myanmar (Burma)", "_myanmar"},
{"Wallis and Futuna", "_wallisAndFutunaIslands"}
};
// Replace with "'s" in the Country names with "s"
string countryName2 = Regex.Replace(countryName, #"\'s", "s");
// Call a function that replaces accented characters with non-accented equivalent
countryName2 = RemoveDiacritics(countryName2);
countryName2 = Regex.Replace(countryName2, #"\W", " ");
string[] separators = {" ","'"}; // "'" required to deal with country names like "Cote d'Ivoire"
string[] words = countryName2.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < words.Length; i++)
{
string word = words[i];
if (i == 0)
{
words[i] = char.ToLower(word[0]) + word.Substring(1);
}
else
{
words[i] = char.ToUpper(word[0]) + word.Substring(1);
}
}
string countryEnum2 = "_" + String.Join("", words);
// return an empty string if the country name contains Deprecated
bool b = countryName.Contains("Deprecated");
if (b)
{
return String.Empty;
}
else
{
// test to see if the country name was one of the exceptions
string test;
bool isExceptionCountry = dictExceptions.TryGetValue(countryName, out test);
if (isExceptionCountry == true)
{
return dictExceptions[countryName];
}
else
{
return countryEnum2;
}
}
}
In the above I used a function, RemoveDiacritics I found here. I will repost the referenced function below:
static string RemoveDiacritics(string text)
{
string formD = text.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
foreach (char ch in formD)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(ch);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(ch);
}
}
return sb.ToString().Normalize(NormalizationForm.FormC);
}
Here are the tricky cases to test any solution you develop with:
// Test tricky names
Debug.WriteLine(getCountryEnum("Curaçao"));
Debug.WriteLine(getCountryEnum("Saint Barthélemy"));
Debug.WriteLine(getCountryEnum("Croatia/Hrvatska"));
Debug.WriteLine(getCountryEnum("Korea, Democratic People's Republic"));
Debug.WriteLine(getCountryEnum("US Minor Outlying Islands"));
Debug.WriteLine(getCountryEnum("Cote d'Ivoire"));
Debug.WriteLine(getCountryEnum("Heard and McDonald Islands"));
// Enums that fail
Debug.WriteLine(getCountryEnum("Congo, Democratic Republic of")); // _congoDemocraticPeoplesRepublic added to exceptions
Debug.WriteLine(getCountryEnum("Myanmar (Burma)")); // _myanmar added to exceptions
Debug.WriteLine(getCountryEnum("Netherlands Antilles (Deprecated)")); // Skip Deprecated
Debug.WriteLine(getCountryEnum("Serbia and Montenegro (Deprecated)")); // Skip Deprecated
Debug.WriteLine(getCountryEnum("Wallis and Futuna")); // _wallisAndFutunaIslands added to exceptions
For my purposes I wanted a JSON object that had all the values for Coutries (Name, Code, Enum, Value). I'll include it here in case anyone is searching for it. The numeric values are useful when you have a 3rd party HTML form that has to forward the information to a Netsuite online form.
Here is a link to the JSON object on Pastebin.
My appologies for the lack of programming knowledge (only really do a bit of javascript), hopefully this additional information will be useful for someone.

How do I use LINQ Contains(string[]) instead of Contains(string)

I got one big question.
I got a linq query to put it simply looks like this:
from xx in table
where xx.uid.ToString().Contains(string[])
select xx
The values of the string[] array would be numbers like (1,45,20,10,etc...)
the Default for .Contains is .Contains(string).
I need it to do this instead: .Contains(string[])...
EDIT : One user suggested writing an extension class for string[]. I would like to learn how, but any one willing to point me in the right direction?
EDIT : The uid would also be a number. That's why it is converted to a string.
Help anyone?
spoulson has it nearly right, but you need to create a List<string> from string[] first. Actually a List<int> would be better if uid is also int. List<T> supports Contains(). Doing uid.ToString().Contains(string[]) would imply that the uid as a string contains all of the values of the array as a substring??? Even if you did write the extension method the sense of it would be wrong.
[EDIT]
Unless you changed it around and wrote it for string[] as Mitch Wheat demonstrates, then you'd just be able to skip the conversion step.
[ENDEDIT]
Here is what you want, if you don't do the extension method (unless you already have the collection of potential uids as ints -- then just use List<int>() instead). This uses the chained method syntax, which I think is cleaner, and
does the conversion to int to ensure that the query can be used with more providers.
var uids = arrayofuids.Select(id => int.Parse(id)).ToList();
var selected = table.Where(t => uids.Contains(t.uid));
If you are truly looking to replicate Contains, but for an array, here is an extension method and sample code for usage:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ContainsAnyThingy
{
class Program
{
static void Main(string[] args)
{
string testValue = "123345789";
//will print true
Console.WriteLine(testValue.ContainsAny("123", "987", "554"));
//but so will this also print true
Console.WriteLine(testValue.ContainsAny("1", "987", "554"));
Console.ReadKey();
}
}
public static class StringExtensions
{
public static bool ContainsAny(this string str, params string[] values)
{
if (!string.IsNullOrEmpty(str) || values.Length > 0)
{
foreach (string value in values)
{
if(str.Contains(value))
return true;
}
}
return false;
}
}
}
Try the following.
string input = "someString";
string[] toSearchFor = GetSearchStrings();
var containsAll = toSearchFor.All(x => input.Contains(x));
LINQ in .NET 4.0 has another option for you; the .Any() method;
string[] values = new[] { "1", "2", "3" };
string data = "some string 1";
bool containsAny = values.Any(data.Contains);
Or if you already have the data in a list and prefer the other Linq format :)
List<string> uids = new List<string>(){"1", "45", "20", "10"};
List<user> table = GetDataFromSomewhere();
List<user> newTable = table.Where(xx => uids.Contains(xx.uid)).ToList();
How about:
from xx in table
where stringarray.Contains(xx.uid.ToString())
select xx
This is an example of one way of writing an extension method (note: I wouldn't use this for very large arrays; another data structure would be more appropriate...):
namespace StringExtensionMethods
{
public static class StringExtension
{
public static bool Contains(this string[] stringarray, string pat)
{
bool result = false;
foreach (string s in stringarray)
{
if (s == pat)
{
result = true;
break;
}
}
return result;
}
}
}
This is a late answer, but I believe it is still useful.
I have created the NinjaNye.SearchExtension nuget package that can help solve this very problem.:
string[] terms = new[]{"search", "term", "collection"};
var result = context.Table.Search(terms, x => x.Name);
You could also search multiple string properties
var result = context.Table.Search(terms, x => x.Name, p.Description);
Or perform a RankedSearch which returns IQueryable<IRanked<T>> which simply includes a property which shows how many times the search terms appeared:
//Perform search and rank results by the most hits
var result = context.Table.RankedSearch(terms, x => x.Name, x.Description)
.OrderByDescending(r = r.Hits);
There is a more extensive guide on the projects GitHub page: https://github.com/ninjanye/SearchExtensions
Hope this helps future visitors
Linq extension method. Will work with any IEnumerable object:
public static bool ContainsAny<T>(this IEnumerable<T> Collection, IEnumerable<T> Values)
{
return Collection.Any(x=> Values.Contains(x));
}
Usage:
string[] Array1 = {"1", "2"};
string[] Array2 = {"2", "4"};
bool Array2ItemsInArray1 = List1.ContainsAny(List2);
I believe you could also do something like this.
from xx in table
where (from yy in string[]
select yy).Contains(xx.uid.ToString())
select xx
So am I assuming correctly that uid is a Unique Identifier (Guid)? Is this just an example of a possible scenario or are you really trying to find a guid that matches an array of strings?
If this is true you may want to really rethink this whole approach, this seems like a really bad idea. You should probably be trying to match a Guid to a Guid
Guid id = new Guid(uid);
var query = from xx in table
where xx.uid == id
select xx;
I honestly can't imagine a scenario where matching a string array using "contains" to the contents of a Guid would be a good idea. For one thing, Contains() will not guarantee the order of numbers in the Guid so you could potentially match multiple items. Not to mention comparing guids this way would be way slower than just doing it directly.
You should write it the other way around, checking your priviliged user id list contains the id on that row of table:
string[] search = new string[] { "2", "3" };
var result = from x in xx where search.Contains(x.uid.ToString()) select x;
LINQ behaves quite bright here and converts it to a good SQL statement:
sp_executesql N'SELECT [t0].[uid]
FROM [dbo].[xx] AS [t0]
WHERE (CONVERT(NVarChar,[t0].[uid]))
IN (#p0, #p1)',N'#p0 nvarchar(1),
#p1 nvarchar(1)',#p0=N'2',#p1=N'3'
which basicly embeds the contents of the 'search' array into the sql query, and does the filtering with 'IN' keyword in SQL.
I managed to find a solution, but not a great one as it requires using AsEnumerable() which is going to return all results from the DB, fortunately I only have 1k records in the table so it isn't really noticable, but here goes.
var users = from u in (from u in ctx.Users
where u.Mod_Status != "D"
select u).AsEnumerable()
where ar.All(n => u.FullName.IndexOf(n,
StringComparison.InvariantCultureIgnoreCase) >= 0)
select u;
My original post follows:
How do you do the reverse? I want to
do something like the following in
entity framework.
string[] search = new string[] { "John", "Doe" };
var users = from u in ctx.Users
from s in search
where u.FullName.Contains(s)
select u;
What I want is to find all users where
their FullName contains all of the
elements in `search'. I've tried a
number of different ways, all of which
haven't been working for me.
I've also tried
var users = from u in ctx.Users select u;
foreach (string s in search) {
users = users.Where(u => u.FullName.Contains(s));
}
This version only finds those that
contain the last element in the search
array.
The best solution I found was to go ahead and create a Table-Valued Function in SQL that produces the results, such as ::
CREATE function [dbo].[getMatches](#textStr nvarchar(50)) returns #MatchTbl table(
Fullname nvarchar(50) null,
ID nvarchar(50) null
)
as begin
declare #SearchStr nvarchar(50);
set #SearchStr = '%' + #textStr + '%';
insert into #MatchTbl
select (LName + ', ' + FName + ' ' + MName) AS FullName, ID = ID from employees where LName like #SearchStr;
return;
end
GO
select * from dbo.getMatches('j')
Then, you simply drag the function into your LINQ.dbml designer and call it like you do your other objects. The LINQ even knows the columns of your stored function. I call it out like this ::
Dim db As New NobleLINQ
Dim LNameSearch As String = txt_searchLName.Text
Dim hlink As HyperLink
For Each ee In db.getMatches(LNameSearch)
hlink = New HyperLink With {.Text = ee.Fullname & "<br />", .NavigateUrl = "?ID=" & ee.ID}
pnl_results.Controls.Add(hlink)
Next
Incredibly simple and really utlizes the power of SQL and LINQ in the application...and you can, of course, generate any table valued function you want for the same effects!
I believe that what you really want to do is:
let's imagine a scenario
you have two database
and they have a table of products in common
And you want to select products from the table "A" that id has in common with the "B"
using the method contains would be too complicated to do this
what we are doing is an intersection, and there is a method called intersection for that
an example from msdn:
http://msdn.microsoft.com/en-us/vcsharp/aa336761.aspx#intersect1
int [] numbers = (0, 2, 4, 5, 6, 8, 9);
int [] numbersB = (1, 3, 5, 7, 8);
var = commonNumbers numbersA.Intersect (numbersB);
I think what you need is easily solved with intersection
Check this extension method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ContainsAnyProgram
{
class Program
{
static void Main(string[] args)
{
const string iphoneAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like...";
var majorAgents = new[] { "iPhone", "Android", "iPad" };
var minorAgents = new[] { "Blackberry", "Windows Phone" };
// true
Console.WriteLine(iphoneAgent.ContainsAny(majorAgents));
// false
Console.WriteLine(iphoneAgent.ContainsAny(minorAgents));
Console.ReadKey();
}
}
public static class StringExtensions
{
/// <summary>
/// Replicates Contains but for an array
/// </summary>
/// <param name="str">The string.</param>
/// <param name="values">The values.</param>
/// <returns></returns>
public static bool ContainsAny(this string str, params string[] values)
{
if (!string.IsNullOrEmpty(str) && values.Length > 0)
return values.Any(str.Contains);
return false;
}
}
}
from xx in table
where xx.uid.Split(',').Contains(string value )
select xx
Try:
var stringInput = "test";
var listOfNames = GetNames();
var result = from names in listOfNames where names.firstName.Trim().ToLower().Contains(stringInput.Trim().ToLower());
select names;
var SelecetdSteps = Context.FFTrakingSubCriticalSteps
.Where(x => x.MeetingId == meetid)
.Select(x =>
x.StepID
);
var crtiticalsteps = Context.MT_CriticalSteps.Where(x =>x.cropid==FFT.Cropid).Select(x=>new
{
StepID= x.crsid,
x.Name,
Checked=false
});
var quer = from ax in crtiticalsteps
where (!SelecetdSteps.Contains(ax.StepID))
select ax;
string texto = "CALCA 40";
string[] descpart = texto.Split(' ');
var lst = (from item in db.InvItemsMaster
where descpart.All(val => item.itm_desc.Contains(val))
select item
).ToList();
Console.WriteLine("ITM".PadRight(10) + "DESC".PadRight(50)+"EAN".PadRight(14));
foreach(var i in lst)
{
Console.Write(i.itm_id.ToString().PadRight(10));
Console.Write(i.itm_desc.ToString().PadRight(50));
Console.WriteLine(i.itm_ean.ToString().PadRight(14));
}
Console.ReadKey();
string[] stringArray = {1,45,20,10};
from xx in table
where stringArray.Contains(xx.uid.ToString())
select xx
Dim stringArray() = {"Pink Floyd", "AC/DC"}
Dim inSQL = From alb In albums Where stringArray.Contains(alb.Field(Of String)("Artiste").ToString())
Select New With
{
.Album = alb.Field(Of String)("Album"),
.Annee = StrReverse(alb.Field(Of Integer)("Annee").ToString())
}

Categories