How to fix FirstOrDefault returning Null in Linq - entity-framework

My Linq Query keeps returning the null error on FirstOrDefault
The cast to value type 'System.Int32' failed because the materialized value is null
because it can't find any records to match on the ClinicalAssetID form the ClinicalReading Table, fair enough!
But I want the fields in my details page just to appear blank if the table does not have matching entry.
But how can I handle the null issue when using the order by function ?
Current Code:
var ClinicalASSPATINCVM = (from s in db.ClinicalAssets
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subASSPAT in AP.DefaultIfEmpty()
join ci in db.ClinicalINSs on s.ClinicalAssetID equals ci.ClinicalAssetID into AI
from subASSINC in AI.DefaultIfEmpty()
join co in db.ClinicalReadings on s.ClinicalAssetID equals co.ClinicalAssetID into AR
let subASSRED = AR.OrderByDescending(subASSRED => subASSRED.MeterReadingDone).FirstOrDefault()
select new ClinicalASSPATINCVM
{
ClinicalAssetID = s.ClinicalAssetID,
AssetTypeName = s.AssetTypeName,
ProductName = s.ProductName,
ModelName = s.ModelName,
SupplierName = s.SupplierName,
ManufacturerName = s.ManufacturerName,
SerialNo = s.SerialNo,
PurchaseDate = s.PurchaseDate,
PoNo = s.PoNo,
Costing = s.Costing,
TeamName = s.TeamName,
StaffName = s.StaffName,
WarrantyEndDate = subASSPAT.WarrantyEndDate,
InspectionDate = subASSPAT.InspectionDate,
InspectionOutcomeResult = subASSPAT.InspectionOutcomeResult,
InspectionDocumnets = subASSPAT.InspectionDocumnets,
LastTypeofInspection = subASSINC.LastTypeofInspection,
NextInspectionDate = subASSINC.NextInspectionDate,
NextInspectionType = subASSINC.NextInspectionType,
MeterReadingDone = subASSRED.MeterReadingDone,
MeterReadingDue = subASSRED.MeterReadingDue,
MeterReading = subASSRED.MeterReading,
MeterUnitsUsed = subASSRED.MeterUnitsUsed,
FilterReplaced = subASSRED.FilterReplaced
}).FirstOrDefault(x => x.ClinicalAssetID == id);
Tried this but doesn't work
.DefaultIfEmpty(new ClinicalASSPATINCVM())
.FirstOrDefault()
Error was:
CS1929 'IOrderedEnumerable<ClinicalReading>' does not contain a definition for 'DefaultIfEmpty' and the best extension method overload 'Queryable.DefaultIfEmpty<ClinicalASSPATINCVM>(IQueryable<ClinicalASSPATINCVM>, ClinicalASSPATINCVM)' requires a receiver of type 'IQueryable<ClinicalASSPATINCVM>'
Feel a little closer with this but still errors
let subASSRED = AR.OrderByDescending(subASSRED => (subASSRED.MeterReadingDone != null) ? subASSRED.MeterReadingDone : String.Empty).FirstOrDefault()
Error:
CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'System.DateTime?' and 'string'

The original error means that some of the following properties of the ClinicalASSPATINCVM class - MeterReadingDone, MeterReadingDue, MeterReading, MeterUnitsUsed, or FilterReplaced is of type int.
Remember that subASSRED here
let subASSRED = AR.OrderByDescending(subASSRED => subASSRED.MeterReadingDone).FirstOrDefault()
might be null (no corresponding record).
Now look at this part of the projection:
MeterReadingDone = subASSRED.MeterReadingDone,
MeterReadingDue = subASSRED.MeterReadingDue,
MeterReading = subASSRED.MeterReading,
MeterUnitsUsed = subASSRED.MeterUnitsUsed,
FilterReplaced = subASSRED.FilterReplaced
If that was LINQ to Objects, all these would generate NRE (Null Reference Exception) at runtime. In LINQ to Entities this is converted and executed as SQL. SQL has no issues with expression like subASSRED.SomeProperty because SQL supports NULL naturally even if SomeProperty normally does not allow NULL. So the SQL query executes normally, but now EF must materialize the result into objects, and the C# object property is not nullable, hence the error in question.
To solve it, find the int property(es) and use the following pattern inside query:
SomeIntProperty = (int?)subASSRED.SomeIntProperty ?? 0 // or other meaningful default
or change receiving object property type to int? and leave the original query as is.
Do the same for any non nullable type property, e.g. DateTime, double, decimal, Guid etc.

You're problem is because your DefaultIfEmpty is executed AsQueryable. Perform it AsEnumerable and it will work:
// create the default element only once!
static readonly ClinicalAssPatInVcm defaultElement = new ClinicalAssPatInVcm ();
var result = <my big linq query>
.Where(x => x.ClinicalAssetID == id)
.AsEnumerable()
.DefaultIfEmpty(defaultElement)
.FirstOrDefault();
This won't lead to a performance penalty!
Database management systems are extremely optimized for selecting data. One of the slower parts of a database query is the transport of the selected data to your local process. Hence it is wise to let the DBMS do most of the selecting, and only after you know that you only have the data that you really plan to use, move the data to your local process.
In your case, you need at utmost one element from your DBMS, and if there is nothing, you want to use a default object instead.
AsQueryable will move the selected data to your local process in a smart way, probably per "page" of selected data.
The page size is a good compromise: not too small, so you don't have to ask for the next page too often; not too large, so that you don't transfer much more items than you actually use.
Besides, because of the Where statement you expect at utmost one element anyway. So that a full "page" is fetched is no problem, the page will contain only one element.
After the page is fetched, DefaultIfEmpty checks if the page is empty, and if so, returns a sequence containing the defaultElement. If not, it returns the complete page.
After the DefaultIfEmpty you only take the first element, which is what you want.

Related

Trying to update Values in Standard Object from metaData

I am using Trigger isBefore
In System.debug(opp.get(metaData.get(0).Opportunity_Field_Name__c), it is showing correct Values but not Updating in Opportunity Object
Below is Trigger and its Apex Class Trigger
Trigger
trigger MetadataObjectFieldMapping on Opportunity (before insert, before update)
{
if(Trigger.isInsert || Trigger.isUpdate )
{
MetadataObjectFieldMappingHandler oppHandler = new MetadataObjectFieldMappingHandler();
oppHandler.Show(Trigger.new);
}
}
And Apex Class
public class MetadataObjectFieldMappingHandler {
List<String> strAccField = new List<String>();
//Getting List of MetaData Values
List<Object_Field_Mapping__mdt> metaData = new List<Object_Field_Mapping__mdt>
([SELECT Account_Field_Name__c,
Opportunity_Field_Name__c
FROM Object_Field_Mapping__mdt]);
//Function to check if Field Name Exists in Object or not
public Boolean hello(String objName, String fieldName)
{
Boolean temp = False;
//Creating Schema to get all fields from Account and Opportunity Object
Map<String, Schema.SObjectField> accFields = Schema.getGlobalDescribe().get(objName).getDescribe().fields.getMap();
for(Schema.SObjectField field : accFields.values())
{
strAccField.add(field+'');
}
//Calling Account and Opportunity Object in fieldName
if(strAccField.contains(fieldName)){
System.debug('PASS '+fieldName);
temp = true;
}
return temp;
}
public void Show(List<opportunity> newOppList)
{
Boolean test1 = hello('Account',metaData.get(0).Account_Field_Name__c);
Boolean test2 = hello('Opportunity',metaData.get(0).Opportunity_Field_Name__c);
//If both Field Value exists
if(test1 && test2){
//Getting value from Opp using dynamic Query
String query = 'Select Account.'+metaData.get(0).Account_Field_Name__c+', '+metaData.get(0).Opportunity_Field_Name__c+' from Opportunity where Id IN : newOppList ';
List<Opportunity> oppList =database.query(query);
for(Opportunity opp : oppList){
opp.put(
metaData.get(0).Opportunity_Field_Name__c,
opp.Account.get(metaData.get(0).Account_Field_Name__c)
);
System.debug(opp.get(metaData.get(0).Opportunity_Field_Name__c));
}
}
}
Can you please tell me, why Value is not Updating in Opportunity Object while it showing in Debug Logs..?
Multiple fails here I think.
Apex is case-insensitive when you do if('a' == 'A'). But when comparing Strings in collections (Lists, Sets, Map keys) it suddenly becomes case-sensitive.
List<String> fields = new List<String>{'Id', 'Name'};
System.debug(fields.contains('name')); // false
(this should have been a Set<String> by the way, for performance and logical readability). So I suspect something's fishy there, in case. You didn't show your metadata but check this one (you have only 1 row now, right? If you have more than one - we'll your metaData.get(0) essentially returns a random row).
I don't like the cast from Schema.SObjectField to String either.
Next: String query = 'Select Account.'+metaData.get(0).Account_Field_Name__c+', '+metaData.get(0).Opportunity_Field_Name__c+' from Opportunity where Id IN : newOppList ';
This has a chance of working in before update. But for sure not in before insert. Nothing's in database yet, your query will return zero results. You have to loop through trigger.new, collec AccountIds, query Accounts (directly, not via Opportunity table) and then make final loop that writes data.
You passed newOppList to Show(). If you want to get the save to database for free - you should modify values on the original, on newOppList. Instead you modify the in-memory results of query (oppList). Nothing will happen to them, they'll be discarded. If you want to save them, you'd have to do it manually (but then you risk entering a loop of update triggers and SF will stop you).
You sure this has to be code? Sounds like a job for workflow or process builder. Or make them formula fields so you always display fresh value instead of such copying... When something changes on Account it won't automatically cascade down to all opps unless you make next trigger/process builder...

LINQ to SQL compare nullable in subquery

I fail to translate a sql query to a linq query that could calculate some stock.
This is my test query that I'm trying to convert to a linq query.
SELECT
i.*,
(SELECT COUNT(t.*) FROM tickets t
WHERE t.starttime::time = i.sessionstarttime::time
AND t.starttime::date = '2018-04-06'::date)
as stock
FROM items I
-- note that the hardcoded date ('2018-04-06') is a function parameter
( tl;dr; how would you convert this PostgreSQL query to LINQ? )
My attempts so far are the variations of the following query:
var items = await _context.Items.Select(x => new Item
{
Id = x.Id,
IsTicket = x.IsTicket,
Name = x.Name,
Price = x.Price,
SaleItems = x.SaleItems,
SessionStartTime = x.SessionStartTime,
DateCreated = x.DateCreated,
DateEdit = x.DateEdit,
UserIdCreated = x.UserIdCreated,
UserIdEdited = x.UserIdEdited,
// calculate stock in subquery
Stock = _context.Tickets.Count(
t => t.StartTime.Date == ticketDate
&& x.SessionStartTime.HasValue
&& t.StartTime.Hour == x.SessionStartTime.Value.Hours // this is the part that is failing
&& t.State != TicketState.Canceled)
}).ToListAsync();
t.StartTime is Datetime and x.SessionStartTime is Nullable Timespan
So when I comment the line && t.StartTime.Hour == x.SessionStartTime.Value.Hours everything is fine, but with it I get warnings that it could not be translated and will be evaluated locally. But I don't want to download the whole ticket table just to count them.
The t.StartTime.Hour part is fine, I tried to perform static comparisons with both parameters. t.StartTime.Hour == 5 was translated without any problems, but x.SessionStartTime.Value.Hours == 5 failed to translate.
Also the problematic part in the application output:
([t].StartTime.Hour == Convert([x].SessionStartTime, TimeSpan).Hours))
So I guess that convert part is failing.
So what I'm missing and how I could work around this problem. Any help will be appreciated.
Update:
After experimenting a bit I have found two workarounds, that I wouldn't call the answers.
First I noticed that EF is trying to convert Nullable<TimeSpan> to a regular TimeSpan from the mentioned output: ([t].StartTime.Hour == Convert([x].SessionStartTime, TimeSpan).Hours))
I thought I could prevent that conversion by converting to a string and comparing the strings (I have a feeling this will bite me in the future):
t.StartTime.ToString().Contains(x.SessionStartTime.ToString())
The second workaround is only viable for my scenario since I know the items query is final and I can materialise it without calculated Stock, and then loop through the results and calculate it on a separate query. But this seems to add additional calls to the database and sacrifice some performance.
foreach(var x in items.Where(x=>x.SessionStartTime.HasValue))
{
// accessing the t.StartTime.TimeOfDay property seems to fail the LINQ to SQL as well
var hours = x.SessionStartTime.Value.Hours;
var minutes = x.SessionStartTime.Value.Minutes;
x.Stock = _context.Tickets.Count(t => t.StartTime.Date == ticketDate
&& t.StartTime.Hour == hours
&& t.StartTime.Minute == minutes);
}

Querying average with a function of column in Rails

I'm using Rails 4 in a web app, Postgresql database and squeel gem for queries.
I have this function in my model statistic.rb
def properties_mean_ppm(mode, rooms, type, output_currency_id)
sql_result = properties(mode, rooms, type).select{
avg(price_dolar / property_area).as(prom)
}
avg = sql_result[0].prom
final_avg = change_currency(avg, DOLAR_ID, output_currency_id)
return final_avg.to_f
end
price_dolar and property_area are columns in the properties table.
It works fine in Rails console and displays the result, but when I use it on the controller it gives an error:
ActiveModel::MissingAttributeError (missing attribute: id)
And indicates the line
avg = sql_result.to_a[0].prom
I also tried using sql_result[0].prom or sql_result.take or sql_result.first, they all have the same error.
The sql_result is this:
#<ActiveRecord::Relation [#<Property >]>
This is the action called in the controller
def properties_mean_ppm
#statistic = Statistic.find(params[:id])
mode = params[:mode] ? params[:mode] : ANY_MODE
type = params[:type] ? params[:type] : ANY_TYPE
one_room = #statistic.properties_mean_ppm(mode, 1, type, UF)
end
I know how to get the result using only SQL without activerecord but that would be very inefficient for me because I have lots of filters called before in the properties() function
Seems like calling that from the properties object made the controller to expect a Property with an id as a result. So I made it work without squeel.
sql_result = properties(mode, rooms, type).select(
"avg(precio_dolar / dimension_propiedad) as prom, 1 as id"
)
And giving a fixed id to the result

How to read UnitPrice from invoice line in QBO API v3 .NET

The bizarre properties in the .NET SDK continue to baffle me. How do I read the UnitPrice from an invoice line?
If I do this:
sild = (SalesItemLineDetail)line.AnyIntuitObject;
ln = new QBInvoiceLine(); // My internal line item class
ln.Description = line.Description;
ln.ItemRef = new QBRef() { Id = sild.ItemRef.Value, Name = sild.ItemRef.name };
if (sild.QtySpecified)
ln.Quantity = sild.Qty;
else
ln.Quantity = 0;
if (sild.ItemElementName == ItemChoiceType.UnitPrice)
ln.Rate = (decimal)sild.AnyIntuitObject; // Exception thrown here
The last line throws an invalid cast exception, even though the debugger shows that the value is 20. I've tried other types but get the same exception no matter what I do. So I finally punted and am calculating the rate like so:
ln.Rate = line.Amount / ln.Quantity;
(With proper rounding and checking for divide by zero, of course)
While we're on the subject... I noticed that in many cases ItemElementName == ItemChoiceType.PriceLevelRef. What's up with that? As far as I know, QBO doesn't support price levels, and I certainly wasn't using a price level with this invoice or customer. In this case I was also able to get what I needed from the Amount property.
Try this-
SalesItemLineDetail a1 = (SalesItemLineDetail)invoice11.Line[0].AnyIntuitObject;
object unitprice = a1.AnyIntuitObject;
decimal quantity = a1.Qty;
PriceLevelRef as an 'entity' is not supported. This means CRUD operations are not supported on this entity.
The service might however be returning readonly values in the transactions sometimes, but since this not mentioned in the docs, please consider it as unsupported.
Check that both request/response are in either json or xml format-
You can use the following code to set that-
ServiceContext context = new ServiceContext(appToken, realmId, intuitServiceType, reqvalidator);
context.IppConfiguration.Message.Request.SerializationFormat = Intuit.Ipp.Core.Configuration.SerializationFormat.Json;
context.IppConfiguration.Message.Response.SerializationFormat = Intuit.Ipp.Core.Configuration.SerializationFormat.Json;
Also, in QBO UI, check if Company->sales settings has Track Quantity and Price/rate turned on.

Linq to Entities does not recognize the method System.DateTime.. and cannot translate this into a store expression

I have a problem that has taken me weeks to resolve and I have not been able to.
I have a class where I have two methods. The following is supposed to take the latest date from database. That date represents the latest payment that a customer has done to "something":
public DateTime getLatestPaymentDate(int? idCustomer)
{
DateTime lastDate;
lastDate = (from fp in ge.Payments
from cst in ge.Customers
from brs in ge.Records.AsEnumerable()
where (cst.idCustomer == brs.idCustomer && brs.idHardBox == fp.idHardbox
&& cst.idCustomer == idCustomer)
select fp.datePayment).AsEnumerable().Max();
return lastDate;
}//getLatestPaymentDate
And here I have the other method, which is supposed to call the previous one to complete a Linq query and pass it to a Crystal Report:
//Linq query to retrieve all those customers'data who have not paid their safebox(es) annuity in the last year.
public List<ReportObject> GetPendingPayers()
{
List<ReportObject> defaulterCustomers;
defaulterCustomers = (from c in ge.Customer
from br in ge.Records
from p in ge.Payments
where (c.idCustomer == br.idCustomer
&& br.idHardBox == p.idHardBox)
select new ReportObject
{
CustomerId = c.idCustomer,
CustomerName = c.nameCustomer,
HardBoxDateRecord = br.idHardRecord,
PaymentDate = getLatestPaymentDate(c.idCustomer),
}).Distinct().ToList();
}//GetPendingPayers
No compile error is thrown here, but when I run the application and the second method tries to call the first one in the field PaymentDate the error mentioned in the header occurs:
Linq to Entities does not recognize the method System.DateTime.. and cannot translate this into a store expression
Please anybody with an useful input that put me off from this messy error? Any help will be appreciated !
Thanks a lot !
Have a look at these other questions :
LINQ to Entities does not recognize the method
LINQ to Entities does not recognize the method 'System.DateTime Parse(System.String)' method
Basically, you cannot use a value on the C# side and translate it into SQL. The first question offers a more thorough explanation ; the second offers a simple solution to your problem.
EDIT :
Simply put : the EF is asking the SQL server to perform the getLatestPaymentDate method, which it has no clue about. You need to execute it on the program side.
Simply perform your query first, put the results into a list and then do your Select on the in-memory list :
List<ReportObject> defaulterCustomers;
var queryResult = (from c in ge.Customer
from br in ge.Records
from p in ge.Payments
where (c.idCustomer == br.idCustomer
&& br.idHardBox == p.idHardBox)).Distinct().ToList();
defaulterCustomers = from r in queryResult
select new ReportObject
{
CustomerId = r.idCustomer,
CustomerName = r.nameCustomer,
HardBoxDateRecord = r.idHardRecord,
PaymentDate = getLatestPaymentDate(r.idCustomer),
}).Distinct().ToList();
I don't have access to your code, obviously, so try it out and tell me if it works for you!
You'll end up with an in-memory list