ASP.NET MVC Select list value using key - c#

I am still reasonably new to C# and trying to understand list objects.
I have a list object with key/value pairs in it, populated from a database (that part is working). I then want to select a value from that list, using the 'key'. From what I have read I think Linq might be the best way to do this, however I cannot quite work out the syntax I need.
I have a list object as follows:
// Create a list of Items from the database, where Value is the 'key' and Text is the 'value'
IEnumerable<SelectListItem> ItemList = dbLists.ItemList.Select(x => new SelectListItem { Text = x.Description, Value = x.Id.ToString() });
I then want to populate another variable with the description of the selected item in the model, where the model only saves the id.
I have tried various linq Where and Select queries, but cannot work it out.
The easiest way I can think to explain what I am trying to achieve is to use sql syntax,
string SelectedItemDescription = SELECT Text FROM ItemList WHERE ItemList.Value = model.ItemCode
An example of a scenario would be something like:
ItemList
{
(Value = "1", Text = "Item 1"),
(Value = "2", Text = "Item 2"),
(Value = "3", Text = "Item 3"),
...
};
model.ItemCode = 2;
// How do I make:
SelectedItemDesctiption = "Item 2";
Hopefully that makes sense..
Thanks.

you can do like this in LINQ, if you write it this way:
string SelectedItemDescription = (from list in ItemList
where list.Value = model.ItemCode
select list.Text).FirstOrDefault();
or you can Use Extension Methods, If it will always return a single element then you can use SingleOrDefault():
SelectListItem SelectedItemDescription = ItemList.SingleOrDefault(item=>item.Value == model.ItemCode);
if(SelectedItemDescription !=null)
{
string Key = SelectedItemDescription .Value;
string Text = SelectedItemDescription.Text;
}
If it will return a multiple objects then you can use FirstOrDefault():
SelectListItem SelectedItemDescription = ItemList.FirstOrDefault(item=>item.Value == model.ItemCode);
if(SelectedItemDescription !=null)
{
string Key = SelectedItemDescription .Value;
string Text = SelectedItemDescription.Text;
}
UPDATED:
As #CodeGeek suggested in comments, you can also do like this:
string SelectedItemDescription =ItemList
.SingleOrDefault(item=>item.Value == model.ItemCode)!=null ? ItemList.SingleOrDefault(item=>item.Value == model.ItemCode).Text : String.Empty;

Related

Dynamic linq filter children using pivot

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);

Search a Json object array- List vs Dictionary

I deserialized a JsonResponse using the below code.
var data = (JObject)JsonConvert.DeserializeObject(jsonResponse);
I got the response string which looks something like this
{
"results":[
{
"url":"tickets/2063.json",
"id":20794,
"subject":"Device not working",
"created_date": "2018-01-10T13:03:23Z",
"custom-fields":[
{
"id":25181002,
"value":34534
},
{
"id":2518164,
"value":252344
}
]
}
]
}
My objective is to read certain fields in this array of json objects and insert into a database. The fields i require are id, subject, created_date, member_id.
The member id is part of the custom fields. member_id is the value where id=2518164. I've used List to store this, can you let me know if List or Dictionary is better for this case. How to implement a dictionary
var data = (JObject)JsonConvert.DeserializeObject(jsonResponse);
var tickets = data["results"].ToList();
foreach (var ticketItem in tickets){
Int64? ticketFormId = ticketItem["id"].Value<Int64>();
string subject = ticketItem["subject"].Value<string>();
DateTime createdDate = ticketItem["created_date"].Value<DateTime>();
//Do you think for the next step a dictionary is better or a List is better, since I want to search for a particular id=2518164
var fieldsList = ticketItem["fields"].ToList();
foreach(var fieldItem in fieldList){
Int64? fieldId = fieldItem["id"].Value<Int64>();
if(fieldId!=null && fieldId == 2518164){
memberId = fieldItem["value"].Value<string>();
}
}
}
If you're next step to insert them all into the database, just store them into a list. A dictionary is only useful to search the item by a key.
You can also use linq to process the json in a simpler way:
var tickets = JObject.Parse(jsonResponse)["results"]
.Select(ticket => new
{
Id = (long)ticket["id"],
Subject = (string)ticket["subject"],
CreatedDate = (DateTime)ticket["created_date"],
MemberId = (long)ticket["custom-fields"]
.FirstOrDefault(cf => (int)cf["id"] == 2518164)
?["value"],
})
.ToList();

Convert String[] to List<SelectList>

Is there an easy way, without looping over all the items in an array manually, to convert to a SelectList from an array of strings for a drop down menu?
I'm assuming you need either a SelectList or a List<SelectListTiem>, not a List<SelectList>. SelectList has a constructor that takes a collection:
string[] strings = new [] { .. strings .. };
SelectList sl = new SelectList(strings);
or you can project to a List<SelectListItem>:
string[] strings = new [] { .. strings .. };
var sl = strings.Select(s => new SelectListItem {Value = s})
.ToList();
Note that SelectList implements IEnumerable<SelectListItem>, so if you have a model property of type IEnumerable<SelectListItem> you can create a SelectList and assign it to that property rather than projecting to a List<SelectListItem>. It's functionally the same but the code will be a little cleaner.
This is all assuming we're talking about MVC, not Web Forms
Second to D Stanley's answer, another solution:
string[] strings = new [] { ... strings ... };
var selectListItems = strings.Select(x => new SelectListItem() { Text = x, Value = x, Selected = x == "item 1" });
A list of SelectListItem can also be used to populate an MVC drop down list.
With this method, you can also set other properties on a SelectListItem such as, display value.
We can't call Select on a IQueryable using the SelectListItem constructor because LINQ will try and convert that to SQL. Which unless there is a provider for it, is impossible, and also not what we want to achieve.
In order to always assure we can enumerate like I have shown above, we need to force EF or other ORMs to get all our data back. We can do this by calling ToList() BEFORE we enumerate with Select:
var selectListItems = strings.ToList().Select(x => new SelectListItem() { Text = x, Value = x, Selected = x == "item 1" });
As #BCdotWEB has pointed out:
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField
)
Is the constructor that this list will inevitably get put into. If I can remember correctly, the razor view should look like this:
#Html.DropDownListFor(x => x.SelectedString, new SelectList(x.Strings, "Value", "Text"))
Where x.SelectedString is where you want the chosen value from the drop down to be put. x.Strings is that selectListItems we created in the Controller/Service
I assume you can use the SelectList Constructor, since it accepts an IEnumerable:
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField
)
if you are talking about asp.net webForms you can use this code
string[] stringArray= new []{"var1",...};
Dictionary<string,string> listItemSource=stringArray.ToDictionary(i => i, i =>i);
yourDropDownList.DataSource=listItemSource;
yourDropDownList.DataValueField = "value";
yourDropDownList.DataTextField = "key";
yourDropDownList.DataBind();

Querying a list of strings with a query string?

I have a dictionary:
<string,List<string>>
The key is the product code say "product1" then the list is a list of properties:
"Brand","10.40","64","red","S"
Then I 'can' have a list of rules/filters e.g.
var tmpFilter = new customfilters();
tmpFilter.Field = "2";
tmpFilter.Expression = ">";
tmpFilter.Filter = "10";
So for the above example this would pass because at index 2 (tmpFilter.Field) it is more than 10; then I have another object which defines which fields within the list I want to write to file. For that dictionary item I just want to write the product brand and price where the filters match.
At the moment without the filter I have:
var tmp = new custom();
tmp.Columns = "0,1";
tmp.Delimiter = ",";
tmp.Extention = ".csv";
tmp.CustomFilters = new List<customfilters>() {new customfilters(){ Field = "2", Expression = ">", Filter = "10"} };
public static void Custom(custom custom)
{
foreach (var x in Settings.Prods)
{
//Get Current Product Code
var curprod = Settings.ProductInformation[x];// the dictionary value
foreach (var column in custom.Columns)
{
var curVal = curprod[Convert.ToInt32(column)];
tsw.Write(curVal + custom.Delimiter);
}
Settings.Lines++;
tsw.WriteLine();
}
tsw.Close();
}
I only want to write the curprod if all the filters pass for that list of strings.
How I can do this?
There's a really nice Nuget package based on an example published by Microsoft, that they have decided to make really hard to find for some reason, that allows dynamic linq queries:
https://www.nuget.org/packages/System.Linq.Dynamic/1.0.2
Source:
https://github.com/kahanu/System.Linq.Dynamic
Using that you can do stuff like this very easily (note: I used strings here because the OP states they have a List<string>):
List<string> stuff = new List<string> { "10.40", "64", "5", "56", "99", "2" };
var selected = stuff.Select(s => new { d = double.Parse(s) }).Where("d > 10");
Console.WriteLine(string.Join(", ", selected.Select(s => s.d.ToString()).ToArray()));
Outputs:
10.4, 64, 56, 99
That may give you a place to start. One thing you are going to have to tackle is identifying which of your fields are numeric and should be converted to a numeric type before trying to apply your filter. Otherwise you are going to comparing as strings.

How do I order a sql datasource of uniqueidentifiers in Linq by an array of uniqueindentifiers

I have a string list(A) of individualProfileId's (GUID) that can be in any order(used for displaying personal profiles in a specific order based on user input) which is stored as a string due to it being part of the cms functionality.
I also have an asp c# Repeater that uses a LinqDataSource to query against the individual table. This repeater needs to use the ordered list(A) to display the results in the order specified.
Which is what i am having problems with. Does anyone have any ideas?
list(A)
'CD44D9F9-DE88-4BBD-B7A2-41F7A9904DAC',
'7FF2D867-DE88-4549-B5C1-D3C321F8DB9B',
'3FC3DE3F-7ADE-44F1-B17D-23E037130907'
Datasource example
IndividualProfileId Name JobTitle EmailAddress IsEmployee
3FC3DE3F-7ADE-44F1-B17D-23E037130907 Joe Blo Director dsd#ad.com 1
CD44D9F9-DE88-4BBD-B7A2-41F7A9904DAC Maxy Dosh The Boss 1
98AB3AFD-4D4E-4BAF-91CE-A778EB29D959 some one a job 322#wewd.ocm 1
7FF2D867-DE88-4549-B5C1-D3C321F8DB9B Max Walsh CEO 1
There is a very simple (single-line) way of doing this, given that you get the employee results from the database first (so resultSetFromDatabase is just example data, you should have some LINQ query here that gets your results).
var a = new[] { "GUID1", "GUID2", "GUID3"};
var resultSetFromDatabase = new[]
{
new { IndividualProfileId = "GUID3", Name = "Joe Blo" },
new { IndividualProfileId = "GUID1", Name = "Maxy Dosh" },
new { IndividualProfileId = "GUID4", Name = "some one" },
new { IndividualProfileId = "GUID2", Name = "Max Walsh" }
};
var sortedResults = a.Join(res, s => s, e => e.IndividualProfileId, (s, e) => e);
It's impossible to have the datasource get the results directly in the right order, unless you're willing to write some dedicated SQL stored procedure. The problem is that you'd have to tell the database the contents of a. Using LINQ this can only be done via Contains. And that doesn't guarantee any order in the result set.
Turn the list(A), which you stated is a string, into an actual list. For example, you could use listAsString.Split(",") and then remove the 's from each element. I’ll assume the finished list is called list.
Query the database to retrieve the rows that you need, for example:
var data = db.Table.Where(row => list.Contains(row.IndividualProfileId));
From the data returned, create a dictionary keyed by the IndividualProfileId, for example:
var dic = data.ToDictionary(e => e.IndividualProfileId);
Iterate through the list and retrieve the dictionary entry for each item:
var results = list.Select(item => dic[item]).ToList();
Now results will have the records in the same order that the IDs were in list.

Categories