Azure functions with Cosmos DB with wild search - c#

I want a 'wild' search where I get FirstName OR Surname. so it would be possible to send a FirstName of "" and Surname of "Smith" so I get everyone with the surname "Smith".
I feel this is partly correct, but having a sql query in the header has a slightly off smell. Is there a better way to do this?
[FunctionName($"People_Get")]
public IActionResult GetPeople(
[HttpTrigger( AuthorizationLevel.Anonymous, "get", Route = "Review/{firstName}/{surname}")]
PeopleRequest request,
HttpRequest req,
[CosmosDB( "CosmosDB", "PeopleContainer", Connection = "CosmosDbConnectionString",
SqlQuery = #"SELECT c.id ,
c.FirstName ,
c.Surname
FROM c.FirstName
WHERE c.FirstName == firstName OR c.FirstName == surname")]
IEnumerable<Models.People> peopleReviews,
ILogger log)
{
return new OkObjectResult(peopleReviews);
}

Azure functions with Cosmos DB with wild search
The code is correct, but there are a few changes to be done.
The SqlQuery property in the CosmosDB attribute should use string interpolation to include the values of the firstName and surname parameters in the query, rather than hardcoding them.
Use the 'LIKE' operator in the WHERE clause instead of '==' to match partial strings.
And also, you can add a condition to check if either firstName or surname is null or empty, and return an error response in that case.
The below is the updated code:
public static class Function1
{
[FunctionName("People_Get")]
public IActionResult GetPeople([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Review/{firstName}/{surname}")]
PeopleRequest request, HttpRequest req, [CosmosDB("CosmosDB", "PeopleContainer", Connection = "CosmosDbConnectionString")]
IEnumerable<Models.People> peopleReviews, ILogger log)
{
if (string.IsNullOrEmpty(request.firstName) && string.IsNullOrEmpty(request.surname))
{
return new BadRequestObjectResult("Please provide either a first name or a surname.");
}
var query = from p in peopleReviews
where (string.IsNullOrEmpty(request.firstName) || p.FirstName.Contains(request.firstName))
&& (string.IsNullOrEmpty(request.surname) || p.Surname.Contains(request.surname))
select p;
return new OkObjectResult(query);
}
}

Related

how to concatenate the string in AsQueryable to search by the name, by null checking

I want to search by a name, which is the input from the UI, however, I have the separate fields for the name in the database, which are firstname, lastname and middle initial.
So far what i have tried:
var filtered = (from c in _dbContext.Address
join i in _dbContext.Customers.Where(a => !request.AccountNumbers.Any() ||
request.AccountNumbers.Contains(a.AccountNum)) on c.FkParent equals i.Id
select new ContactRequestListModel
{
Id = c.ID,
FirstName = c.FirstName,
LastName = c.LastName,
MiddleInitial = c.MiddleInitial
});
var query = filtered.AsQueryable();
if (request.Names.Any())
query = query.Where(x => request.Names.Contains(x.FullName));
ContactListRequestModel
public class ContactRequestListModel : ContactRequestModel
{
public int Id { get; set; }
public string FullName => string.Join(" ", new string[] { FirstName, MiddleInitial, LastName }.Where(s => !string.IsNullOrEmpty(s)));
}
The middle initial could be null, so which could add the double spaces while concatenating the string, so I have tried to omit the spaces in the name. But this query has given me the error.
The LINQ expression ... could not be translated. Either rewrite the
query in a form that can be translated, or switch to client evaluation
explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable',
'ToList', or 'ToListAsync'.
I didn't want to convert the query to ToList() and filter by name.
But is there any way to query by null checking the MiddleInitial and query as AsQueryable. What is the workaround in this case?
Did you try to use + operator:
public string FullName {
get {
return FirstName + MiddleInitial ?? "" + LastName ;
}
}

Linq query and multiple where clauses with OR only first condition gets evaluated

I give a user flexibility by providing either username or user id, which are different database fields, both strings. Here is my Linq query below:
var usr = ctx.Users.Where(a => (a.Username.Equals(id) || a.UserID.Equals(id))).ToList();
The thing is if I call it with username: "johndoe", I get a record back, but if I user UserID: "12345" then I do not get any records back even though there is a user "johndoe" with id "12345" in the database.
Also if I change it to:
var usr = ctx.Users.Where(a => a.UserID.Equals(id)).ToList();
It works fine with UserID; "12345". So it seems that only first condition gets evaluated. Can't figure out what am I doing wrong...
Just to make things clear: I want to check both fields for the given id value and return the record where either field matches the id.
The final result I want to get is to have a record(s) returned in my usr variable regardless of which field, Username or UserID matches the input id.
Your linq query looks ok to me
public class Users
{
public string Username {get;set;}
public string Userid {get;set;}
}
void Main()
{
var users = new List<Users>{new Users {Username="johndoe",Userid="123"},
new Users {Username="stevejobs",Userid="456"}
};
var filter = users.Where(a => (a.Username.Equals("123") || a.Userid.Equals("123"))).ToList();
filter.Dump();
var filter2 = users.Where(a => (a.Username.Equals("456") || a.Userid.Equals("456"))).ToList();
filter2.Dump();
}

LINQ to Entities - How to retrieve a data and use it as a variable in the whole project

I have 1 WinForm and 2 tables:
Form1 contains TextBox1
Employee contains ID, LastName, FirstName, BirthDate.
Tasks contains ID, TaskName
I need to input LastName and FirtName in TextBox1 to retrieve the related "TaskName" if exists,
then make the retrieved data usable from any location in the project
I need to know where to put the TaskName variable?
and how can I modify the next code to reach that purpose?
using (LINQtoEntitiesEntities MyEntities = new LINQtoEntitiesEntities())
{
ObjectQuery<Employee> Employee = MyEntities.Employee;
var query = (from p in Employee
where p.FirstName == TextBox1.Text.Trim()
select p.LasttName, p.FirstName);
}
If LastName and FirstName are being input (in that order) to TextBox1 then, assuming they are separated by a space or spaces, you can split them up as follows:
string[] items = TextBox1.Text.Trim().Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries);
string lastName = items[0];
string firstName = items[1];
The second argument to the Split method deals with the possibility that there might be multiple spaces separating the last and first names.
If the ID column is used to link the Employee and Tasks tables, you should then be able to retrieve the related TaskName and place it in a global variable as follows:
// create a class to contain global variables if you don't already have one
static class Global
{
public static string Task {get; set;}
}
// within some method in some other class
// insert above code to get last and first names
using (LINQtoEntitiesEntities MyEntities = new LINQtoEntitiesEntities())
{
ObjectQuery<Employee> Employee = MyEntities.Employee;
ObjectQuery<Tasks> Tasks = MyEntities.Tasks;
Global.Task = (from e in Employee where e.LastName == lastName && e.FirstName == firstName join t in Tasks on e.ID equals t.ID select t.TaskName).FirstOrDefault();
}
// retrieve global variable from elsewhere:
if (Global.Task != null)
{
// do something with Global.Task
}

Getting Specific Columns in Entity Framework

I would like to get the list of users from the database, but I want only 5 columns instead of all (it has about 35 columns). When I wrote like the following, it shows me no error at the compile time but the error at the runtime.
bksb_Users is the table name in my database as well as object name in the Entity Model.
public List<bksb_Users> SearchStudents(string reference, string firstname, string lastname)
{
return (from u in context.bksb_Users
where u.userName.Contains(reference)
&& u.FirstName.Contains(firstname)
&& u.LastName.Contains(lastname)
orderby u.FirstName, u.LastName
select new bksb_Users
{
user_id = u.user_id,
userName = u.userName,
FirstName = u.FirstName,
LastName = u.LastName,
DOB = u.DOB
}).Take(100).ToList<bksb_Users>();
}
The error is...
The entity or complex type 'bksbModel.bksb_Users' cannot be constructed in a LINQ to Entities query.
Does below work?
public List<bksb_Users> SearchStudents(string reference, string firstname, string lastname)
{
var anon = (from u in context.bksb_Users
where u.userName.Contains(reference)
&& u.FirstName.Contains(firstname)
&& u.LastName.Contains(lastname)
orderby u.FirstName, u.LastName
select new
{
user_id = u.user_id,
userName = u.userName,
FirstName = u.FirstName,
LastName = u.LastName,
DOB = u.DOB
}).Take(100).ToList();
return anon.Select(z => new bksb_Users()
{
user_id = z.user_id, userName = z.userName, FirstName = z.FirstName, DOB = z.DOB
}).ToList();
}
All I have done is split the task into two steps:
Get the data out (into an anonymous type) using LINQ to Entities.
Convert the anonymous type into the desired type using LINQ to
Objects.
Note a better option would be to create a new type (class) that contains just the fields/properties you need - that would remove the need for step 2, and will make it clear to the callers of your function which columns are 'populated' and which aren't. It also means you are less likely to 'accidentally' try and persist these half populated entities back to the database.
for some reason i quess that field DOB looks something like this
public object DOB { get { return fieldX + fieldY } }
Entity framework does not understand that. All fields in query must be mapped with certain columns in DB

LINQ method with varying parameters

I have a LINQ method for the Search page of an in house app. The method looks as below
public static DataTable SearchForPerson(String FirstName, String MiddleName, String LastName, String SSN, DateTime? BirthDate)
{
var persons = (from person in context.tblPersons
where person.LastName == LastName || person.LastName.StartsWith(LastName)
join addresse in context.tblAddresses on person.PersonID equals addresse.PersonID
orderby person.LastName
select new { person.PersonID, person.LastName, person.FirstName, person.SSN, addresse.AddressLine1 });
var filteredPersonsList = persons.Where(p => p.LastName == LastName).ToList();
if (filteredPersonsList.Count == 0)
filteredPersonsList = persons.Where(p => p.LastName.StartsWith(LastName)).ToList();
var dataTable = filteredPersonsList.CopyLinqToDataTable();
return dataTable;
}
Now, as you can no doubt see, I made a slight oversight when creating this as it only searches by LastName. I was in process of expanding this when it occured to me that I may not be going about this properly.
So, finally to my question; Is it more desirable(read-best practice, more efficient, etc...) to have a single method like this with a mechanism(I am thinking a SWITCH on non-empty param) to tell which parameter to search with or should I simply make multiple versions, a la SearchForPersonByLastName & SearchForPersonBySSN?
Additionally, is there an even more elagent solution to this, I would think common, issue?
Am I understanding correctly that only one of the parameters will be used to do the search? If so then absolutely these should be separate methods. Any time you describe a method (or class etc.) using the word "and" or "or" you probably have a method that can be broken into multiple methods. So it sounds like this method is currently described as "this method searches for Persons by FirstName or MiddleName or LastName or SSN or BirthDate." So, write methods
SearchByFirstName
SearchByMiddleName
SearchByLastName
SearchBySSN
SearchByBirthDate
Obviously there will be some common logic between these methods that you can factor out into a helper method.
Please clarify if I misunderstood and I'll edit my answer accordingly.
Edit:
Okay, so you say that you might be searching by multiple parameters. I still strongly prefer the idea of separate methods for each parameter (better separation of concerns, easier to maintain, easier to test, etc.). Here is one way to tie them all together:
DataTable Search(
string firstName,
string middleName,
string lastName,
string ssn,
DateTime? birthdate
) {
IQueryable<Person> query = context.tblPersons;
if(SearchParameterIsValid(firstName)) {
query = SearchByFirstName(query, firstName);
}
if(SearchParameterIsValid(middleName)) {
query = SearchByMiddleName(query, middleName);
}
if(SearchParameterIsValid(lastName)) {
query = SearchByLastName(query, lastName);
}
if(SearchParameterIsValid(ssn)) {
query = SearchBySSN(query, ssn);
}
if(birthDate != null) {
query = SearchByBirthDate(query, birthDate);
}
// fill up and return DataTable from query
}
bool SearchParameterIsValid(string s) {
return !String.IsNullOrEmpty(s);
}
IQueryable<Person> SearchByFirstName(
IQueryable<Person> source
string firstName
) {
return from p in source
where p.FirstName == firstName || p.FirstName.StartsWith(firstName)
select p;
}
// etc.
Or:
DataTable Search(
string firstName,
string middleName,
string lastName,
string ssn,
DateTime? birthdate
) {
Predicate<Person> predicate = p => true;
if(SearchParameterIsValid(firstName)) {
predicate = PredicateAnd(predicate, FirstNamePredicate(firstName));
}
if(SearchParameterIsValid(middleName)) {
predicate = PredicateAnd(predicate, MiddleNamePredicate(middleName));
}
// etc.
}
Predicate<T> PredicateAnd<T>(Predicate<T> first, Predicate<T> second) {
return t => first(t) && second(t);
}
Predicate<Person> FirstNamePredicate(string firstName) {
return p => p.FirstName == firstName || p.FirstName.StartsWith(firstName);
}
// etc.
DataTable SearchByPredicate(
IQueryable<Person> source,
Predicate<Person> predicate
) {
var query = source.Where(predicate)
.Join(
context.tblAddresses,
p => p.PersonID,
a => a.PersonID,
(p, a) => new {
p.PersonID,
p.LastName,
p.FirstName,
p.SSN,
a.AddressLine1
}
);
return query.CopyLinqToDataTable();
}
If I understand your question right, you're trying to add the other parameters to the where clause of your query. Might I suggest:
var persons = (from person in context.tblPersons
where (!string.IsNullOrEmpty(LastName) && (person.LastName == LastName || person.LastName.StartsWith(LastName))) &&
(!string.IsNullOrEmpty(SSN) && (person.SSN == SSN)) // && etc as needed
join addresse in context.tblAddresses on person.PersonID equals addresse.PersonID
orderby person.LastName
select new { person.PersonID, person.LastName, person.FirstName, person.SSN, addresse.AddressLine1 });
This would allow you to pass any combination of parameters to filter on so you're not locked in to filtering on one parameter.
The intent would be much more clear with multiple methods.
If I look at your code and you use just one method, I'd be able to figure out what was going on, but I'd have to look at it for a while to see what the heck you're doing. Maybe some comments would help clarify things, etc...
But, multiple methods will show me EXACTLY what you're trying to do.
As Jason said, be sure to factor out the common code into a helper method. I'd hate to see the same (more or less) linq query in each method.
You can add multiple where clauses so callers can specify the name fields they wish to search on, or null to match anything:
var filteredPersonsList = persons
.Where(p => FirstName != null && p.FirstName == FirstName)
.Where(p => MiddleName != null && p.MiddleName == MiddleName)
.Where(p => LastName != null && p.LastName == LastName).ToList();
So the caller could specifiy:
var matches = SearchForPerson("firstName", null, "lastName", "SSN", dob);
To ignore the middle name in the search.
Note you can combine these clauses into one using && although that could get difficult to read.
The single method you have is fine.
I would build up the LINQ one where clause at a time. This way when you actually run the LINQ it only processes the needed where clauses. This should be more efficient than other solutions proposed. LINQ is great as you can create your LINQ expression piece by piece using if logic as needed and then run it. You do not need to put all your if logic inside your LINQ expression if you can determine the logic while building the LINQ expression.
I would also simplify to only StartsWith.
One other thing, it seems that filteredPersonsList filtering is redundant as you are already filtering and I believe you can get rid of those lines.
var persons = from person in context.tblPersons
select person;
if (!string.IsNullOrEmpty(FirstName))
persons = from person in persons
where person.FirstName.StartsWith(FirstName)
select person;
if (!string.IsNullOrEmpty(MiddleName))
persons = from person in persons
where person.MiddleName.StartsWith(MiddleName)
select person;
if (!string.IsNullOrEmpty(LastName))
persons = from person in persons
where person.LastName.StartsWith(LastName)
select person;
if (!string.IsNullOrEmpty(SSN))
persons = from person in persons
where person.SSN = SSN
select person;
if (BirthDate.HasValue)
persons = from person in persons
where person.BirthDate == BirthDate.Value
select person;
return (from person in persons
join address in context.tblAddresses
on person.PersonID equals address.PersonID
orderby person.LastName
select new { person.PersonID, person.LastName,
person.FirstName, person.SSN, address.AddressLine1 })
.ToList()
.CopyLinqToDataTable();
Might want to create an object to reflect a person, then add a filter method to it:
Person.AddFilter( fieldToLimit, operator, value)
This way you can add any number of filter criteria to the object.
Example:
Person.AddFilter( FirstName, Contains, "Bob");
Person.AddFilter( LastName, StartsWith, "Z");
Another way is to simply add your criteria to your Linq to SQL IQueryable datatype so that you can simply:
Person.Where( t => t.FirstName.Contains("Bob")).Where( t => t.LastName.StartsWith("Z"));

Categories

Resources