LINQ select where EF Navigation property value in array - c#

I have a Web API method that is used to search a EF object named 'Patients'. The patient has a navigation property named Referrals (one patient can have many referrals) and each Referral has an integer property named 'ConsultantID'.
I would like to return all patients who have referrals whose ConsultantID value is within a defined array named 'consultants' but i can't get my head around the logic needed. I have the below at the moment but it isn't working as i expected, the Referrals.Any call seems to be performing an 'Exists' call rather than the join behaviour i was expecting.
public List<HelperCode.DTO.SearchResult> SearchPatients(string firstname, string surname, [FromUri] int[] consultants)
{
IQueryable<Patient> results = db.Patients;
List<HelperCode.DTO.SearchResult> output = new List<HelperCode.DTO.SearchResult>();
List<int> inputConsultants = consultants.OfType<int>().ToList();
if (!String.IsNullOrEmpty(firstname)) { results = db.Patients.Where(c => c.FirstName.ToLower().Contains(firstname.ToLower())); }
if (!String.IsNullOrEmpty(surname)) { results = results.Where(c => c.Surname.ToLower().Contains(surname.ToLower())); }
if (consultants.Length > 0) {
results = results.Where(c => c.Referrals.Any(r => inputConsultants.Contains(r.ConsultantID ?? default(int))));
}
results = results.OrderBy(i => i.Surname);
foreach (Patient p in results) {
output.Add(new HelperCode.DTO.SearchResult(p));
}
return output;
}

Developer's a tool, code works fine but input values were incorrect. Hopefully will be of use to someone down the line.

Related

C# - Parsing Data inside Lambda Linq Block | Splitting .Where()

I want to filter Entities from a Database, but I am kinda stuck on how to correctly chain the Methods together....
The User can search from one Textfield the Id, Title and Description. This SearchString will be bound to SearchString in SearchData.
I have a method:
public List<Movies> Search(SearchData search)
{
var movies = from m in entities select m;
if (!String.isNullOrEmpty(search.SearchString))
{
movies = movies.Where(x => x.Title.Contains(search.SearchString)).Where(//descritpion);
}
return movies;
}
This works, but I also need to check for the Id
.Where(x=>x.Id == search.SearchString)
This won´t work since Id is a int and SearchString a String.
I have tried multiple ways to do so:
I did use "Convert.ToInt32" on the SearchString or "Convert.ToString" on the Id, but out of some reason I won´t get anything back with this and an Error when I search for a String.
I tried to use a block with in the where : .Where(x => {if(Tryparse(Searchstring) {}else{}}), but it doesn´t like it when I try to return the Movie object or null.
I also tried to split the clause up:
if (int.tryparse(searchstring))
movies = movies.where(x=>x.id ==Int32.Parse(SearchString));
movies = movies.where(//title and desc)
,but with this all the Movies I have found in the if will be filtered out due to the title and desc.
My questions are:
1.)Is it possible to "split" those Methods so that they behave like an OR instead of an AND?.... Or that they will not be executed anymore after one worked since the User will only be allowed to enter an Int OR a String. I have more values I am filtering against for which I would need this too.
2)How can I test against more "complex" Logic inside the Lambdas?
I'm not sure if I understand it correctly but If you want to search where id == search.SearchString, and also any other condition with OR then you should do something like this:
.Where(x=>x.Id == search.SearchString && (x.description.Contains(search.Description) || x.Title.Contains(search.Title) || x.Whatever.Contains(search.Whatever)));
You can use an OR in the Where clause.
public List<Movies> Search(SearchData search)
{
var movies = from m in entities select m;
if (!String.IsNullOrEmpty(search.SearchString))
{
movies = movies.Where(x => x.Title.Contains(search.SearchString) || x.Description.Contains(search.SearchString));
}
return movies;
}
I don't understand why the movie Title should contain the movie Id, for my point of view it's bad practice,
i think you need to update the SearchData and add a separate field for the Id, and use it for filtering, this will make things more clear and easy for debugging
In order to respect both conditions, I'd first check whether the search string can be parsed to an integer. If so, you can compare the id against it, if not, the Id comparison should be omitted:
public List<Movies> Search(SearchData search)
{
var movies = from m in entities select m;
if (!String.isNullOrEmpty(search.SearchString))
{
int? searchId;
if (int.TryParse(search.SearchString, out i)
searchId = i;
else
searchId = null;
movies = movies.Where(x =>
(searchId.HasValue && x.Id == searchId.Value)
|| x.Title.Contains(search.SearchString)).Where(//descritpion);
}
return movies;
}
In the comparison, the first part checks whether the searchId is set and - if so - compares the id of the row against it. In addition, it checks whether the title contains the search string. If any of the two conditions are met, the row is returned. However, if the user enters 123 and this is both a valid id and part of a title, both rows will be returned.
If you want to search for id (exclusive) or a part of a text, you could use the following approach:
public List<Movies> Search(SearchData search)
{
var movies = from m in entities select m;
if (!String.isNullOrEmpty(search.SearchString))
{
Expression<Func<Movie, bool>> whereClause;
if (int.TryParse(search.SearchString, out searchId)
whereClause = (x) => x.Id == searchId;
else
whereClause = (x) => x.Title.Contains(search.SearchString);
movies = movies.Where(whereClause).Where(//descritpion);
}
return movies;
}
When entering 123, above sample searches for the id, when entering anything that cannot be parsed into an integer, it looks for a part of the title.
As for your second question: when using Entity Framework, the conditions in your lambda expressions are translated to SQL. This limits the available options. However, as shown in the first example, you can prepare your data and adjust the conditions accordingly in many cases. Nevertheless, not every valid lambda expression can be translated to SQL.

Reusing Base Linq Query from one method to another

I am trying to remove duplicate code throughout my project and I am at a standstill trying to figure this out. What I am trying to do is create a base linq query that will be reused to add things like Where, Take...etc in multiple different methods.
public IQueryable<Object> FooLinq(int id)
{
using (var ctx = new dbEntities())
{
var results =
(from account in ctx.account
join memberProducts in ctx.tblMemberProducts on account.Id equals memberProducts.AccountId
orderby account.date descending
select new{account,memberProducts}).ToList();
return results;
}
}
So that would be by base query above and I would have a seperate method that would reuse VioLinq but this time would use a where clause in it.
public List<IncomingViolations> Foo1(int id)
{
//Linq query FooLinq() where Name == "Bob"
}
You'll need to do two things:
Return the query prior to materializing it.
Make sure the context is still in scope when the final query is materialized.
These two requirements will play off each other somewhat, and there are a number of approaches you can take to meet them.
For example, you could make your method take the context as a parameter, forcing the caller to provide it and manage its lifecycle.
public IQueryable<AccountInfo> FooLinq(DbEntities ctx, int id)
{
return
from account in ctx.account
orderby account.date descending
select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
};
}
public List<IncomingViolations> Foo1(int id)
{
using(var ctx = new dbEntities())
{
//Linq query FooLinq() where Name == "Bob"
return FooLinq(ctx).Where(v => v.Name == "Bob").ToList();
}
}
You could alternatively inject the context as a constructor-injected dependency, and use a DI framework to manage the context's lifecycle.
You can do it as Queryable then add conditions to it.
For example:
public List<account> GetAccountsByName(string name, bool usePaging, int offset = 0, int take = 0) {
var query = GetMyQuery();
query = query.Where(x => x.Name == name);
query = query.OrderBy(x => x.Name);
if(usePaging) {
query = query.Take(take).Skip(offset);
}
query = PrepareSelectForAccount(query);
return query.ToList(); .
}
public IQueryable<account> GetMyQuery(){
return ctx.account.AsQueryable();
}
public IQueryable<account> PrepareSelectForAccount(IQueryAble<account> query){
return query.Select(select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
}
);
}
Sure, but don't call .ToList(), and return IQueryable<T> instead of List<T>. LINQ is based on the concept of deferred execution which means the query is not actually performed until the enumerable is iterated over. Until then, all you have done is built an object which knows how to do the query when the time comes.
By returning an IQueryable<T> from a function that sets up the "basic query," you are then free to tack on additional LINQ methods (such as .Where() or .Take()) to produce a modified query. At this point you are still simply setting up the query; it is actually performed only when you iterate over the enumerable, or call something like .ToList() which does that for you.

Linq code doesn't return correct record

I have a table named dbo.EmployeeType with three records:
PK_EmployeetypeID EmployeeTypeName
1 Project Manager
2 Business Analyst
3 Developer
I have this piece of Linq code:
public static string GetTypeByID(int id)
{
using (ProjectTrackingEntities1 db = new ProjectTrackingEntities1())
{
var type = db.EmployeeTypes.Select(o => new LOOKUPEmployeeType
{
PK_EmployeeTypeID = id,
EmployeeTypeName = o.EmployeeTypeName
});
return type.FirstOrDefault().EmployeeTypeName;
}
}
No matter what id I send to it, it returns Project Manager, and I'm confused as to why.
You need to apply a filter, otherwise you're just returning the first record and hard coding the ID. Try this:
public static string GetTypeByID(int id)
{
using (ProjectTrackingEntities1 db = new ProjectTrackingEntities1())
{
//Here we apply a filter, the lambda here is what creates the WHERE clause
var type = db.EmployeeTypes
.FirstOrDefault(et => et.PK_EmployeeTypeID == id);
if(type != null)
{
return type.EmployeeTypeName;
}
else
{
return "";
}
}
}
Note that using FirstOrDefault means if there are no matches, or multiple matches, type will be null and you will get an empty string returned.
Set a breakpoint on type = ... and inspect it. You have no Where in there so you get all - and Select just makes LOOKUPEmployeeTypes out of all of them.
FirstOrDefault then returns the first of those 3 which is always the ProjManager
Fix:
var type = db
.EmployeeTypes
.Where( o => o.Id == id)
.Select(o => new LOOKUPEmployeeType
{
PK_EmployeeTypeID = id,
EmployeeTypeName = o.EmployeeTypeName
});
In your code you only return the first value. You need to tell EF which value you need to return.
Let us assume you need the value with Id=2. Instead of Select(), use Single(x => x.Id == 2) or First(x => x.Id == 2).

How to modify only one or two field(s) in LINQ projections?

I have this LINQ query:
List<Customers> customers = customerManager.GetCustomers();
return customers.Select(i => new Customer {
FullName = i.FullName,
Birthday = i.Birthday,
Score = i.Score,
// Here, I've got more fields to fill
IsVip = DetermineVip(i.Score)
}).ToList();
In other words, I only want one or two fields of the list of the customers to be modified based on a condition, in my business method. I've got two ways to do this,
Using for...each loop, to loop over customers and modify that field (imperative approach)
Using LINQ projection (declarative approach)
Is there any technique to be used in LINQ query, to only modify one property in projection? For example, something like:
return customers.Select(i => new Customer {
result = i // telling LINQ to fill other properties as it is
IsVip = DetermineVip(i.Score) // then modifying this one property
}).ToList();
you can use
return customers.Select(i => {
i.IsVip = DetermineVip(i.Score);
return i;
}).ToList();
Contrary to other answers, you can modify the source content within linq by calling a method in the Select statement (note that this is not supported by EF although that shouldn't be a problem for you).
return customers.Select(customer =>
{
customer.FullName = "foo";
return customer;
});
You "can", if you create a copy constructor, which initializes a new object with the values of an existing object:
partial class Customer
{
public Customer(Customer original)
{
this.FullName = original.FullName;
//...
}
}
Then you can do:
return customers.Select(i => new Customer(i) { IsVip = DetermineVip(i.Score)})
.ToList()
But the downfall here is you will be creating a new Customer object based on each existing object, and not modifying the existing object - this is why I have put "can" in quotes. I do not know if this is truly what you desire.
No, Linq was designed to iterate over collections without affecting the contents of the source enumerable.
You can however create your own method for iterating and mutating the collection:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach(T item in enumeration)
{
action(item);
}
}
You can then use as follows:
return customers.ToList()
.ForEach(i => i.IsVip = DetermineVip(i.Score))
.ToList();
Note that the first ForEach will clone the source list.
As customers already is a List, you can use the ForEach method:
customers.ForEach(c => c.IsVip = DetermineVip(c.Score));

How can I make this LINQ query?

I have a Dropdownlist that I fill with all consultants.
This is my entities I am using for this LINQ query:
What I want to do is to get all consultants ids that have a goalcard with Complete_date set. I have no idea how to do this.
Right now I have this following LINQ query to get all consultants ids.
public List<Consultant> GetAllConsultantsByID()
{
var allConsultants = from id in db.Consultant
select id;
return allConsultants.ToList();
}
Any kind of help is appreciated
Thanks in advance
Update:
This is how I have to use my Linq method in my Get Action Method:
var consultants = repository.GetAllConsultantsByID();
model.Consultants = new SelectList(consultants, "Id", "Name");
You can use the Consultant.GoalCard navigation property and the Any extension method:
var query = from con in db.Consultant
where con.GoalCard.Any(card => card.Completed_Date != null)
select con;
return query.ToList();
Consultant.Goalcard exposes all GoalCards of the Consultant as a queryable property. So you can perform queries on that, too. (This example assumes Completed_Date is nullable)
Note: Seeing that a Consultant can have several GoalCards, you might want to rename the Consultant's GoalCard navigation property to GoalCards (to make it clear there can be several).
Now assuming the Complete_Date is of type DateTime?, you could do it like that:
public IEnumerable<Consultant> GetConsultantIds()
{
return db.Consultant.Where(c => c.GoalCard != null && c.GoalCard.Completed_Date.HasValue).Select(c => c.Id).AsEnumerable();
}
[EDIT]
Since GoalCard is a collection (misleading name :) ), you can do something like that to get the IDs of Consultants who have at least one completed date set on any of the cards:
public IEnumerable<int> GetConsultantIds()
{
return db.Consultant.Where(c => c.GoalCard != null && c.GoalCard.Any(card => card.Completed_Date.HasValue)).Select(c => c.Id).AsEnumerable();
}
That's for the list of IDs only, for the list of Consultant objects meeting the criteria:
public IEnumerable<Consultant> GetConsultantIds()
{
return db.Consultant.Where(c => c.GoalCard != null && c.GoalCard.Any(card => card.Completed_Date.HasValue)).AsEnumerable();
}

Categories

Resources