Linq to SQL multiple conditional where clauses - c#

At the moment I am retrieving my results as follows :
public List<claim> GetClaims()
{
return _db.claims.OrderBy(cl => cl.claimId).ToList();
}
But now I am trying to add up to 8 conditional where clauses based on filters above my listview.
So I turned into:
public List<claim> GetFilteredClaims(string submissionId, string claimId,
string organization, string status,
string filterFromDate, string filterToDate,
string region, string approver)
{
return _db.claims.Where(cl => cl.submissionId == 5).ToList();
}
How can I do a check for each filter to add a where clause only if they contain a value?

There is no reason why you can't just keep filtering the results by calling .Where several times. Because of the deferred execution of LINQ to SQL it will all be executed in one SQL statement:
public List<claim> GetFilteredClaims(string submissionId, string claimId,
string organization, string status,
string filterFromDate, string filterToDate,
string region, string approver)
{
IQueryable<claim> filteredClaims = _db.claims;
if (!string.IsNullOrWhiteSpace(submissionId))
{
filteredClaims = filteredClaims.Where(claim => claim.submissionId == submissionId);
}
if (!string.IsNullOrWhiteSpace(claimId))
{
filteredClaims = filteredClaims.Where(claim => claim.claimId == claimId);
}
...
return filteredClaims.ToList();
}
If you will ever need to add OR conditions, you could take a look at PredicateBuilder.

You could group each filter option with an OR null condition, and chain them all together with AND, like so:
public List<claim> GetFilteredClaims(string submissionId, string claimId, string organization, string status, string filterFromDate, string filterToDate, string region, string approver)
{
return _db.claims
.Where(cl => (cl.submissionId == submissionId || submissionId == null)
&& (cl.claimId == claimId || claimId == null)
&& so on and so on... ).ToList();
}
So if submissionId == null and claimId == "123", it would only filter the results on claimId. You could replace each null with an empty string or whatever your "no value" condition is.

Take a look at LINQKIT http://www.albahari.com/nutshell/linqkit.aspx It will allow you to build predicate
Here is a code that I use but read the documentation above. The predicate will allow you to chain up a bunch of OR or AND or combination of it.
private static IEnumerable<SurveyResult> filterData(string firstName, string lastName, List<SurveyResult> results)
{
var predicate = PredicateBuilder.True<SurveyResult>();
IEnumerable<SurveyResult> a;
if (excludeBadData)
if (firstName != string.Empty)
{
predicate = predicate.And(p => p.User.FirstName.ToLower().Contains(firstName.ToLower()));
}
if (lastName != string.Empty)
{
predicate = predicate.And(p => p.User.LastName.ToLower().Contains(lastName.ToLower()));
}
a = from r in results.AsQueryable().Where(predicate) select r;
return a;
}

Related

Calling query inside query in entity framework core

I want to do something like this
public string GetSiteTitleFromChangeHistory(int siteId)
{
var changeHistories = changeHistoryRepository.GetAll(c => c.SiteRegistryId == siteId);
var value = changeHistories.firstOrDefault(r=>r.State="Active")
return value.Title;
}
public IQueryable<PendingReconfirmation> GetSitesForBusinessReconfirmationReminder(IList<StateStatus> stateStatusMappingIds,
string country, int reminderDay)
{
return from reg in repositorySpositeRegistry.GetAll(x => x.SiteUrlcountryCode != null
&& x.SiteUrlcountryCode.ToLower() == country.ToLower())
select new PendingReconfirmation()
{
Id = reg.Id,
SiteTitle = GetSiteTitleFromChangeHistory(reg.Id!).ToString() ?? reg.SiteTitle,
};
}
Repository.GetAll look like this
public IQueryable<T> GetAll(Expression<Func<T, bool>>? filter = null)
{
var query = entities.AsQueryable();
if (filter != null)
query = query.Where(filter);
return query;
}
But I am getting error
The client projection contains a reference to a constant expression of '' through the instance method 'GetSiteTitleFromChangeHistory'. This could potentially cause a memory leak; consider making the method static so that it does not capture constant in the instance.
Any help will be highly appreciated
Make this function GetSiteTitleFromChangeHistory as STATIC.
Is there a specific reason you split up GetSiteTitleFromChangeHistory into a separate method?
Otherwise you should be able to do a subquery like so, provided the queryable used for the subquery is based on a dbset in the same efcontext:
public IQueryable<PendingReconfirmation> GetSitesForBusinessReconfirmationReminder(IList<StateStatus> stateStatusMappingIds,
string country, int reminderDay)
{
return from reg in repositorySpositeRegistry.GetAll(x => x.SiteUrlcountryCode != null
&& x.SiteUrlcountryCode.ToLower() == country.ToLower())
select new PendingReconfirmation()
{
Id = reg.Id,
SiteTitle = changeHistoryRepository.GetAll(c => c.SiteRegistryId == reg.Id && c.State == "Active").FirstOrDefault().Title ?? reg.Title
};
}

FindAsync not finding results with DateTime comparison

using MongoDB.Bson;
public async Task<List<Person>> Get(string lastName, string firstName, string dob, string id)
{
var dobParse = DateTime.Parse(dob);
var persons = await _patients.FindAsync<Patient>(person =>
(String.IsNullOrWhiteSpace(lastName) || person.lastName == lastName) &&
(String.IsNullOrWhiteSpace(firstName) || person.firstName == firstName) &&
(String.IsNullOrWhiteSpace(dob) || person.dob.Equals(DateTime.Parse(dob))) &&
(String.IsNullOrWhiteSpace(id) || person.Id == id));
return await persons.ToListAsync();
}
When isolated it looks like all other comparisons work as they return results, except for this condtion (String.IsNullOrWhiteSpace(dob) || person.dob.Equals(DateTime.Parse(dob))). I've tried various ways of checking for a null dob DateTime including passing it in as a nullable DateTime and checking if it has value, as well as various ways of comparing the 2 DateTime objects.
Here is a sample record and parameters used to query it:
{
"_id": {
"$oid": "610f727c7ec433f528bf3727"
},
"firstName": "John",
"lastName": "Doe",
"dob": "1980-01-30T00:00:00"
}
QueryParams: lastName=Doe&firstName=John&dob=1980-01-30T00:00:00&id=610f727c7ec433f528bf3727
The DateTimes are identical.
I've added some debug code to compare the DateTimes and added setting the search DateTime kind to UTC and adding the UTC attribute to the model property for "Person".
public async Task<List<Person>> Get(string lastName, string firstName, string dob, string id)
{
var dobParse = DateTime.Parse(dob,CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
var Persons = await _Persons.FindAsync<Person>(person =>
(String.IsNullOrWhiteSpace(lastName) || person.lastName == lastName) &&
(String.IsNullOrWhiteSpace(firstName) || person.firstName == firstName) &&
//person.dob == dobParse &&
(String.IsNullOrWhiteSpace(id) || person.Id == id));
var resultDob = Persons.FirstOrDefault().dob;
if (dobParse == resultDob)
{
var match = true;
}
return await Persons.ToListAsync();
}
In the example above match is set as true as if (dobParse == resultDob) resolves as true. However, no results from the DB are still returned despite the other conditions being true (firstName, lastName, and id).

How to sort List using custom column values in linq?

Net core, Ef core and linq. I have table with column status. It holds the values New, In-Progress and closed. When I will query all the rows with New should come first followed by in-progress and closed. Below is my code.
var Requests = await this.RequestRepository.GetAsync(x => x.IsActive == true && x.CreatedBy == LoggedInUser, null, x => x.Country).ConfigureAwait(false);
Below is my GetAsync method
public async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = this.dbSet;
foreach (Expression<Func<T, object>> include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return await query.ToListAsync().ConfigureAwait(false);
}
So I am expecting all the rows with status New should come first then In-Progress and closed. I am not finding anyway to figure it out. Can someone help me to write this. Any help would be appreciated. Thank you
Your life would probably be a lot easier if you throw this "helper" method away and just use LINQ straight. Look how much more obviously readable it is:
var apps = db.FundingApplications
.Include(x => x.Applicant)
.Where(x => x.ApplicationDate.Year == DateTime.Now.Year)
.OrderBy(x => (int)x.Status)
(I skipped the await/async for brevity, not because I'm advocating sync)
Status of course being an enum like:
enum Status{
New, Inprogress, Closed
}
If your enum is awkwardly NOT in numeric order like this but is instead eg
enum Status {
New = 20,
InProgress = 1,
Closed = 99
}
Then you could order it by descending after you ToString it (you'll notice the names are in reverse alphabet order) or create a mapping using a dictionary that maps eg d[New] = 0, d[InProgress] = 1 ..., or do an (in-line) if set in the OrderBy.
OrderByDescending(x => x.Status.ToString("g"));
var d = new Dictionary<status, int>() {
{New, 0}, {InProg,1}, {Closed,2}
};
OrderBy(x => d[x.Status]);
OrderBy(x => x.Status == Status.New ? 0 : (x.Status == Status.InProg ? 1 : 2))
Only the last of these has any real chance of being executed on the server though, and ordering by its name will only work If you don't rename them or add new names that break the ordering.
I do also appreciate the OrderBy/ThenBy given in the other answer and think it's good but I think care should be given to testing whether it executed on the server or the client because it carries out a boolean compare and a lot of db don't use booleans as a data type or if they do they might sort falsy (0) ahead of truey (1)
OrderBy(x => x.Status == New)
Might just be then that it sorts all closed and InProg ahead of New because the result of the boolean if evaluated by the server is 0 for InProg/closed and 1 for new. MySql and Postgres would be my top picks for worrying that this would be server evaluated because they do allow boolean type values in contexts other dbs don't.
So, be careful that a) this kind of ordering is evaluating where you expect and want (client/server) and b) if it really is evaluating on the server that the server orders it as you want
Edit, just saw your comment that indicates that Status isn't an enum but some set of string constants??
If your DB values are literally string and status was never an enum then you can just order them by descending
OrderByDescending(x => x.Status)
I have made a sample code according to your requirement. It's working fine on mine. Please refer to this. Hope this will work on your code too.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
List<Name> list = new List<Name>
{
new Name { FirstName = "Nishan1", LastName = "Dhungana1", Status = Status.Closed},
new Name { FirstName = "Nishan2", LastName = "Dhungana2", Status = Status.New},
new Name { FirstName = "Nishan3", LastName = "Dhungana3", Status = Status.New},
new Name { FirstName = "Nishan4", LastName = "Dhungana4", Status = Status.Closed},
new Name { FirstName = "Nishan5", LastName = "Dhungana5", Status = Status.Closed},
new Name { FirstName = "Nishan6", LastName = "Dhungana6", Status = Status.InProgress},
new Name { FirstName = "Nishan7", LastName = "Dhungana7", Status = Status.Closed},
new Name { FirstName = "Nishan8", LastName = "Dhungana8", Status = Status.InProgress},
new Name { FirstName = "Nishan9", LastName = "Dhungana9", Status = Status.InProgress},
new Name { FirstName = "Nishan10", LastName = "Dhungana10", Status = Status.New},
new Name { FirstName = "Nishan11", LastName = "Dhungana11", Status = Status.New}
};
list = list.OrderByDescending(x => x.Status == Status.New)
.ThenByDescending(x => x.Status == Status.InProgress)
.ThenByDescending(x => x.Status == Status.Closed)
.ToList();
Console.WriteLine("hey");
Console.ReadLine();
}
}
public class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Status Status { get; set; }
}
public enum Status
{
New = 1,
InProgress = 2,
Closed = 3
}
}

LINQ's SelectMany Equivalent in OData

I have an entity called AccountAction that has property called TransactionTypes that is an ICollection of TransactionType entities.
I return a list of all the TransactionTypes that are related to all the AccountActions in a method called GetTransactionTypes. I would like to apply query options to the returned list of TransactionTypes. However, so far I have hit a wall because all of the query options are applied to AccountActions.
Is there any way I can apply query options in the URL to the returned lists of TransactionTypes? In other words, is there a way I can do a SelectMany from the URL to get the TransactionTypes related to the AccountActions to move on to apply the query options to the found TransactionTypes?
Below is an extract of the code that I am using.
[Route(FullControllerPath + "/TransactionTypes")]
public IHttpActionResult GetTransactionTypes(ODataQueryOptions<AccountAction> queryOptions, bool addCols, int? skip, int? take)
{
using (AccountActionManagement _accountActionManage = new AccountActionManagement(this.GenerateInformation()))
{
_accountActionManage.SetTraslationList("DATASTRUCT-CONFIG-ACCOUNTACTIONTRANSACTIONTYPE", language);
// Query composition
IQueryable<TransactionType> query = queryOptions.ApplyTo(_accountActionManage.GetTypeAsQueryable<AccountAction>())
.OfType<AccountAction>()
.SelectMany(aa => aa.TransactionTypes)
.Include(tt => tt.AccountActionForDefaultTransactionType.DefaultTransactionType);
var queryData = query.Select(tt => new
{
Id = tt.Id,
Name = tt.Name,
Operation = tt.Operation,
Type = tt.Type,
Default = tt.AccountActionForDefaultTransactionType != null &&
tt.AccountActionForDefaultTransactionType.DefaultTransactionType.Id == tt.Id,
Version = tt.AccountActionForDefaultTransactionType.Version
});
// Get count
int totalRows = queryData.Count();
// Get biggest version in query
var maxVersion = queryData.Max(i => i.Version);
// Get data from database
var queryResult = queryOptions.OrderBy == null
? queryData.OrderBy(i => i.Id)
.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList()
: queryData.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList();
...}}
As seen in the diagram below, AccountAction has a many-to-many relationship to TransactionType. AccountAction has the first role and TransactionType has the second role.
I found a workaround for this issue. I realized that I was not passing the right type to the ApplyTo method. Now, I apply the query options to an IQueryable of TransactionTypes instead of applying the query options to an IQueryable of AccountActions.
Below is the code with the described modification. Also, a diffchecker of the change I made is here.
[Route(FullControllerPath + "/TransactionTypes")]
public IHttpActionResult GetTransactionTypes(ODataQueryOptions<AccountAction> queryOptions, bool addCols, int? skip, int? take)
{
using (AccountActionManagement _accountActionManage = new AccountActionManagement(this.GenerateInformation()))
{
_accountActionManage.SetTraslationList("DATASTRUCT-CONFIG-ACCOUNTACTIONTRANSACTIONTYPE", language);
// Query composition
IQueryable<TransactionType> query = queryOptions.ApplyTo(_accountActionManage.GetTypeAsQueryable<AccountAction()
.SelectMany(aa => aa.TransactionTypes)
.Include(aa => aa.AccountActionForDefaultTransactionType.DefaultTransactionType))
.OfType<TransactionType>();
var queryData = query.Select(tt => new
{
Id = tt.Id,
Name = tt.Name,
Operation = tt.Operation,
Type = tt.Type,
Default = tt.AccountActionForDefaultTransactionType != null &&
tt.AccountActionForDefaultTransactionType.DefaultTransactionType.Id == tt.Id,
Version = tt.AccountActionForDefaultTransactionType.Version
});
// Get count
int totalRows = queryData.Count();
// Get biggest version in query
var maxVersion = queryData.Max(i => i.Version);
// Get data from database
var queryResult = queryOptions.OrderBy == null
? queryData.OrderBy(i => i.Id)
.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList()
: queryData.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList();
...}}

c# linq: how to retrieve pieces of data from a returned record

So i have the query:
var query =
from product in db.LUT_ProductInfos
where product.flavor == flavor && product.Container == container
select flavor;
which is returning 1 record (it should always return one record). How do i pull individual attribute values from the result? For example, if it returns 1 record with 3 columns like firstName lastName phoneNumber, and i want to do something with each of the values like
string fName = firstName
string lName = lastName
int pNumber = phoneNumber
without getting into data mapping for objects, what is the most direct way to extract those values from the result variable query
var query =
from product in db.LUT_ProductInfos
where product.flavor == flavor && product.Container == container
select new {product.Foo, product.flavor.Bar, ...};
var row = query.First(); // or Single(), FirstOrDefault(), SingleOrDefault()
string foo = row.Foo;
int bar = row.Bar;
...
This has the advantage that only the desired properties are included in the SELECT statement, and there are no hidden additional round-trips (lazy loading).
var singleResult =
(from product in db.LUT_ProductInfos
where product.flavor == flavor && product.Container == container
select flavor).Single();
string fName = singleResult.firstName
string lName = singleResult.lastName
int pNumber = singleResult.phoneNumber
var row = db.LUT_ProductInfos
.SingleOrDefault(product => product.flavor == flavor && product.Container == container);
if (row != null)
{
string firstName = row.fName;
string lastName = row.lName;
string phone = row.pNumber;
}
The best and easiest way to do this is to recode like this:
var query =
(from product in db.LUT_ProductInfos
where product.flavor == flavor && product.Container == container
select flavor).FirstOrDefault();
it will return the first matched record. If there's no matched record it will retutn null value, otherwise it returns an object of LUT_ProductInfos class. you can also skip records as you wish like this:
var query =
(from product in db.LUT_ProductInfos
where product.flavor == flavor && product.Container == container
select flavor).Skip(25).FirstOrDefault();

Categories

Resources