LINQ Matching on Multiple Columns - c#

I am trying to write a LINQ query in an MVC application that allows users to search multiple fields at the same time for resident information.
Here is an example of what I am trying to accomplish.
The user has 3 textboxes to search from:
#using (Html.BeginForm("Index", "Voters", FormMethod.Get))
{
<p>
Last Name: #Html.TextBox("voterSearchModel.LastNameSearch", !String.IsNullOrEmpty(voterSearchModel.FirstNameSearch) ? voterSearchModel.FirstNameSearch : "")
First Name: #Html.TextBox("voterSearchModel.FirstNameSearch", !String.IsNullOrEmpty(voterSearchModel.LastNameSearch) ? voterSearchModel.LastNameSearch : "")
Address: #Html.TextBox("voterSearchModel.Address", !String.IsNullOrEmpty(voterSearchModel.LastNameSearch) ? voterSearchModel.AddressSearch : "")
</p>
<input type="submit" value="Search" />
}
Lets say we have the following in our table:
---------------------------------------------------------------
|FirstName | LastName | Address |
|Bob | Doe | 123 Sesame St. |
|Joe | Doe | 123 Sesame St. |
|Cookie | Monster | 111 FooBar Lane|
|Bob | Magoo | 321 Street Ave.|
|Mark | McAllister | 213 Duckie Ave.|
|Joe | Doe | 18 Sunset Blvd |
My LINQ query right now is as such:
voters = voters.Where(voter => (voterSearchModel.LastNameSearch != null && voter.Last_Name.Contains(voterSearchModel.LastNameSearch))
|| (voterSearchModel.FirstNameSearch != null && voter.First_Name.Contains(voterSearchModel.FirstNameSearch)
|| (voterSearchModel.AddressSearch != null && voter.StreetNameComplete.Contains(voterSearchModel.AddressSearch)));
Say the user searches for First Name: "Joe" Last Name: "Doe" to find all of the Joe Does in the city. The query, right now will return ALL the "Joe"s and ALL of the "Doe"s.
What would I need to do in order have it return only the "Joe Does"?

Instead of doing an overall or search ||, it sounds like you want an and search &&. But your test for empty search strings is wrong - you want to say a record matches if (search string is null) OR (search string is a match).
voters = voters.Where(voter => (voterSearchModel.LastNameSearch == null || voter.Last_Name.Contains(voterSearchModel.LastNameSearch)) &&
(voterSearchModel.FirstNameSearch == null || voter.First_Name.Contains(voterSearchModel.FirstNameSearch)) &&
(voterSearchModel.AddressSearch == null || voter.StreetNameComplete.Contains(voterSearchModel.AddressSearch)));

It looks like you are OR-ing your search terms together when you really want to AND them.
Try building your query by conditionally applying separate Where clauses for each criteria:
if (voterSearchModel.FirstNameSearch != null)
{
voters = voters.Where(voter.First_Name.Contains(voterSearchModel.FirstNameSearch));
}
if (voterSearchModel.LastNameSearch != null)
{
voters = voters.Where(voter.Last_Name.Contains(voterSearchModel.LastNameSearch));
}
if (voterSearchModel.AddressSearch != null)
{
voters = voters.Where(voter.StreetNameComplete.Contains(voterSearchModel.AddressSearch));
}
This should give you the results you want while also making the code a little easier to reason about.

Related

Getting Multiple Lists and displaying them to the View

I have a system where I enter in other, employee, and dealer information as such (there is more fields but trying to keep it simple).
+----+---------+--------------+------------+-------+
| ID | itemId | memberTypeId | MemberType | price |
+----+---------+--------------+------------+-------+
| 1 | 202 | 2 | employee | 2.00 |
| 2 | 202 | 3 | dealer | 3.00 |
| 3 | 101 | 1 | other | 4.00 |
+----+---------+--------------+------------+-------+
I’m trying to make the Update functionality on my site. When I make a DB call based on the ItemId to get everything.
Here is such code from helper class:
public class Item {
public static List<ItemOption> GetItemOpttions(long itemID)
{
using (DBContext context = new DBContext())
{
List<ItemOptionDTO> ItemOptionsDto = (
from I in context.ItemOptions
where I.ItemID == itemID
select new ItemOptionDTO
{
ItemID = I.ItemID,
MemberTypeId = I.MemberTypeId
MemberType = I.MemberType,
Price = I.Price,
}).ToList();
return ItemOptionsDto;
}
}
}
So, when I get the data and store it in a list. It’s either one row of data or two rows based on how it was saved. The option “Other” will always be saved into the DB as one row of data as where “employee and dealer” will be saved as two separate row sharing the same ItemId. When called the number of rows will vary based on each item created at an earlier date/time.
I am trying to get this info back to the controller and send it to the View showing the correct values that might get updated. Here is what I have in the controller. I'm not sure if this is the correct way of doing this or if there is a better way.
Also to add that the if the item was saved as an employee and dealer it will leave the other list empty/null. I don't think I can do this because it will cause a null exception error
This is what I have so far in the controller:
public ActionResult UpdateItem(long itemID) {
List<ItemOptionDTO> itemOptionDto = Item.GetItemOpttions(itemID);
var other = itemOptionDto.Where(x => x.MemberTypeId == 1).FirstOrDefault();
var employee = itemOptionDto.Where(x => x.MemberTypeId == 2).FirstOrDefault();
var dealer = itemOptionDto.Where(x => x.MemberTypeId == 3).FirstOrDefault();
UpdateItemViewModel viewModel = new UpdateItemViewModel(itemID, employee, dealer, option)
{
ItemId = option.ItemId,
MemberType = option.MemberType,
Price = other.price
ItemId = employee.ItemId,
MemberType = employee.MemberType,
Price = employee.price
ItemId = dealer.ItemId,
MemberType = dealer.MemberType,
Price = dealer.price
};
return View(viewModel);
}
Let me know if there is anything else I can provide.

Linq GroupBy throws ArgumentOutOfRangeException

I have these two tables
Animals Activities
+----+-------+ +----+------------+----------+------------+
| Id | Name | | Id | Activity | FkAnimal | Date |
+----+-------+ +----+------------+----------+------------+
| 1 | Cats | | 1 | Ball | 2 | 2015-05-21 |
+----+-------+ +----+------------+----------+------------+
| 2 | Dogs | | 2 | Pet | 2 | 2015-06-07 |
+----+-------+ +----+------------+----------+------------+
| 3 | Birds | | 3 | Running | 1 | 2014-11-03 |
+----+-------+ +----+------------+----------+------------+
| 4 | Kill a fly | 1 | 2014-08-05 |
+----+------------+----------+------------+
| 5 | Kill a fly | 3 | 2014-08-05 |
+----+------------+----------+------------+
What I want is the result of this query
SELECT Animals.Name, Animals.Id, Activities.Data
FROM Activities
INNER JOIN Animals ON Animals.Id = Activities.Id
GROUP BY Animals.Name, Animals.Data
In LINQ from the Entity Framework
Here's my attempt:
//My repository is of type IRepository<Activities>
var list = Repository.GetAll().GroupBy(a => a.Animals).Select((grouping,i) => new {
name = grouping.Key.Name,
id = grouping.Key.Id,
data = grouping.ElementAt(i).Data
}).ToList();
Unfortunately the ToList() method generate ArgumentOutOfRangeException, and if I debug the lambda it shows that i goes out of range
The i in .Select((grouping,i) => is the index of the group. In your example, .GroupBy(a => a.Animals) will return an IGrouping which is, essentially, just an IEnumerable with a Key property. The result from .GroupBy(a => a.Animals) will, loosely, look something like this (not sure exactly what your DbContext looks like):
{[
{
Key: Dogs
GetEnumerator(): [
{
Id: 1
Activity: Ball
Date: 2015-05-21
},
{
Id: 2
Activity: Pet
Date: 2015-06-07
}
]
},
{
Key: Cats
GetEnumerator(): [
{
Id: 3
Activity: Running
Date: 2014-11-03
},
{
Id: 4
Activity: Kill a fly
Date: 2014-08-05
}
]
},
{
Key: Birds
GetEnumerator(): [
{
Id: 5
Activity: Kill a fly
Date: 2014-08-05
}
]
}
]}
The Select method is iterating over the groups, not the elements in the group. So the i in .Select((grouping,i) =>, in this case, refers to the index of the group (there are three groups) not an element in a group. Within your select you are calling data = grouping.ElementAt(i).Data, grouping in this case is an IGropuing which is an IEnumerable so ElementAt(i) is asking for the ith element in whichever group is currently being evaluated. By the time you get the third group i will be 2 but there is only one element in the group, hence the exception; at least in this example, your groups may come back in a different order but the principle is the same.
You probably want something like this:
var list =
Repository
.GetAll()
.GroupBy(a => a.Animals)
.Select(grouping => new {
name = grouping.Key.Name,
id = grouping.Key.Id,
data = grouping.Select(x => x)
}).ToList();
does this work...
var res= from act in Repository.GetAll()
let anm=act.Animals.Single(a=>a.Id=act.FkAnimal)
select new {
anm.Id, anm.Name, act.Activity
};

Linq expression checkin if one user is following another

I have a table that stores followers and followees.These are based on the guids they have.
before i want to make this relation i want to check and see if the person is already following the person
EDIT
bool userExist = _databaseEntities.Users.Count(e => e.UserName == followerName) > 0;
if(userExist)
{
var user1 = _databaseEntities.Users.FirstOrDefault(y => (y.UserName == username));
var user2 = _databaseEntities.Users.FirstOrDefault(z => (z.UserName == followerName));
So i get two users and check if they are there and then check if their userIds are in the table of followers forming a relationship.
so I have this _db.Followers.Count(c => (c.UserId == user1.UserId && c.FollowerId == user2.UserId) < 0 );
but it always comes out as 0 even if the relation is there
How can i do this right?
If i understand correctly, you have a table like this:
| UserId | FollowerId |
|----------|--------------|
| Homer | Marge |
| Homer | Lisa |
| Marge | Homer |
| Marge | Bart |
| Bart | |
| Lisa | Bart |
|----------|--------------|
And the related User class should contain at least a string property for the UserId and a Collection of Users (those who the user is following)
So you can have a method inside User like this:
public bool CanFollow(User followee)
{
return !Followee.Any(x => x.UserId == followee.UserId);
}
It's easier and more logical and you simply use it like that
User homer = _db.Followers.Where(x => x.UserId == "Homer").FirstOrDefault();
User marge = _db.Followers.Where(x => x.UserId == "Marge").FirstOrDefault();
// can Homer follow Marge?
homer.CanFollow(marge);

Return a record despite a field being empty

(I'm new to Entity Framework, so apologies if this is an easy question!)
In my DB table, I have a column titled [SiteURL] and another [Keywords]
I have records in there, but some do not have any entries in the Keywords field.
I want to let my users filter the records based on these 2 columns.
My code is here:
Publishers = db.Publishers.Where(p =>
p.isActive == true
&& p.SiteURL.ToLower().Contains(((txtFilterBy_SiteURL.Value.Length > 0) && (txtFilterBy_SiteURL.Value.ToLower() == "site url")) ? p.SiteURL.ToLower() : txtFilterBy_SiteURL.Value.ToLower())
&& p.Keywords.ToLower().Contains(((txtFilterBy_KeyWord.Value.Length > 0) && (txtFilterBy_KeyWord.Value.ToLower() == "keyword")) ? p.Keywords.ToLower() : txtFilterBy_KeyWord.Value.ToLower())
).ToList();
I'm trying to say:
IF txtFilterBy_SiteURL.Value isn't empty and doesn't equal "site url", Then Search [SiteURL] for whatever has been entered into txtFilterBy_SiteURL.Value
AND
IF txtFilterBy_KeyWord.Value isn't empty and doesn't equal "keyword", Then include [Keywords] in the criteria list for whatever has been entered into txtFilterBy_KeyWord.Value.
ID | SiteURL | Keywords |
1 | Rod.org | Travel |
2 | jane.com | |
3 | fred.com | motoring |
So, if the user enters ".com" into txtFilterBy_SiteURL, I want to return
ID | SiteURL | Keywords |
2 | jane.com | |
3 | fred.com | motoring |
If the user enters ".com" into txtFilterBy_SiteURL and "mot" into the txtFilterBy_KeyWord textbox, I want:
ID | SiteURL | Keywords |
3 | fred.com | motoring |
You can try with a bit of boolean logic where you fetch your publishers from database. The below is in LINQ to SQL but it should work as well. Apologize for the way the code below looks, couldn't get the long one-liners to display ok in here...
Publishers = (from p in db.Publishers
where p.isActive == true &&
p.SiteURL.ToLower().Contains(((txtFilterBy_SiteURL.Value.Length > 0) &&
(txtFilterBy_SiteURL.Value.ToLower() == "site url")) ?
p.SiteURL.ToLower() : txtFilterBy_SiteURL.Value.ToLower()) &&
(p.Keywords == null || p.Keywords == "" ||
p.Keywords.ToLower().Contains(((txtFilterBy_KeyWord.Value.Length > 0)
&&
(txtFilterBy_KeyWord.Value.ToLower() == "keyword")) ?
p.Keywords.ToLower() : txtFilterBy_KeyWord.Value.ToLower()))
select p).ToList();
Publishers = db.Publishers.Where(p =>
p.Keywords.ToLower().Contains(p.Keywords.ToLower()) || String.IsNullOrEmpty(p.Keywords))
).ToList();
if KeyWords is a string.
this line always true for not null Keywords p.Keywords.ToLower().Contains(p.Keywords.ToLower())
if you need to filter check null before access Keywords property and then check contains
Publishers = db.Publishers.Where(p => String.IsNullOrEmpty(p.Keywords) &&
p.Keywords.ToLower().Contains("stringToSearch")
).ToList();
Edit
// get all data
Publishers = db.Publishers.ToList();
// filter by site url
if(!String.IsNullOrEmpty(txtFilterBy_SiteURL.Text))
{
Publishers = Publishers.Where(p => String.IsNullOrEmpty(p.SiteURL) &&
p.SiteURL.ToLower().Contains(txtFilterBy_SiteURL.Text)
).ToList();
}
// filter by keyword
if(!String.IsNullOrEmpty(txtFilterBy_KeyWord.Text))
{
Publishers = Publishers.Where(p => String.IsNullOrEmpty(p.Keywords) &&
p.Keywords.ToLower().Contains(txtFilterBy_KeyWord.Text)
).ToList();
}

Search on all fields of an entity

I'm trying to implement an "omnibox"-type search over a customer database where a single query should attempt to match any properties of a customer.
Here's some sample data to illustrate what I'm trying to achieve:
FirstName | LastName | PhoneNumber | ZipCode | ...
--------------------------------------------------
Mary | Jane | 12345 | 98765 | ...
Jane | Fonda | 54321 | 66666 | ...
Billy | Kid | 23455 | 12345 | ...
If the query was "Jane", I'd expect row #1 to be returned as well as row #2.
A query for 12345 would yield rows #1 and #3.
Right now, my code looks pretty much like this:
IEnumerable<Customer> searchResult = context.Customer.Where(
c => c.FirstName == query ||
c.LastName == query ||
c.PhoneNumber == query ||
c.ZipCode == query
// and so forth. Fugly, huh?
);
This obviously works. It smells like really bad practice to me, though, since any change in the Entity (removal of properties, introduction of new properties) would break stuff.
So: is there some LINQ-foo that will search across all properties of whatever Entity I throw at it?
first find all properties within Customer class with same type as query:
var stringProperties = typeof(Customer).GetProperties().Where(prop =>
prop.PropertyType == query.GetType());
then find all customers from context that has at least one property with value equal to query:
context.Customer.Where(customer =>
stringProperties.Any(prop =>
prop.GetValue(customer, null) == query));

Categories