microchef
microchef

Reputation: 89

How to fix FirstOrDefault returning Null in Linq

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'

Upvotes: 0

Views: 6999

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205859

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.

Upvotes: 1

Harald Coppoolse
Harald Coppoolse

Reputation: 30512

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.

Upvotes: 1

Related Questions