How to write Linq expression using external objects - c#

I am trying to convert a loop in to a linq expression. But it seams not to work the way i am doing it:
var customer = GetCustomerFromDatabase(id);
ICollection<Order> customerOrders = null;
if (customer == null)
{
LogAndThrowCustomerNotFound(id);
}
else
{
customerOrders = customer.Orders;
}
customer.YearToDateSales = 0.0;
customer.CurrentSales = 0.0;
DateTime today = DateTime.Now;
if (customerOrders != null)
foreach (var order in customerOrders)
{
if (order.SubmittedDate != null
&& order.SubmittedDate.Value.Year.CompareTo(today.Year) == 0)
{
customer.YearToDateSales += (double)order.OrderTotal;
}
if (order.SubmittedDate != null
&& (order.SubmittedDate.Value.Month.CompareTo(today.Month) == 0
&& order.SubmittedDate.Value.Year.CompareTo(today.Year) == 0))
{
customer.CurrentSales += (double)order.OrderTotal;
}
}
So I came up with that expression to get the customer orders that match the current year... bot it does not work. in he expression order is empty and today is conflicting. I i create
DateTime today = DateTime.Now; in the parm of the expression i get different errors...
IEnumerable<Order> cOrders = customerOrders
.Where((ICollection<Order> order , today) =>
order.SubmittedDate.Value.Month == today.Month);

It's simpler if you just don't attempt pass today into the lambda, it'll be closed into the expression anyway;
customer.YearToDateSales = customerOrders
.Where(x => x.SubmittedDate != null &&
x.SubmittedDate.Value.Year == today.Year)
.Sum(x => x.OrderTotal);
customer.CurrentSales = customerOrders
.Where(x => x.SubmittedDate != null &&
x.SubmittedDate.Value.Month == today.Month &&
x.SubmittedDate.Value.Year == today.Year)
.Sum(x => x.OrderTotal);

Hard to tell exactly what's wrong without the error, but you probably need to check for null on the SubmittedDate like in the original version:
IEnumerable<Order> cOrders = customerOrders
.Where((ICollection<Order> order , today) =>
order.SubmittedDate.HasValue &&
order.SubmittedDate.Value.Month == today.Month);

Related

The cast to value type System.Boolean failed because the materialized value is null, result type's generic parameter must use a nullable type

This code was working before but now I've got this error: The cast to value type 'System.Boolean' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
public async Task<ActionResult> BankDepositVoucher(BankDepositVoucherSearchViewModel search, int? PageNo)
{
var model = new BankDepositVoucherListViewModel
{
Search = search ?? new BankDepositVoucherSearchViewModel()
};
if (search != null)
{
search.StartDate = search.StartDate.ToStartOfDay();
search.EndDate = search.EndDate.ToEndOfDay();
}
try
{
var Vouchers = DbManager.Invoices.Include(x => x.BankDepositVoucher)
.Where(x => x.Type == InvoiceType.BankDepositVoucher
&& (x.VoucherNumber == search.VoucherNo || search.VoucherNo == null)
&& (x.BankDepositVoucher.SlipNo.Contains(search.SlipNo) || search.SlipNo == null)
&& (x.BankDepositVoucher.ChequeNo.Contains(search.ChequeNo) || search.ChequeNo == null)
&& (x.BankDepositVoucher.Bank.AccountName.Contains(search.BankDetails)
|| search.BankDetails == null)
&& (x.BankDepositVoucher.AccountName.Contains(search.AccountName) || search.AccountName == null)
&& (x.BankDepositVoucher.Narration.Contains(search.Narration) || search.Narration == null)
&& (x.TotalAmount == search.Amount || search.Amount == null)
&& (x.Date >= search.StartDate || search.StartDate == null)
&& (x.Date <= search.EndDate || search.EndDate == null));
//model.Pager = new Pager(await Vouchers.CountAsync(), PageNo, 10);
model.Vouchers = await Vouchers.OrderByDescending(x => x.VoucherNumber)
//.Skip((model.Pager.CurrentPage - 1) * model.Pager.PageSize)
//.Take(model.Pager.PageSize)
.Select(x => new BankDepositVoucherBaseViewModel
{
Id = x.Id,
VoucherNumber = x.VoucherNumber,
AccountName = x.BankDepositVoucher.AccountName,
BankAccountName = x.BankDepositVoucher.Bank.AccountName,
Date = x.Date,
ChequeNo = x.BankDepositVoucher.ChequeNo,
Narration = x.BankDepositVoucher.Narration,
SlipNo = x.BankDepositVoucher.SlipNo,
TotalAmount = x.TotalAmount,
IsCleared = x.BankDepositVoucher.IsCleared
}).ToListAsync();
}
catch (Exception ex)
{
Console.WriteLine("", ex.Message);
}
return PartialView(model);
}
This is the part throwing above mentioned exception
model.Vouchers = await Vouchers.OrderByDescending(x => x.VoucherNumber)
//.Skip((model.Pager.CurrentPage - 1) * model.Pager.PageSize)
//.Take(model.Pager.PageSize)
.Select(x => new BankDepositVoucherBaseViewModel
{
Id = x.Id,
VoucherNumber = x.VoucherNumber,
AccountName = x.BankDepositVoucher.AccountName,
BankAccountName = x.BankDepositVoucher.Bank.AccountName,
Date = x.Date,
ChequeNo = x.BankDepositVoucher.ChequeNo,
Narration = x.BankDepositVoucher.Narration,
SlipNo = x.BankDepositVoucher.SlipNo,
TotalAmount = x.TotalAmount,
IsCleared = x.BankDepositVoucher.IsCleared
}).ToListAsync();
The issue is likely that when populating the view model it cannot deal with the fact that a record may not have a BankDepositVoucher.
For instance:
IsCleared = x.BankDepositVoucher.IsCleared
This should probably be:
IsCleared = x.BankDepositVoucher?.IsCleared ?? false
One other thing to improve performance considerably:
While it may look concise in the code to write statements like this:
.Where(x => x.Type == InvoiceType.BankDepositVoucher
&& (x.VoucherNumber == search.VoucherNo || search.VoucherNo == null)
&& (x.BankDepositVoucher.SlipNo.Contains(search.SlipNo) || search.SlipNo == null)
&& (x.BankDepositVoucher.ChequeNo.Contains(search.ChequeNo) || search.ChequeNo == null)
&& (x.BankDepositVoucher.Bank.AccountName.Contains(search.BankDetails)
|| search.BankDetails == null)
&& (x.BankDepositVoucher.AccountName.Contains(search.AccountName) || search.AccountName == null)
&& (x.BankDepositVoucher.Narration.Contains(search.Narration) || search.Narration == null)
&& (x.TotalAmount == search.Amount || search.Amount == null)
&& (x.Date >= search.StartDate || search.StartDate == null)
&& (x.Date <= search.EndDate || search.EndDate == null));
It is more efficient to write it out as:
.Where(x => x.Type == InvoiceType.BankDepositVoucher);
if(!string.IsNullOrEmpty(search.VoucherNo))
Voucher = Voucher.Where(x => x.VoucherNumber == search.VoucherNo);
if(!string.IsNullOrEmpty(search.SlipNo))
Voucher = Voucher.Where(x => x.BankDepositVoucher.SlipNo.Contains(search.SlipNo))
// etc.
The reason is that in the first case you are generating a much larger SQL statement to be sent to the database, and it is quite easy to "slip up" on conditions if that query is ever edited in the future. (missing parenthesis, etc.) The second example only adds conditions to the query if they are needed, keeping the resulting SQL statement much more compact.

Date range filter in linq to crm

I am using LINQ to query CRM entity. Below is my code
var data = svcContext.CreateQuery("myentity");
if (info.FromDate != null && info.ToDate != null)
{
data = data.Where(r => r.GetAttributeValue<DateTime>("rundate") >= info.FromDate.Value.Date && r.GetAttributeValue<DateTime>("rundate") <= info.ToDate.Value.Date);
}
This returns all records between FromDate and ToDate but it also consider time part while comparing which return wrong records. I want to truncate time part and use only date part for comparing. I already used code like this
1.
if (info.FromDate != null && info.ToDate != null)
{
data = data.Where(r => r.GetAttributeValue<DateTime>("rundate").Date >= info.FromDate.Value.Date && r.GetAttributeValue<DateTime>("rundate").Date <= info.ToDate.Value.Date);
}
2.
if (info.FromDate != null && info.ToDate != null)
{
data = data.Where(r => Convert.ToDateTime(r.Attributes["rundate"]).Date >= info.FromDate.Value.Date && Convert.ToDateTime(r.Attributes["rundate"]).Date <= info.ToDate.Value.Date);
}
3.
if (info.FromDate != null && info.ToDate != null)
{
data = data.Where(r => EntityFunctions.TruncateTime(r.GetAttributeValue<DateTime>("rundate")) >= info.FromDate.Value.Date && EntityFunctions.TruncateTime(r.GetAttributeValue<DateTime>("rundate")).Date <= info.ToDate.Value.Date);
}
These all returns "Invalid 'where' condition. An entity member is invoking an invalid property or method." error. So how to compare by only date part and truncate time part. Thank you..
If the actual times do not matter, why don't you query your records by
var data = svcContext.CreateQuery("myentity");
if (info.FromDate != null && info.ToDate != null)
{
var compareFromDate = info.FromDate.Value.Date;
var compareToDate = info.ToDate.Value.Date.AddDays(1);
data = data.Where(r => r.GetAttributeValue<DateTime>("rundate") >= compareFromDate && r.GetAttributeValue<DateTime>("rundate") < compareToDate);
}
The CRM LINQ provider has some limitations that LINQ to Objects does not.
Adding ToList() to the CreateQuery result converts to using LINQ to Objects, which should allow you to do what you want.
For the comparisons you're looking to do, coverting to OLE Automation's decimal date format (OADate), then casting to int gives you a serial number for the date without any time component.
And, for clarity I moved the predicate to a separate method.
public override void Run()
{
using (var context = new Microsoft.Xrm.Sdk.Client.OrganizationServiceContext(svc))
{
var result = (from e in context.CreateQuery("account").ToList()
.Where(ne => isBetween(ne.GetAttributeValue<DateTime>("createdon"), DateTime.MinValue, DateTime.Now))
select e).ToList();
}
}
private bool isBetween(DateTime value, DateTime min, DateTime max)
{
var val = (int)value.ToOADate();
return val >= (int)min.ToOADate() || val <= (int)max.ToOADate();
}

IQueryable with only optional parameters

I currently have the following method:
public List<Order> GetOrders(int profileId, string timeSpan, string workOrd, string partNo, bool includeDeleted)
{
DateTime startDate = DateTime.Now;
DateTime endDate = DateTime.Now;
string[] times = (!string.IsNullOrWhiteSpace(timeSpan)) ? timeSpan.Trim().Split('-') : new string[] { "", "" };
if (!string.IsNullOrWhiteSpace(times[0]) && !string.IsNullOrWhiteSpace(times[0]))
{
startDate = DateTime.Parse(times[0]).Date;
endDate = DateTime.Parse(times[1]).Date;
}
//New Real Query
IQueryable<Order_Travel> otQuery = _context.Order_Travels.Where(x =>
(profileId != 0 || x.Profile.ProfileID == profileId)
&& ((timeSpan == null || timeSpan.Trim() == "") || ((DbFunctions.TruncateTime(x.TimeRecieved) >= startDate)
&& (DbFunctions.TruncateTime(x.TimeRecieved) <= endDate)))
&& ((workOrd == null || workOrd.Trim() == "") || x.Order.WorkOrdNo == workOrd)
&& ((partNo == null ||partNo.Trim() == "") || x.Order.PartNo == partNo)
&& (!includeDeleted || x.Aborted == true));
//The results is now in order_travel. Under here binding them to a list of orders with only the respective orderTravels included.
List<Order> orders = new List<Order>();
List<Order_Travel> ots = otQuery.ToList();
foreach (Order_Travel ot in ots)
{
var OrderInList = orders.FirstOrDefault(X => X == ot.Order);
if (OrderInList == null)
{
orders.Add(ot.Order);
OrderInList = orders.FirstOrDefault(X => X == ot.Order);
OrderInList.OrderTravels.Clear();
OrderInList.OrderTravels.Add(ot);
}
else
{
OrderInList.OrderTravels.Add(ot);
}
}
return orders;
}
What I need it to do, is (as I've attempted) to make a call, finding all Order_Travel objects that match the paramters sent to it. If some (or all) are left blank, it takes everything, regardless of the values.
The code right now, does not return anything, if a blank search is made (a search that does not have any parameters), and I can not see what could be the issue. I have tried debugging it, but with no luck.
Any help would be greatly appreciated!
Thanks!
Filter one option at a time, instead of trying to put everything into a single expression:
IQueryable<T> query = all; // start with everything
if (IsPresent(option1))
{
query = query.Where(t => t.XXX == option1);
}
Example
IQueryable<Order_Travel> otQuery = _context.Order_Travels;
if (profileId != 0)
{
otQuery = otQuery.Where(x => x.Profile.ProfileID == profileId);
}
if (timeSpan != null && timeSpan.Trim() != "")
{
otQuery = otQuery.Where(x => DbFunctions.TruncateTime(x.TimeRecieved) >= startDate &&
DbFunctions.TruncateTime(x.TimeRecieved) <= endDate);
}
You will also find this easier to maintain than one huge expression.
Probably this part is your problem:
(profileId != 0 || x.Profile.ProfileID == profileId)
It should be
(profileId == 0 || x.Profile.ProfileID == profileId)
If your profile ID is 0, it will only find entries with x.Profile.ProfileID being 0. Probably there are no such entries.

Lambda Expression for Not In a Collection

I have 3 tables
eTrip
Country | eProfile
eProfile
Documents (collection of Document)
Documents
Type | ExpiryDate | Country
I am trying to get a collection of eTrips in a search api. There are several conditions that need to be met for an eTrip.
Each employee can hold multiple documents (visas, passports, etc). For an eTrip to be valid we need to make sure the eTrip.Country != the country of a valid passport (future expiry date) document.
How can we write a lambda expression to accomplish this?
The code that I have so far is something like this
var query = context.eTrip.AsQueryable();
query = query.Where(e => e.validTrip == true);
var docs = query.Select(e => e.eProfile.Documents);
foreach (Documents d in docs)
{
if (d.DocumentTypeCode == "Passport" && d.ExpiryDate != null && d.ExpiryDate > DateTime.Now)
{
query = query.Where(e => e.Country != d.Country);
}
}
I need to write the filter for the country now and I am not sure how we can do it for a collection.
you can extend your Where clause with an Any subquery on Documents
query = query.Where(e => e.validTrip == true && e.eProfile.Documents.Any(a=>a.DocumentTypeCode == "Passport" && a.ExpiryDate.HasValue && a.ExpiryDate.Value > DateTime.Now && e.Country!=d.Country));
Try that:
// your part
var query = context.eTrip.AsQueryable();
query = query.Where(e => e.validTrip == true);
var docs = query.Select(e => e.eProfile.Documents);
// get countries for which there are no documents with future date
var myCountryQuery = query.Where(x => !docs
.Where(d => d.DocumentTypeCode == "Passport" && d.ExpiryDate != null && d.ExpiryDate > DateTime.Now)
.Any(d => d.Country != x.Country)
);

Comma separated List in linq select

I have Table HR_Travel(TravelID, TravelCode....) and HR_TravelDocuments(TravelDocID, TravelID, DocUrl)
FromDate = FromDate.AddDays(1);
ToDate = ToDate.AddDays(1);
List<dynamic> Lst = new List<dynamic>();
var queryTravelDetails = from t in db.HR_TravelDetails
where ((t.StatusDate >= FromDate && t.StatusDate <= ToDate)
&& (t.EmpID == EmpID || EmpID == 0) && (t.TravelStatus == TravelStatus || TravelStatus == "All"))
orderby t.StatusDate descending
select new
{
TravelID = t.TravelID,
TravelSubID = db.HR_TravelDetails.Where(i => i.TravelID == 0).FirstOrDefault().TravelID == null ? 0 : db.HR_TravelDetails.Where(i => i.TravelID == 0).FirstOrDefault().TravelID,
t.TravelCode,
t.EmpID,
EmpName = db.EE_Employee.Where(i => i.EmpID == t.EmpID).FirstOrDefault().EmpName,
t.CellNo,
t.BoardingForm,
t.DestinationTO,
t.JournyDate,
t.Purpose,
t.Organization,
t.TravelStatus,
DocUrl = DocUrl = string.Join(",",( db.HR_TravelDocuments.Where(i => i.TravelID == t.TravelID && i.TravelSubID == 0).Select(i => i.DocUrl).ToList()))
};
foreach (var element in queryTravelDetails)
{
Lst.Add(element);
}
Gives the following error:
LINQ to Entities does not recognize the method 'System.String Join[String](System.String, System.Collections.Generic.IEnumerable`1[System.String])' method, and this method cannot be translated into a store expression.
The Join() method can't be used in LINQ expressions, because it cannot translate from CLR to T-SQL. Another example is that you can't use:
var itemCount = db.Table.Count(p => p.Something == SomeFunction(someVariable));
You should move the method call outside the LINQ statement:
var anotherVariable = SomeFunction(someVariable);
var itemCount = db.Table.Count(p => p.Something == anotherVariable);
I hope I explained in a good way.
EDIT: As seen in the comments before, you can also use ToArray() and when the data is loaded locally you can use functions in statements freely.

Categories

Resources