Func<T, TResult> with OrderBy resulting in error with null fields - linq

I've inherited some code, and also a bug.
The app is a C# MVC website using EF. The VIEW presents clients in a table (taken straight from a CLIENTS table in a SQL Server database).
List<Client> _clients = db.Client.ToList();
IEnumerable<Client> filteredClients;
The problem arises when the user clicks a header to sort by. The args passed to the controller indicate an index of the field to sort by (sortColumnIndex).
The original dev created a func to handle the translation from the index to the field.
Func<Client, string> orderingFunction = (c => sortColumnIndex == 1 ? c.Name.ToString()
: sortColumnIndex == 2 ? c.AccountExec.ToString()
: sortColumnIndex == 3 ? c.SalesforceLink.ToString()
: sortColumnIndex == 4 ? c.Location.ToString()
: sortColumnIndex == 5 ? c.PrimaryContact.ToString()
: sortColumnIndex == 6 ? c.AccountId.ToString()
: sortColumnIndex == 6 ? c.MongoClientId.ToString()
: ""); // index 0 is the hidden ClientId
The results of this are used in the OrderBy() clause.
filteredClients = filteredClients.OrderBy(orderingFunction);
When the field being sorted on has complete data (i.e. no NULL values), it works fine. As soon as a column has a NULL value, however, the resulting OrderBy throws a "Object reference not set to an instance of an object" error.
I'm afraid I'm not completely up to the task of deciphering the solution here; we still need to sort on the field selected by the user, even if all but one of the records have a NULL value. Is there any way to achieve this with the existing code structure, or is this better served by refactoring?
EDIT: full code up to the point of exception:
"param" is the argument that contains all of the filters and such.
List<Client> _clients = db.Client.ToList();
IEnumerable<Client> filteredClients;
//Check for filters. This is a search, and we can say it's empty for this purpose
if (!string.IsNullOrEmpty(param.sSearch))
{
var nameFilter = Convert.ToString(Request["bSearch_1"]); // Search string
var isNameSearchable = Convert.ToBoolean(Request["bSearchable_1"]); // Is column searchable? Optional
filteredClients = _clients.Where(x => x.Name.ToLower().Contains(param.sSearch.ToLower()));
}
else
{
filteredClients = _clients.OrderBy(x => x.Name);
}
// Sort Column
var isNameSortable = Convert.ToBoolean(Request["bSortable_1"]);
var sortColumnIndex = Convert.ToInt32(Request["iSortCol_0"]);
Func<Client, string> orderingFunction = (c => sortColumnIndex == 1 ? c.Name.ToString()
: sortColumnIndex == 2 ? c.AccountExec.ToString()
: sortColumnIndex == 3 ? c.SalesforceLink.ToString()
: sortColumnIndex == 4 ? c.Location.ToString()
: sortColumnIndex == 5 ? c.PrimaryContact.ToString()
: sortColumnIndex == 6 ? c.AccountId.ToString()
: ""); // index 0 is the hidden ClientId
// Sort Direction
var sortDirection = Request["sSortDir_0"]; // asc or desc
if (sortDirection == "asc")
{
//Results of this line generate exception
filteredClients = filteredClients.OrderBy(orderingFunction);
}
else
{
filteredClients = filteredClients.OrderByDescending(orderingFunction);
}

Well! Thanks to johnny 5's wondering why the c.AccountId.ToString() was in use, I pulled the .ToString() from the func<>, and that seemed to do the trick.
Thanks, johnny 5! Sometimes you just need another set of eyes!

Related

How to compare IEnumerable<string> for null in Linq query

For the following query:
var result = from sch in schemeDashboard
join exp in Expenditure on sch.schemeId equals exp.SchemeCode
into SchExpGroup
where sch.SectorDepartmentId == selectedDepartmentId &&
sch.YearCode == StateManager.CurrentYear
orderby sch.ADPId
select new
{
ModifiedAmounts = SchExpGroup.Select(a => a.ModifiedAmounts),
ProjectName = sch.schemeName,
ADPNo = sch.ADPId,
Allocation = sch.CurrentAllocation,
Expenditures = from expend in SchExpGroup
where expend.YearCode == StateManager.CurrentYear &&
expend.DepartmentId == selectedDepartmentId &&
InvStatus.Contains(expend.Status)
orderby expend.ADPId
group expend by expend.InvoiceId
};
I want to filter the above query on a condition so that result gives only those records where "ModifiedAmounts" are not null. I have tried as follow:
if (rbList2.SelectedIndex == 6)
{
result = result.Where(a => a.ModifiedAmounts != null));
}
but this gives error as:
Cannot compare elements of type
'System.Collections.Generic.IEnumerable`1'. Only primitive types,
enumeration types and entity types are supported.
Any suggestions as I am lost as how to rephrase the filtered query.
I think the problem is that ModifiedAmounts will never be null. Select will return an empty list. Unless SchExpGroup is null in which case you will get a null reference exception.
Try changing your code to
result = result.Where(a => a.ModifiedAmounts.Any());
if (rbList2.SelectedIndex == 6)
{
result = result.Where(a => a.!ModifiedAmounts.Any());
}

linq to entity,How does the where clause use ?: Expression

TAB_XXX and TAB_XXX_details are one-to-many relationships, I need to query the two tables, however, we need to be filtered TAB_XXX_details。
The code is as follows:
var qu = from c in db.TAB_XXX.Where(n => n.DELETE_MARK == false)
let dets = c.TAB_XXX_DETAILS.Where(n => condition.SaleType.HasValue ? n.SALE_TYPE == (decimal)condition.SaleType : 1 == 1)
select new
{
c,
dets
};
Condition.SaleType is number?, if the condition.SaleType is a valid number, such as 1, 2, 3 ... I want to filter the child record based on these numbers; when the condition.SaleType is null, I want to query TAB_XXX and all its child records;
How do I modify the where clause?
Thank you for your answer!
Since 1 == 1 is always true, your condition boils down to this:
let dets = c.TAB_XXX_DETAILS
.Where(n => !condition.SaleType.HasValue || n.SALE_TYPE == condition.SaleType.Value)
Essentially, you want to return all rows when condition.SaleType does not have value; otherwise, you make a comparison to condition.SaleType.Value.

LINQ to Entities does not recognize the method 'Int32 Min(Int32, Int32)'?

Im getting this error when I execute the following code, any Ideas how to fix it?
LINQ to Entities does not recognize the method 'Int32 Min(Int32, Int32)' method, and this method cannot be translated into a store expression.
result = items.ToList()
.Select(b => new BatchToWorkOnModel()
{
BatchID = b.Batch.ID,
SummaryNotes = b.Batch.Notes,
RowVersion = b.Batch.RowVersion,
Items = items
.Select(i => new ItemToWorkOnModel()
{
SupplierTitle = i.Title,
ItemID = i.ID,
BatchID = i.BatchID ?? 0,
ItemDate = i.PubDate,
// KB - Issue 276 - Return the correct Outlet name for each item
Outlet = i.Items_SupplierFields != null ? i.Items_SupplierFields.SupplierMediaChannel != null ? i.Items_SupplierFields.SupplierMediaChannel.Name : null : null,
Status = ((short)ItemStatus.Complete == i.StatusID ? "Done" : "Not done"),
NumberInBatch = i.NumInBatch,
Text = string.IsNullOrEmpty(i.Body) ? "" : i.Body.Substring(0, Math.Min(i.Body.Length, 50)) + (i.Body.Length < 50 ? "" : "..."),
IsRelevant = i.IsRelevant == 1,
PreviouslyCompleted = i.PreviouslyCompleted > 0 ? true : false
}).ToList()
})
.FirstOrDefault();
It seems Math.Min is not implemented by the EF query provider. You should be able to fix it by simply applying AsEnumerable on your items collection to do the expression using Linq to Objects instead;
Items = items.AsEnumerable().Select(i => new ItemToWorkOnModel()...
If you add a where condition to the item selection (seems a little strange to take all items in the whole table), you'll want to add it before AsEnumerable() to allow EF to do the filtering in the database.
Also, you only want the first result from the query, but you're fetching all of them using ToList() before cutting the list down to a single item. You may want to remove the ToList() so that EF/the underlying database can return only a single result;
result = items.Select(b => new BatchToWorkOnModel()...
You do not need Math.Min.
The line in question is:
Text = string.IsNullOrEmpty(i.Body)
? "" : i.Body.Substring(0, Math.Min(i.Body.Length, 50)) + (i.Body.Length < 50 ? "" : "...")
So what does this line return?
If i.Body is null or empty it returns an empty string. If it is 50 or more characters long it returns a substring of 50 characters and appends "...".
If the length is less than 50 it takes a substring with the length of the string and appends an empty string. But that's just the original string.
Text = string.IsNullOrEmpty(i.Body)
? "" : (i.Body.Length < 50 ? i.Body : i.Body.Substring(0, 50) + "...")

LINQ Substring CRM 2011

I have a LINQ query that pulls down some information based on certain criteria in the Where clause from CRM 2011. But I want to add it so it only looks at the first 5 characters of the Zip instead of the whole Zip. I tried adding SubString. But if the value is null it will fail. How would you go about matching the first 5 characters of the Zip in the Where clause. This is my query.
var lQuery = (from a in gServiceContext.CreateQuery("account")
where (a["name"].Equals(lLead.AccountName) &&
a["address1_postalcode"].Equals(lLead.ZipCode) &&
a["address1_stateorprovince"].Equals(lLead.State)) ||
(a["address1_line1"].Equals(lLead.Address1) &&
a["address1_postalcode"].Equals(lLead.ZipCode) &&
a["address1_city"].Equals(lLead.City))
select new
{
Name = !a.Contains("name") ? string.Empty : a["name"],
City = !a.Contains("address1_city") ? string.Empty : a["address1_city"],
State = !a.Contains("address1_stateorprovince") ? string.Empty : a["address1_stateorprovince"],
Zip = !a.Contains("address1_postalcode") ? string.Empty : a["address1_postalcode"],
AccountId = !a.Contains("accountid") ? string.Empty : a["accountid"]
})
Thanks!
One of the other issues with String.Substring is that it throws if the string isn't long enough, i.e. you try to get the first 5 characters of a 4 character string. You can use a simple helper method like this:
where First(a["address1_postalcode"], 5) == lLead.ZipCode
(You might need to convert or cast a["address1_postalcode"] to string depending on what type it returns.)
public static string First(string s, int charcount)
{
if (s == null) return String.Empty;
return s.Substring(0, Math.Min(s.Length, charcount));
}

How to handle no results in LINQ?

in this example code
public Company GetCompanyById(Decimal company_id)
{
IQueryable<Company> cmps = from c in db.Companies
where c.active == true &&
c.company_id == company_id
select c;
return cmps.First();
}
How should I handle if there is no data in cmps?
cmps will never be null, so how can I check for non existing data in a LINQ Query?
so I can avoid this
'cmps.ToList()' threw an exception of type ... {System.NullReferenceException}
when transforming it into, for example, a List
GetCompanyById(1).ToList();
Do I always need to wrap it up in a try catch block?
You can use Queryable.Any() (or Enumerable.Any()) to see if there is a member in cmps. This would let you do explicit checking, and handle it however you wish.
If your goal is to just return null if there are no matches, just use FirstOrDefault instead of First in your return statement:
return cmps.FirstOrDefault();
What about applying .Any or .Count() ?
Here's an example on MSDN
List<int> numbers = new List<int> { 1, 2 };
bool hasElements = numbers.Any();
Console.WriteLine("The list {0} empty.",
hasElements ? "is not" : "is");
Or just use the ?: operator
return myExample.Any() ? myExample.First() : null;
This will return the first one if there is one, or null if there isn't:
return (from c in db.Companies
where c.active == true &&
c.company_id == company_id
select c).FirstOrDefault();
Try return cmps.Count()==0?null:cmp.First()
That way if it is null it will simply return a null Company and if its not then it will return the first one in the list.
Check out http://en.wikipedia.org/wiki/Ternary_operation
var context = new AdventureWorksLT2008Entities();
var cust = context.Customers.Where(c => c.CustomerID == 1);
if (cust.Any())
{
Customer c = cust.First();
}

Resources