How to do this kind of search in ASP.net MVC? - c#

I have an ASP.NET MVC web application.
The SQL table has one column ProdNum and it contains data such as 4892-34-456-2311.
The user needs a form to search the database that includes this field.
The problem is that the user wants to have 4 separate fields in the UI razor view whereas each field should match with the 4 parts of data above between -.
For example ProdNum1, ProdNum2, ProdNum3 and ProdNum4 field should match with 4892, 34, 456, 2311.
Since the entire search form contains many fields including these 4 fields, the search logic is based on a predicate which is inherited from the PredicateBuilder class.
Something like this:
...other field to be filtered
if (!string.IsNullOrEmpty(ProdNum1) {
predicate = predicate.And(
t => t.ProdNum.toString().Split('-')[0].Contains(ProdNum1).ToList();
...other fields to be filtered
But the above code has run-time error:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities`
Does anybody know how to resolve this issue?

Thanks a lot for all responses, finally, I found an easy way to resolve it.
instead of rebuilding models and change the database tables, I just add extra space in the search strings to match the search criteria. since the data format always is: 4892-34-456-2311, so I use Startwith(PODNum1) to search first field, and use Contains("-" + PODNum2 + "-") to search second and third strings (replace PODNum1 to PODNum3), and use EndWith("-" + PODNum4) to search 4th string. This way, I don't need to change anything else, it is simple.
Again, thanks a lot for all responses, much appreciated.

If i understand this correct,you have one column which u want to act like 4 different column ? This isn't worth it...For that,you need to Split each rows column data,create a class to handle the splitted data and finally use a `List .Thats a useless workaround.I rather suggest u to use 4 columns instead.
But if you still want to go with your existing applied method,you first need to Split as i mentioned earlier.For that,here's an example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
string part1 = datareader(1).toString.Split("-")(0);///the 1st part of your column data
string part2 = datareader(1).toString.Split("-")(1);///the 2nd part of your column data
}
}
Now,as mentioned in the comments,you can rather a class to handle all the data.For example,let's call it mydata
public class mydata {
public string part1;
public string part2;
public string part3;
public string part4;
}
Now,within the While loop of the SqlDatareader,declare a new instance of this class and pass the values to it.An example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
Mydata alldata = new Mydata;
alldata.Part1 = datareader(1).toString.Split("-")(0);
alldata.Part2 = datareader(1).toString.Split("-")(1);
}
}
Create a list of the class in class-level
public class MyForm
{
List<MyData> storedData = new List<MyData>;
}
Within the while loop of the SqlDatareader,add this at the end :
storedData.Add(allData);
So finally, u have a list of all the splitted data..So write your filtering logic easily :)

As already mentioned in a comment, the error means that accessing data via index (see [0]) is not supported when translating your expression to SQL. Split('-') is also not supported hence you have to resort to the supported functions Substring() and IndexOf(startIndex).
You could do something like the following to first transform the string into 4 number strings ...
.Select(t => new {
t.ProdNum,
FirstNumber = t.ProdNum.Substring(0, t.ProdNum.IndexOf("-")),
Remainder = t.ProdNum.Substring(t.ProdNum.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
SecondNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
Remainder = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
t.SecondNumber,
ThirdNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
FourthNumber = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
... and then you could simply write something like
if (!string.IsNullOrEmpty(ProdNum3) {
predicate = predicate.And(
t => t.ThirdNumber.Contains(ProdNum3)

Related

Adding a sum to a list instead of a count

I'm using ASP.Net Core 3.1 to develop a web app. We need to return a list of values to a View. The list includes counts and sums of data. We have created a ViewModel to help. It looks like this:
public class ObjectCountViewModel
{
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Count")]
public decimal Count { get; set; }
}
We created a list in the Controller to return the values. It looks like this:
List<ObjectCountViewModel> objectCounts = new List<ObjectCountViewModel>();
Next we added values to the list like this:
int itemsToCount = objects.Where(e => e.ObjectItems.Where(ep => ep.ObjectItemType.Description.Contains("ItemToCount") && ep.ObjectItemSelctionType.Description.Contains("Taken")).Count()>0).Count();
objectCounts.Add(new ObjectCountViewModel() { Description = "Items Counted", Count = itemsToCount });
This code works great! But we also need to generate a sum. this will be used to count items with a decimal I can't get a sum to work. Here is one of the solutions I have tried:
decimal itemToSum = objects.Where(e => e.ObjectItems.Where(ep => ep.ObjectItemType.Description.Contains("ItemToSum") && ep.ObjectItemSelectionType.Description.Contains("Taken") && ep.ObjectValueAmount>0).Sum()>0).Sum();
objectCounts.Add(new ObjectCountViewModel() { Description = "Items Taken Sum", Count = itemToSum });
I have received a wide variety of errors. The current one is: 'IEnumerable' does not contain a definition for 'Sum' and the best extension method overload 'ParallelEnumerable.Sum(ParallelQuery)' requires a receiver type of 'ParallelQuery,decimal>.
What am I doing wrong? What should my query look like for a sum?
If you have a list of lists, where you want to count all lists, then use listsOfList.SelectMany(x=>x).Count().
If you have a list of decimals, where you want a sum of all decimals, then use listsOfDecimals.Sum().
If you have a list of lists of decimals, where you want a sum of all decimals, then use listsOfListOfDecimals.SelectMany(x=>x).Sum().
I found the answer thanks to Heretic Monkey and Intellisense. I had to create a new object with the value I'm trying to sum and then filter to only select from ones that met my criteria. Then, I separated the Select statement from the Where Clause as Heretic Monkey said. Intellisense suggested I put (decimal) in front of the whole thing, and it worked! Here is my final code for this problem.
decimal itemToSum = (decimal)Objects.Where(ep => ep.RelatedObjectType.Description.Contains("Description") && ep.DifferentRelatedObjectType.Description.Contains("Description")).Select(ep => ep.itemToSum).Sum();

C# LINQ How to get MAX of datatable column<string> WHERE value LIKE 'N01%'

I need help, how do I get MAX datatable column value where value LIKE 'N01%'
Basically, if I convert this to SQL:
SELECT MAX(user) FROM tblUser WHERE user LIKE 'N01%'
Thank you.
You can simply do this:
string[] data = {"hello", "N01jaja", "N01ll"};
var userWithN1 = data.Where(we => we.StartsWith("N01")).Max();
StartsWith checks if the element starts with a certain string.
If there's a class then need to implement IComparable.
Sample code:
public class TestClass : IComparable<string>
{
public string Value { get; private set; }
public int CompareTo(string other) { return Value.CompareTo(other); }
}
var result = foo.tblUser.Where(u => u.user.StartsWith("N01")).Max(u => u.user));
Simply use a where statement to start your filter, use a StartsWith to emulate SQL's xx% pattern. Then use Max on a particular column. Though make sure User is something that will actually have a Max value.
In LINQ, I always find it helpful to break the problem down. Here in this case, you have a list of items, you want to narrow down that list with a WHERE clause and return the MAX of the remaining items.
Start
var myItems = db.GetMyList();
WHERE with LIKE
Assuming User is a string variable
myItems = myItems.Where(x=>x.User.StartsWith("N01"));
MAX
var maxItem = myItems.Max(x=>x.User);
All Together
var maxItem = db.GetMyList().Where(x=>x.User.StartsWith("N01")).Max(x=>x.User);
edit - Per comment below, since the search string was 'N01%', is should be starts with and not contains.

How to extract from a list based on user input

The code contains two methods.
The Main which prompts the user for input and prints a sublist based on said user input.
The Extract method passes query from user input and adds all indices to dbQueryList to be extracted from dbListing and printed as query.
How does one to add to a List based on user input?
The primary issue is the if statement which contains the condition of
i.Substring(0, query.Length) = query. This is meant to test the condition 'if part of the query exists in any index in dbListing, add elements to dbQueryList '.
I originally wrote this in Python and it worked perfectly fine. I'm learning C# and not sure how to change that if condition. I considered changing the code and use LINQ in the foreach loop but not entirely clear on how to implement that.
Looking forward to community feedback! :)
//**************************************************
// File Name: autocomplete.cs
// Version: 1.0
// Description: Create a method that functions like an autocomplete
// API and truncates search to 5 results.
// Last Modified: 12/19/2018
//**************************************************
using System;
using System.Collections.Generic;
namespace autocomplete
{
class Program
{
private static string[] database;
private static string input;
private static string query;
static void Main(string[] args)
{
// user input to pass as query
Console.Write("Enter your query: ");
string query = Console.ReadLine();
// dynamic list comprised of 'database' array
List<string> dbListing = new List<string>();
string[] database = new string[] { "abracadara", "al", "alice", "alicia", "allen", "alter","altercation", "bob", "element", "ello", "eve", "evening", "event", "eventually", "mallory" };
dbListing.AddRange(database);
// write results based on user query
Console.WriteLine("Your results: " + Extract(Program.query));
// keep console window open after displaying results
Console.ReadLine();
}
// extract method passing query, return dbQueryList as query
public static List<string> Extract(string query)
{
// empty list is initiated
List<string> dbQueryList = new List<string>();
// foreach assesses all strings in database in main
// then, appends all indices of list equal to given query
foreach (string i in database)
{
// compares query (from index 0 to length of) to all strings in database
if (i.Substring(0, query.Length) = query)
{
// add to list above based on query
dbQueryList.Add(i);
}
// if statement truncates dbQueryList to 5 results
if (dbQueryList.Capacity >= 5)
break;
}
return dbQueryList;
}
}
UPDATE: 1/3/2019 18:30
I made the following changes to the Extract(query) and it worked!
foreach (string i in database)
{
// compares query (from index 0 to length of) to all strings in database
if (i.StartsWith(query))
{
// add to list above based on query
dbQueryList.Add(i);
Console.WriteLine(i);
}
// if statement truncates dbQueryList to 5 results
if (dbQueryList.Capacity >= 5)
break;
}
return dbQueryList;
Very excited that I got this to work! Please let me know if there are any further feedback about how to improve and clean this code if necessary! Cheers, everyone!
The problem is you are using = instead of == in the if statement.
In C# = operator is for assignment so what you are doing is trying to assign query to the expression on the left side, which is not possible. Instead use == operator which is for comparison.
Also, there is a more suitable method - use i.StartsWith(query) to check if the string starts with the given query. The current solution would work as long as i is not shorter than query.Length, in which case it would throw an exception.
if (i.StartsWith(query))
{
...

Selecting Multiple Linq Fields Removing Column Names

I need to select multiple fields into an enumerator to run a foreach in my Razor web app.
In the controller, I have:
...
cols = (from b in a.RefTable select new {b.Col1,b.Col2,b.Col3}),
...
It returns the values correctly when I use:
#foreach(var col in #item.cols)
{
#col
}
However, the representation on the page is:
{ Col1 = col1Value, Col2 = col2Value, Col3 = col3Value }
Two things I want to do:
Only show the column values on the page without being comma separated (will be separated onto each line within a dataTable field), and then not to show any value if the "col2Value", for example, is blank.
Edit:
Created a new ViewModel to resolve this, e.g.:
public class ColViewModel
{
public string Col1 {get;set;}
...
}
And replaced the Linq with:
...
cols = from b in a.RefTable select new ColViewModel{Col1 = b.Col1, ...},
...
Then, using the example from #Sacrilege below, I'm able to get the strings in the format I need them in.
What you are seeing is the string representation of the object you asked the view to render. You'll need to render each value separately to get your desired output.
#foreach(var col in item.cols)
{
if (!string.IsNullOrEmpty(col.Col1))
{
<div>#col.Col1</div>
}
if (!string.IsNullOrEmpty(col.Col2))
{
<div>#col.Col2</div>
}
if (!string.IsNullOrEmpty(col.Col3))
{
<div>#col.Col3</div>
}
}
You didn't mention the types for each of those fields but I guessed from your comment about them being blank that they were strings. I also added the extra markup because you wanted them each to appear on a separate line and that was a straight forward and easily maintainable way to do so.
#col is an Anonymous Type. So you must access the properties by using #col.Col1 etc. Because this is not supported in razor views in combination with Anonymous Types, you must convert it to an ExpandoObject in order to access the properties.
Take a look here for an example Dynamic Anonymous type in Razor causes RuntimeBinderException

C# resuable library to treat a text file like a database table?

I would like to store values in a text file as comma or tab seperated values (it doesn't matter).
I am looking for a reusable library that can manipulate this data in this text file, as if it were a sql table.
I need select * from... and delete from where ID = ...... (ID will be the first column in the text file).
Is there some code plex project that does this kind of thing?
I do not need complex functionality like joining or relationships. I will just have 1 text file, which will become 1 database table.
SQLite
:)
Use LINQ to CSV.
http://www.codeproject.com/KB/linq/LINQtoCSV.aspx
http://www.thinqlinq.com/Post.aspx/Title/LINQ-to-CSV-using-DynamicObject.aspx
If its not CSV in that case
Let your file hold one record per line. Each record at runtime should be read into a Collection of type Record [assuming Record is custom class representing individual record]. You can do LINQ operations on the collection and write back the collection into file.
Use ODBC. There is a Microsoft Text Driver for csv-Files. I think this would be possible. I don't have tested if you can manipulate via ODBC, but you can test it easily.
For querying you can also use linq.
Have you looked at the FileHelpers library? It has the capability of reading and parsing a text file into CLR objects. Combining that with the power of something like LINQ to Objects, you have the functionality you need.
public class Item
{
public int ID { get; set; }
public string Type { get; set; }
public string Instance { get; set; }
}
class Program
{
static void Main(string[] args)
{
string[] lines = File.ReadAllLines("database.txt");
var list = lines
.Select(l =>
{
var split = l.Split(',');
return new Item
{
ID = int.Parse(split[0]),
Type = split[1],
Instance = split[2]
};
});
Item secondItem = list.Where(item => item.ID == 2).Single();
List<Item> newList = list.ToList<Item>();
newList.RemoveAll(item => item.ID == 2);
//override database.txt with new data from "newList"
}
}
What about data delete. LINQ for query, not for manipulation.
However, List provides a predicate-based RemoveAll that does what you
want:
newList.RemoveAll(item => item.ID == 2);
Also you can overview more advanced solution "LINQ to Text or CSV Files"
I would also suggest to use ODBC. This should not complicate the deployment, the whole configuration can be set in the connection-string so you do not need a DSN.
Together with a schema.ini file you can even set column names and data-types, check this KB article from MS.
sqllite or linq to text and csv :)

Categories