MVC empty query string parameter strange behaviour - c#

I'm trying to implement search by passing the keyword to controller action as a parameter as shown below:
public ActionResult Index(string query)
{
var contacts = _unitOfWork.Contacts.GetContacts(_user.Id, query);
var viewModel = contacts.Select(Mapper.Map<Contact, ContactViewModel>);
return View("Index", viewModel);
}
GetContacts function in the repository looks like the following:
public IEnumerable<Contact> GetContacts(int userId, string query = null)
{
var list = _context.Contacts
.Where(c => c.UserId == userId)
.OrderBy(c => c.FirstName)
.AsQueryable();
if (query != null)
list = list.Where(c => c.FirstName.ToLower().Contains(query.ToLower())
|| c.LastName.ToLower().Contains(query.ToLower()));
return list.ToList();
}
When I navigate to http://localhost:50139/contacts/index?query=, I get an empty list. Having stepped through the code it is apparent that the query parameter is converted to an empty string value.
To ensure the search works, I have the following tests and all of them pass:
GetContacts_SearchByFirstName_ShouldReturnFilteredList
GetContacts_SearchByLastName_ShouldReturnFilteredList
GetContacts_SearchWithCapitalLetters_ShouldReturnFilteredList
GetContacts_SearchWithNullQuery_ShouldReturnAllContacts
In particular, the following test runs the function with empty string, which also passes successfully.
[TestMethod]
public void GetContacts_SearchWithEmptyString_ShouldReturnAllContacts()
{
var contactList = new List<Contact>()
{
// Construct new contact with first and last name and associated user id.
new Contact("e", "b",_userId ),
new Contact("c", "b",_userId ),
new Contact("a", "b",_userId ),
new Contact("d", "b",_userId )
};
_mockContacts.SetSource(contactList);
var result = _repository.GetContacts(_userId, "");
result.Count().Should().Be(4);
}
I have 3 contacts in the database and I can see all of them when I don't pass the query parameter. I would appreciate if you could point out why the controller action returns an empty list.

When you pass an empty string to the query parameter, the condition
if(query!=null) fails and the line below that
list = list.Where(c => c.FirstName.ToLower().Contains(query.ToLower())
|| c.LastName.ToLower().Contains(query.ToLower()));
gets executed, which checks the database for an entry with empty string in the LastName. This condition never gets satisfied and hence your list gets overridden with an empty list.

change if (query != null) to if (!string.IsNullOrEmpty(query))

Related

How to retrieve firstordefault from a ToList in linq

I have the following table in sql
I am trying to select all records with a status of onboard, but however you can see thatfor user '43d658bc-15a7-4056-809a-5c0aad6a1d86' i have two onboard entries. How do i select the firstordefault entry if the user has more than one record?
so my expected outcome should be
this is what i have that gets all the records
public async Task<List<Model>> HandleAsync(Query query)
{
return (await _repository.GetProjectedListAsync(q=> q.Where(x => x.Status== "Onboard").Select( Project.Log_Model))).ToList();
}
internal static partial class Project
{
public static readonly Expression<Func<Log,Model>> Log_Model =
x => x == null ? null : new Model
{
Id = x.Id,
UserId = x.UserId,
Status = x.Status,
created=x.Created,
DepartmentId=x.DepartmentId
};
}
i tried the following
var test = (await _repository.GetProjectedListAsync(q=> q.Where(x => x.Status== "Onboard").Select( Project.Log_Model))).ToList();
var t= test.FirstOrDefault();
return t;
but i get an error "can not implicitly convert type model to system.collections.generic.list"
The following code example demonstrates how to use FirstOrDefault() by passing in a predicate. In the second call to the method, there is no element in the array that satisfies the condition. You can filter out an entry you are looking for directly in the FirstOrDefault().
If you don't want to have duplicates, you could also use Distinct.
string[] names = { "Hartono, Tommy", "Adams, Terry",
"Andersen, Henriette Thaulow",
"Hedlund, Magnus", "Ito, Shu" };
string firstLongName = names.FirstOrDefault(name => name.Length > 20);
Console.WriteLine("The first long name is '{0}'.", firstLongName);
string firstVeryLongName = names.FirstOrDefault(name => name.Length > 30);
Console.WriteLine(
"There is {0} name longer than 30 characters.",
string.IsNullOrEmpty(firstVeryLongName) ? "not a" : "a");
/*
This code produces the following output:
The first long name is 'Andersen, Henriette Thaulow'.
There is not a name longer than 30 characters.
*/

How can I append mutliple results from context to one list/variable LINQ C#

I'm working on small app for fetching products/articles, and I wrote a method that's getting articles by type. (types are contained in request arg).
What I'm trying to achieve is: append all results (from all if conditions if they are satisfied) to one main list which should be returned to customer..
When I'm debugging and checking query it says its returning type is IQueryable<Article> so basically my question is how can I append multiple IQueryables into one which should be returned to user..
This code below is not working because result is always empty..
I've tried also with var result = new List<Article>(); and later result.AddRange(query); and I've changed also return type to
return await result.AsQueryable().ToListAsync(); but obviously something breaks somewhere and I get an empty array at the end.
public async Task<IEnumerable<Article>> GetArticlesByType(ArticleObject request)
{
var result = new Article[] { }.AsQueryable();
IQueryable<ArticleDTO> query = null;
if (request.Food.HasValue && (bool)request.Food)
{
// Return type of query is IQueryable<Article>
query = _context.Articles.Where(x => x.Active == true && x.ArticleType == ArticleType.Food).Select(x => new Article
{
Id = x.Id,
ArticleName = x.ArticleName
});
// Here I just wanted if this condition is satisfied to add values to my result
result.AsQueryable().Union(query);
}
if (request.Drink.HasValue && (bool)request.Drink)
{
query = _context.Articles.Where(x => x.Active == true && x.ArticleType == ArticleType.Drink).Select(x => new Article
{
Id = x.Id,
ArticleName = x.ArticleName
});
// Again if there are any values in query add them to existing result values
result.AsQueryable().Union(query);
}
if (request.Candy.HasValue && (bool)request.Candy)
{
// When its candy I want also articles from food category
query = _context.Articles.Where(x => x.Active == true && x.ArticleType == ArticleType.Food || x.ArticleType == ArticleType.Candy).Select(x => new Article
{
Id = x.Id,
ArticleName = x.ArticleName
});
// Again if there are values in query add them to existing result
result.AsQueryable().Union(query);
}
//At the end return result and all the values in case all conditions were satisfied
return await result.ToListAsync();
}
Try with result.AsQueryable().Union(query.ToList());. This will fetch the object from database. So far query contains references to objects in database and not in your memory

How can l use Regex.Replace method within LINQ

I have a controller with following method getMail():
public JsonResult getMail(string id)
{
int Id = Convert.ToInt32(id);
string pattern = #"\bmem_id\b";
string replace = "100";
var result = (from a in db.tblmail_type
where a.Id == Id
select new MailModel
{
subject = a.Subject,
Content = Regex.Replace(a.Content, pattern, replace);
});
return Json(result, JsonRequestBehavior.AllowGet);
}
This method is used for getting mail content. Before getting content, I want to replace a "mem_id" to "100" in the mail content. Default content is given below:
Content = "You have successfully registered with Member ID: mem_id"
I have used Regex.Replace() method in the LINQ. But this code doesn't change the content. When I change this code to this form which is given below, it's work properly.
public JsonResult getMail(string id)
{
int Id = Convert.ToInt32(id);
var input = db.tblmail_type.Where(x=>x.Id==Id).FirstOrDefault().Content;
string pattern = #"\bmem_id\b";
string replace = "100";
string content = Regex.Replace(input, pattern, replace);
var result = (from a in db.tblmail_type
where a.Id == Id
select new MailModel
{
subject = a.Subject,
Content = content
});
return Json(result, JsonRequestBehavior.AllowGet);
}
Why does this happen? Can anyone specify the reason behind this weird problem? How can I replace the "Content" within the LINQ?
You can use any one of the following solutions,
Solution 1:
Unfortunatelly, you won't be able to send the regex processing logic directly to the database.
You'll need to get the Content from the database and then iterate over the list and apply regex.
This can be done by using AsEnumerable(). It breaks the query into two part.
First part is inside part( query before AsEnumerable()) is executed as Linq-to-SQL.
Second part is outside part( query after AsEnumerable()) is executed as Linq-to-Objects.
First part is executed on database and all data brought in to the client side.
Second part (here it is where, select) is performed on the client side.
So in short AsEnumerable() operator move query processing to client side.
var result = ( from a in db.tblmail_type.AsEnumerable()
where a.Id == Id
select new MailModel {
subject = a.Subject,
Content = Regex.Replace(a.Content, pattern, replace)
});
Solution 2:
var result = db.tblmail_type.Where(x => x.Id == Id).AsEnumerable().Select(x => new MailModel { Content = Regex.Replace(x.Content, pattern, replace) , subject = x.Subject}).ToList();
Solution 3:
var result = db.tblmail_type.Where(x => x.Id == Id).ToList();
result.ForEach(x => x.Content = Regex.Replace(x.Content, pattern, replace));

Passing parameter to LINQ query

I have a method like below:
public void GetUserIdByCode(string userCode)
{
var query = from u in db.Users
where u.Code == userCode // userCode = "LRAZAK"
select u.Id;
var userId = query.FirstOrDefault(); // userId = 0 :(
}
When I ran the code, I got the default value of 0 assigned to userId meaning the Id was not found.
However, if I changed the userCode with a string like below, I will get the value I want.
public void GetUserIdByCode(string userCode)
{
var query = from u in db.Users
where u.Code == "LRAZAK" // Hard-coded string into the query
select u.Id;
var userId = query.FirstOrDefault(); // userId = 123 Happy days!!
}
My question is why passing the parameter into the LINQ query does not work?
When I stepped into the code, I got the SQL statement like so:
// Does not work...
{SELECT "Extent1"."LOGONNO" AS "LOGONNO"FROM "DEBTORSLIVE"."DEBTORS_LOGONS" "Extent1"WHERE ("Extent1"."LOGONCODE" = :p__linq__0)}
The hard-coded LINQ query (the working one) gives an SQL statement as below:
// Working just fine
{SELECT "Extent1"."LOGONNO" AS "LOGONNO"FROM "DEBTORSLIVE"."DEBTORS_LOGONS" "Extent1"WHERE ('LRAZAK' = "Extent1"."LOGONCODE")}
What would be the solution?
As a work-around, I use Dynamic Linq.
The code below is working for me.
public void GetUserIdByCode(string userCode)
{
string clause = String.Format("Code=\"{0}\"", userCode);
var userId = db.Users
.Where(clause)
.Select(u => u.Id)
.FirstOrDefault();
}
The database query returns an object of User with Code and Id as properties. This is defined in one of my classes.
Here is syntax that will work to pass an argument to a LINQ query.
Not sure how many people will be searching this topic so many years later, but here's a code example that gets the job done:
string cuties = "777";
// string collection
IList<string> stringList = new List<string>() {
"eg. 1",
"ie LAMBDA",
"777"
};
var result = from s in stringList
where (s == cuties)
select s;
foreach (var str in result)
{
Console.WriteLine(str); // Output: "777"
}

How to pass the value of an Linq Query to a normal String without using List?

A very simple question, yet I couldn't find the answer.
Let's say we have this:
[HttpPost]
public ActionResult Comparison(int id)
{
string makeLst = new List<String>();
var makeQry = from m in db.Car
where m.ID == id
select m.Make;
makeLst = makeQry.AddRange(makeQry);
ViewBag.make = new List<String>(makeLst);
return View();
}
The "makeQry" Result View would be just one word (String). So I wanted to not use List for this, and just use String. Using ".ToString()" won't work since "makeQry.ToString()" would be the Query itself instead of the it's results. And I checked there is no method such as for instance makeQry.Result or something to get the result of Query.
Thank you.
Details: If you know for sure there will always be a matching value for ID then the below should do the job (not tested)
[HttpPost]
public ActionResult Comparison(int id)
{
ViewBag.make = db.Car.FirstOrDefault(x => x.ID == id).Make;
return View();
}
or if you prefer to keep the linq syntax how you are doing it
[HttpPost]
public ActionResult Comparison(int id)
{
ViewBag.make = (from m in db.Car
where m.ID == id
select m.Make).FirstOrDefault();
return View();
}
You can use First() or FirstOrDefault() to get the single result:
ViewBag.make = makeQry.FirstOrDefault();
(The difference is that First throws an exception if the collection is empty, whereas FirstOrDefault just returns a null value in that case.)
[HttpPost]
public ActionResult Comparison(int id)
{
var makeQry = from m in db.Car
where m.ID == id
select m.Make;
//As soon as the result is guaranteed to contain only one object
ViewBag.make = makeQry.SingleOrDefault();
return View();
}
It uses Enumerable.SingleOrDefault Method (IEnumerable), which returns the single element of the input sequence or null if there are no elements. It'll throw an exception, if the sequence contains more than one element.

Categories