I'm trying to build a sidebar search navigation filters of check boxes and radio buttons. I'm getting the values from a database. Something like the following, but with 12 filter categories total:
Color
[] red
[] green
[] blue
Size
[] small
[] medium
[] large
Shape
[] square
[] circle
[] triangle
It is working for me using something like the code below. But it seems really inefficient to make a database call for each of the sub-categories:
public ActionResult Index ()
{
SearchBarViewModel model = new SearchBarViewModel();
model.Color = GetValuesFromDb();
model.Size = GetValuesFromDb();
model.Shape = GetValuesFromDb();
return View(model)
}
I'm guessing there is a more efficient way to do this by making a single database query, returning a large dataset that contains all the category values and them split then into groups with linq? I'm just not sure how this would be done?
Database Schema*
SearchKey SearchValue
--------- -----------------
Id Name Id KeyId Value
--------- -----------------
1 Color 1 1 Red
2 Size 2 1 Green
3 Shape 3 1 Blue
4 2 Small
5 2 Medium
6 2 Large
Sql Query
SELECT sv.Id, sv.Value
FROM SearchKey sk
JOIN SearchValue sv ON sv.KeyId = sk.Id
WHERE sk.Name = #ValuePassedToSP
It might or might not be a little early in your development to be concerned about performance of db calls. If the menu values are not changing often or in different contexts, it can make more sense to have the menu structure stored in the database like you do. If the menu values are not changing often, it might be better to store them in a program code or settings file that is only loaded when your app is first loaded, or maybe at demand after that.
I think the linq in context you are looking for might go something like this, where the "GetALLSearchValuesFromDb()" method returns an IEnumerable generated by a SQL statement like you have already, only without the WHERE clause:
public ActionResult Index ()
{
SearchBarViewModel model = new SearchBarViewModel();
var searchvalues = GetALLSearchValuesFromDb();
model.Color = searchvalues.Where(sv => sv.Name == "Color");
model.Size = searchvalues.Where(sv => sv.Name == "Size");
model.Shape = searchvalues.Where(sv => sv.Name == "Shape");
return View(model)
}
Related
I have a web site that shows info about lectures that are available. Each lecture has a title, an associated speaker, and (potentially) multiple categories. The database schema looks something like this (warning: this is air-code, as I don't have the database in front of me)...
create table Lectures (
ID int not null identity(1,1) primary key,
Title varchar(max) not null default '',
SpeakerID int not null foreign key references Speakers(ID)
)
create table Categories (
ID int not null identity(1,1) primary key,
Name varchar(max) not null default ''
)
create table Lectures_Categories (
ID int not null identity(1,1) primary key,
LectureID int not null foreign key references Lectures(ID),
CategoryID int not null foreign key references Categories(ID)
)
When viewing details about a lecture, I would like to be able to recommend related lectures, but am not sure how to code this. My initial thought is that the following criteria would be used to calculate relevance (most important first)...
Common categories - ie the more categories shared by the two lectures, the more likely they are to be related
Similarity in title - ie the more words shared by the two lectures, the more likely they are to be related.
Same speaker
If two lectures were equally ranked according to the above criteria, I would like to rank newer ones above older ones.
Anyone any idea how I would go about coding this? I'm doing this in C#, using an Entity Framework model against an SQL Server database if any of that is relevant.
Let me sktech out the basic idea: Assuming all three criteria can be expressed in sql queries you should get weighed result sets which you then union together.
The first would simply be select ID, 10 as weight from lectures where ID <> ourLectureID and speakerID = ourSpeakerID
The second will be a join over Lectures and Topics with a lesser weight, maybe 4.
Let's ignore the problems with the 3rd query for now.
Now that we have a set result1 of IDs and weights we do a group & sum. My sql is rather rusty today, but I'm thinking of something like this: select max(ID), sum(weight) as ranking from result1 group by ID order by ranking.. Done!
Now I haven't touched SQL server in almost 20 years ;-) but I think it is not well suited for creating the 3rd query. And the db designer will only give you the funny look and tell you that querying the Title is bad bad bad; and 'why didn't you add a keywords table..??
If you don't want to da that, as I assume you can pull all Titles into your C# application and use its string/collections/LINQ abilities to filter out interesting words and create the third query with a third ranking; maybe only capitalized words with 4 letters or more..?
Update
Here is a tiny example of how you can find a best fitting line among a list of lines:
List<string> proverbs = new List<string>();
List<string> cleanverbs = new List<string>();
List<string> noverbs = new List<string>();
private void button1_Click(object sender, EventArgs e)
{
noverbs.AddRange(new[] { "A", "a", "by", "of", "all", "the", "The",
"it's", "it", "in", "on", "is", "not", "will", "has", "can", "under" });
proverbs = File.ReadLines("D:\\proverbs\\proverbs.txt").ToList();
cleanverbs = proverbs.Select(x => cleanedLine(x)).ToList();
listBox1.Items.AddRange(proverbs.ToArray());
listBox2.Items.AddRange(cleanverbs.ToArray());
}
string cleanedLine(string line)
{
var words = line.Split(' ');
return String.Join(" ", words.ToList().Except(noverbs) );
}
int countHits(string line, List<string> keys)
{
var words = line.Split(' ').ToList();
return keys.Count(x => words.Contains(x));
}
private void listBox2_SelectedIndexChanged(object sender, EventArgs e)
{
string line = listBox2.SelectedItem.ToString();
int max = 0;
foreach (string proverb in cleanverbs)
{
var keys = proverb.Split(' ').ToList();
int count = countHits(line, keys);
if (count > max && proverb != line)
{
max = count;
Text = proverb + " has " + max + " hits";
}
}
}
It makes use of two listboxes and a text file of proverbs. When loaded you can click on the second listbox and the window title will display the line with the most hits.
You will want to make a few changes:
pull your titles from your DB, including their keys
create a more extensive and expandable file with non-verbs
decide on mixed-case
create not one result but an ordered set of lines
maybe optimize a few things so you don't have to split the body of titles more than once
I am developing an MVC 5 Web Application - I have a screen where a user can tick checkboxes on a grid and this will save the data to the Database. What I need to implement now is the removal of the data if the user navigated back to the screen and unchecked one of the items and then continued.
So the code in my controller so far looks as below:
IEnumerable<string> splitSelectedCars = model.SelectedCars
.Split(',')
.Select(sValue => sValue.Trim());
if (cars.Count > 0)
{
IEnumerable<string> savedCarsInDb = cars.Select(c => c.Id).ToList();
//var merged = splitSelectedCars.Union(savedCarsInDb ,)
//puesdo code - for each value in merged call service layer to remove
}
I am not sure if using a union is the best approach here to find all the values that are in the splitSelected cars list from the model that are not in the savedCarsInDb list and if so what the IEqualityComparer should look like?
So an example list the first time would be 1,2,3,4 passed in to the model - split out and then saved to DB. If the User navigates back and deslects id 4 then splitSelected will have 1,2,3 and savedCarsInDb will still have 1,2,3,4 - so I need to find '4' and then call remove
LINQ can help you here, specificall the Except method:
var selected = model.SelectedCars.Split(',').Select(sValue => sValue.Trim());
var saved = cars.Select(c => c.Id).ToList();
var removed = saved.Except(selected);
Depending upon whether you wish casing to be sensitive or not you can pass in the appropriate string comparer:
var removed = saved.Except(selected, StringComparer.OrdinalIgnoreCase);
I have some code which I'm having problems which and hopefully somebody can assist me, basically I have a 'Player' class such as:
Player JanMccoy = new Player { playerFirstname = "Jan", playerSurname = "Mccoy", playerAge = 23,
playerCode = "MCC0001"};
I have about 10 players, all of which have a unique code to them self, basically this code is stored into a list box with the Name and Surname. How the data gets their isn't important, basically though there are 10 values in the listbox which look like "Jan Mccoy (MCC0001)"
Basically I want to now be able to get the age of the person in the class, I have an event for a button which when he gets the selected item from the listbox box I store into a string just the playerCode, which this code I need to be able to get the player age
I know this is SQL but I need something basically like:
SELECT * FROM MyClass WHERE playerCode = strPlayerCode
I however am not using SQL, I need something which can do that in C#
If I need to add anymore detail just ask, tried to explain as good as I can.
If you could point me into right direction also that be great also!
In c# there is Linq which works similar to SQL
For example:
SELECT * FROM MyClass WHERE playerCode = strPlayerCode
would be
var players = myListOfPlayers.Where(p => p.playerCode == strPlayerCode);
This will return a collection of all the players with that playercode
However, since you said the key is unique and you are only returning a single record FirstOrDefault will work fine without the need tor the where clause. like SELECT TOP 1 FROM ....
var player = myListOfPlayers.FirstOrDefault(p => p.playerCode == strPlayerCode);
Then I would try LINQ:
var player = players.Where(p => p.playerCode == "MCC001").FirstOrDefault();
I've read MANY different solutions for the separate functions of LINQ that, when put together would solve my issue. My problem is that I'm still trying to wrap my head about how to put LINQ statements together correctly. I can't seem to get the syntax right, or it comes up mish-mash of info and not quite what I want.
I apologize ahead of time if half of this seems like a duplicate. My question is more specific than just reading the file. I'd like it all to be in the same query.
To the point though..
I am reading in a text file with semi-colon separated columns of data.
An example would be:
US;Fort Worth;TX;Tarrant;76101
US;Fort Worth;TX;Tarrant;76103
US;Fort Worth;TX;Tarrant;76105
US;Burleson;TX;Tarrant;76097
US;Newark;TX;Tarrant;76071
US;Fort Worth;TX;Tarrant;76103
US;Fort Worth;TX;Tarrant;76105
Here is what I have so far:
var items = (from c in (from line in File.ReadAllLines(myFile)
let columns = line.Split(';')
where columns[0] == "US"
select new
{
City = columns[1].Trim(),
State = columns[2].Trim(),
County = columns[3].Trim(),
ZipCode = columns[4].Trim()
})
select c);
That works fine for reading the file. But my issue after that is I don't want the raw data. I want a summary.
Specifically I need the count of the number of occurrences of the City,State combination, and the count of how many times the ZIP code appears.
I'm eventually going to make a tree view out of it.
My goal is to have it laid out somewhat like this:
- Fort Worth,TX (5)
- 76101 (1)
- 76103 (2)
- 76105 (2)
- Burleson,TX (1)
- 76097 (1)
- Newark,TX (1)
- 76071 (1)
I can do the tree thing late because there is other processing to do.
So my question is: How do I combine the counting of the specific values in the query itself? I know of the GroupBy functions and I've seen Aggregates, but I can't get them to work correctly. How do I go about wrapping all of these functions into one query?
EDIT: I think I asked my question the wrong way. I don't mean that I HAVE to do it all in one query... I'm asking IS THERE a clear, concise, and efficient way to do this with LINQ in one query? If not I'll just go back to looping through.
If I can be pointed in the right direction it would be a huge help.
If someone has an easier idea in mind to do all this, please let me know.
I just wanted to avoid iterating through a huge array of values and using Regex.Split on every line.
Let me know if I need to clarify.
Thanks!
*EDIT 6/15***
I figured it out. Thanks to those who answered it helped out, but was not quite what I needed. As a side note I ended up changing it all up anyways. LINQ was actually slower than doing it other ways that I won't go into as it's not relevent. As to those who made multiple comments on "It's silly to have it in one query", that's the decision of the designer. All "Best Practices" don't work in all places. They are guidelines. Believe me, I do want to keep my code clear and understandable but I also had a very specific reasoning for doing it the way I did.
I do appreciate the help and direction.
Below is the prototype that I used but later abandoned.
/* Inner LINQ query Reads the Text File and gets all the Locations.
* The outer query summarizes this by getting the sum of the Zips
* and orders by City/State then ZIP */
var items = from Location in(
//Inner Query Start
(from line in File.ReadAllLines(FilePath)
let columns = line.Split(';')
where columns[0] == "US" & !string.IsNullOrEmpty(columns[4])
select new
{
City = (FM.DecodeSLIC(columns[1].Trim()) + " " + columns[2].Trim()),
County = columns[3].Trim(),
ZipCode = columns[4].Trim()
}
))
//Inner Query End
orderby Location.City, Location.ZipCode
group Location by new { Location.City, Location.ZipCode , Location.County} into grp
select new
{
City = grp.Key.City,
County = grp.Key.County,
ZipCode = grp.Key.ZipCode,
ZipCount = grp.Count()
};
The downside of using File.ReadAllLines is that you have to pull the entire file into memory before operating over it. Also, using Columns[] is a bit clunky. You might want to consider my article describing using DynamicObject and streaming the file as an alternative implemetnation. The grouping/counting operation is secondary to that discussion.
var items = (from c in
(from line in File.ReadAllLines(myFile)
let columns = line.Split(';')
where columns[0] == "US"
select new
{
City = columns[1].Trim(),
State = columns[2].Trim(),
County = columns[3].Trim(),
ZipCode = columns[4].Trim()
})
select c);
foreach (var i in items.GroupBy(an => an.City + "," + an.State))
{
Console.WriteLine("{0} ({1})",i.Key, i.Count());
foreach (var j in i.GroupBy(an => an.ZipCode))
{
Console.WriteLine(" - {0} ({1})", j.Key, j.Count());
}
}
There is no point getting everything into one query. It's better to split the queries so that it would be meaningful. Try this to your results
var grouped = items.GroupBy(a => new { a.City, a.State, a.ZipCode }).Select(a => new { City = a.Key.City, State = a.Key.State, ZipCode = a.Key.ZipCode, ZipCount = a.Count()}).ToList();
Result screen shot
EDIT
Here is the one big long query which gives the same output
var itemsGrouped = File.ReadAllLines(myFile).Select(a => a.Split(';')).Where(a => a[0] == "US").Select(a => new { City = a[1].Trim(), State = a[2].Trim(), County = a[3].Trim(), ZipCode = a[4].Trim() }).GroupBy(a => new { a.City, a.State, a.ZipCode }).Select(a => new { City = a.Key.City, State = a.Key.State, ZipCode = a.Key.ZipCode, ZipCount = a.Count() }).ToList();
I have the following three tables, and need to bring in information from two dissimilar tables.
Table baTable has fields OrderNumber and Position.
Table accessTable has fields OrderNumber and ProcessSequence (among others)
Table historyTable has fields OrderNumber and Time (among others).
.
var progress = from ba in baTable
from ac in accessTable
where ac.OrderNumber == ba.OrderNumber
select new {
Position = ba.Position.ToString(),
Time = "",
Seq = ac.ProcessSequence.ToString()
};
progress = progress.Concat(from ba in baTable
from hs in historyTable
where hs.OrderNumber == ba.OrderNumber
select new {
Position = ba.Position.ToString(),
Time = String.Format("{0:hh:mm:ss}", hs.Time),
Seq = ""
});
int searchRecs = progress.Count();
The query compiles successfully, but when the SQL executes during the call to Count(), I get an error
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
Clearly the two lists each have three items, one of which is a constant. Other help boards suggested that the Visual Studio 2010 C# compiler was optimizing out the constants, and I have experimented with alternatives to the constants.
The most surprising thing is that, if the Time= entry within the select new {...} is commented out in both of the sub-queries, no error occurs when the SQL executes.
I actually think the problem is that Sql won't recognize your String.Format(..) method.
Change your second query to:
progress = progress.Concat(from ba in baTable
from hs in historyTable
where hs.OrderNumber == ba.OrderNumber
select new {
Position = ba.Position.ToString(),
Time = hs.Time.ToString(),
Seq = ""
});
After that you could always loop trough the progress and format the Time to your needs.