Entity Framework | Include -> Select Empty Field - c#

I have a problem with our Select.
But first, we have 3 tables:
for example:
inventory,
items,
device_properties
The inventory have Items. And a few of the Items have device_properties.
The items and the device_properties doesnt change. In this case we include fix the device_properties to the items.
Now at the call we want to get the inventory, and include at the request an include from items to the inventory entries.
Now the problem is, if we request it, we send data like x.items.device_properties.name
But this can be null. And if this is null it doesnt work because:
nullable object must have a value
and if i do like the following, he needs to long:
deviceName = x.items.device_properties == null ? "" : x.items.device_properties.name
var items = db.inventory.Where(x => x.itemName== itemName).Include(i => i.items).Select(x => new
{
itemId = x.id,
x.itemName,
deviceName = x.items.device_properties.name
}).AsNoTracking().ToList();
At this i get the message, that the nullable needs a value.
And if i do it like:
var items = db.inventory.Where(x => x.itemName== itemName).Include(i => i.items).Select(x => new
{
itemId = x.id,
x.itemName,
deviceName = x.items.device_properties == null ? "" : x.items.device_properties.name
}).AsNoTracking().ToList();
The query needs a lot longer
message
System.InvalidOperationException: Nullable object must have a value

In addition device_properties should not be null, you should check items property should not be null.
var items = db.inventory.Where(x => x.itemName== itemName).Include(i => i.items).Select(x => new
{
itemId = x.id,
x.itemName,
deviceName = x.items == null || x.items.device_properties == null ? "" : x.items.device_properties.name
}).AsNoTracking().ToList();

Related

How to use the let clause in C#

The code below works perfectly in LINQPad, but when I implement it in Visual Studio it doesn't pull out any data. When I comment the let clause, and the cycles (1 to 5), I get the all the other data (Pin ...Notes). Can someone let me know how to implement the let clause in C#?
public List<RouteStatus> Route_AB_List(int yardId, int siteTypeId)
{
using (var context = new COESystemContext())
{
var RouteList = from site in context.Sites
where site.YardID == yardId && site.SiteTypeID == siteTypeId && site.Season.SeasonYear == DateTime.Now.Year
orderby site.Community.Name ascending
let Cycles = site.JobCards
.Where(job => job.OperationID == 1)
.OrderByDescending(job => job.ClosedDate.HasValue)
.ThenBy(job => job.ClosedDate)
.Select(job => new { Date = job.ClosedDate })
select new RouteStatus
{
Pin = site.Pin,
Community = site.Community.Name,
Neighbourhood = site.Neighbourhood,
Address = site.StreetAddress,
Area = site.Area,
Notes = site.Notes,
Cycle1 = Cycles.FirstOrDefault().Date,
Cycle2 = Cycles.FirstOrDefault().Equals(null) ? (DateTime?)null : Cycles.Skip(1).FirstOrDefault().Date,
Cycle3 = Cycles.Skip(2).FirstOrDefault().Equals(null) ? (DateTime?)null : Cycles.Skip(2).FirstOrDefault().Date,
Cycle4 = Cycles.Skip(3).FirstOrDefault().Equals(null) ? (DateTime?)null : Cycles.Skip(3).FirstOrDefault().Date,
Cycle5 = Cycles.Skip(4).FirstOrDefault().Equals(null) ? (DateTime?)null : Cycles.Skip(4).FirstOrDefault().Date
};
return RouteList.ToList();
}
}
It seems to me that you do the select-where-order your cycles once for every Cycle1 to Cycle5.
Besides you have to have do something difficult if the result of FirstOrDefault is null.
My advice would be to use more Selects to optimize your code.
In smaller steps:
var selectedSitesAndCycles = dbContext.Sites
// keep only those sites that ...
.Where(site => site.YardID == yardId
&& site.SiteTypeID == siteTypeId
&& site.Season.SeasonYear == DateTime.Now.Year)
// order the remaining sites in ascending order by name
.OrderBy(site => site.Community.Name)
// from every ordered site, get some properties and a list of 5 cycles:
.Select(site => new
{
Pin = site.Pin,
Community = site.Community.Name,
Neighbourhood = site.Neighbourhood,
Address = site.StreetAddress,
Area = site.Area,
Notes = site.Notes,
ClosedDates = site.JobCards
.Where(job => job.OperationID == 1)
.OrderByDescending(job => job.ClosedDate.HasValue)
.ThenBy(job => job.ClosedDate)
.Select(job => Date = job.ClosedDate)
.Take(5)
.ToList(),
});
Note the the query is not executed yet. I only take 5 cycles. I didn't use the keyword new when selecting the ClosedDate. Therefore CycleClosedDates is a List<DateTime?>.
In other words: every element of CycleClosedDates is a nullable DateTime. If you take FirstOrDefault, you get either the first nullable DateTime, which might be a DateTime or null, or you get a (DateTime?)null if there are not enough CycleClosedDates.
Let's examine the case where a site has only three JobCards:
JobCard[0] is closed and has ClosedDate 2020-03-10
JobCard[1] is not closed yet. ClosedDate isn (DateTime?)null
JobCard[3] is closed and has ClosedDate 2020-03-20
// There is no JobCard[4] [5]
The result is a List<DateTime?>, of length 3, where element [1] has no value. The result of Skip(1).FirstOrDefault() will be a nullable DateTime without value.
The nice thing is that Skip(4).FirstOrDefault() will also be a nullable DateTime without value, even though there are not 5 JobCards
Let's continue with an extra Select to create your five properties:
.Select(site => new
{
Pin = site.Pin,
Community = site.Community.Name,
Neighbourhood = site.Neighbourhood,
Address = site.StreetAddress,
Area = site.Area,
Notes = site.Notes,
Cycle1 = site.CycleClosedDates.FirstOrDefault(),
Cycle2 = site.CycleClosedDates.Skip(1).FirstOrDefault(),
Cycle3 = site.CycleClosedDates.Skip(2).FirstOrDefault(),
...
})
Note that the CycleClosedDates will be ordered only once. Because CycleClosedDates is already a List<DateTime?> it seems a bit superfluous to create separate properties instead of one list with length five. Consider in your second select
.Select(site => new
{
Pin = site.Pin,
Community = site.Community.Name,
...
CycleClosedDates = new List[]
{
site.CycleClosedDates.FirstOrDefault(),
site.CycleClosedDates.Skip(1).FirstOrDefault(),
site.CycleClosedDates.Skip(2).FirstOrDefault(),
...
},
};
Or after the first select:
// move the selected data to local process
.AsEnumerable()
// 2nd select:
.Select(site => new
{
Pin = site.Pin,
Community = site.Community.Name,
...
CycleClosedDates = site.CycleClosedDates
.Concat(Enumerable.Repeat( (DateTime?)null, 5)
.Take(5)
.ToList();
This way you are certain that your CycleClosedDates has exactly five nullable DateTimes, even if there were no JobCards at all.

Null exception in LINQ statement, but returns results in LINQPAD

I have a LINQ statement below which should return 110 records back to my view. When I checked SQL Server, my table has records in it, but when I try to retrieve those records based on an Id and do #foreach(var item in Model) its causing a null reference error. Any help would be appreciated.
LINQPAD --Returns 110 records (RPT is the schema)
RPT.Team_History.OrderByDescending (r => r.DateOfAction).Where(r => r.MgrID ==212)
Controller Code (DB First approach used. RPT schema dropped when VS generated models)
public PartialViewResult _TeamTransitionDisplay()
{
var mgrID = 212;
IEnumerable<TeamHistoryViewModel> teamHistory;
teamHistory = db.Team_History
.Where(x => x.MgrID == #mgrID)
.Select(x => new TeamHistoryViewModel
{
DateOfAction = x.ActionDate,
DeptID = x.DeptID,
Operation = x.Operation,
StartDate = x.StartDate,
AStartDate = x.AStartDate,
FStartDate = x.FStartDate
}).ToList();
return PartialView("_TeamDisplay", teamHistory);
}
[NullReferenceException: Object reference not set to an instance of an object.]
Advice would be appreciated.
I think, When it is trying to get one of your model values, it might be null, so it is throwing the error
You can try this
for nullable properties add checking if this is null, only for nullable properties, i added for all because i don't know which are nullable
teamHistory = db.Team_History
.Where(x => x.MgrID == #mgrID)
.Select(x => new TeamHistoryViewModel
{
DateOfAction = (x.ActionDate != null ? x.ActionDate : something here when null),
DeptID = (x.DeptID!= null ? x.DeptID: 0),
Operation = (x.Operation!= null ? x.Operation: "operation"),
StartDate = (x.StartDate!= null ? x.StartDate: DateTime.MinValue),
AStartDate = (x.AStartDate != null ? x.AStartDate : DateTime.MinValue),
FStartDate = (x.FStartDate != null ? x.FStartDate : DateTime.MinValue)
}).ToList();

Combine list values based on column C#

I am trying to bind two list values into a single list based on the values of the column.
below is the list am getting it.
TestCaseName Screen IEStatus IEPath FFStatus FFPath GCStatus GCPath
--------------------------------------------------------------------------------------------
TC001 Yes Pass C:\\a.jpg null null null null
TC002 Yes Pass C:\\b.jpg null null null null
TC001 Yes null null Pass C:\\c.jpg null null
TC002 Yes null null Pass C:\\d.jpg null null
But I want to combine the values and display as below
TestCaseName Screen IEStatus IEPath FFStatus FFPath GCStatus GCPath
--------------------------------------------------------------------------------------------
TC001 Yes Pass C:\\a.jpg Pass C:\\c.jpg null null
TC002 Yes Pass C:\\b.jpg Pass C:\\d.jpg null null
Below is my code
List<ResultProperties> lstCommon = new List<ResultProperties>();
List<ResultProperties> lstNew= new List<ResultProperties>();
var checked_boxes = this.Controls.OfType<CheckBox>().Where(c => c.Checked);
foreach (CheckBox cbx in checked_boxes)
{
Program obj = new Program();
lstNew = obj.PerformTest(cbx.Text, textBox1.Text);
foreach (ResultProperties item in lstNew)
{
lstCommon.Add(item);
}
}
Any suggestions will be greatly helpful..
Rather than doing it in a single Linq expression, I would probably do something list this, so as to avoid multiple iterations over the source data:
List<ResultProperties> a = LoadSourceList() ;
IEnumerable<IGrouping<string,ResultProperties> groups =
a
.GroupBy( x => x.TestCaseName , StringComparer.OrdinalIgnoreCase )
;
List<ResultProperties> coalesced = new List<ResultProperties>() ;
foreach( IGrouping<string,ResultProperties> group in groups )
{
ResultProperties item = null ;
foreach( ResultProperty rp in group )
{
item = item ?? rp ;
item.Screen = item.Screen ?? rp.Screen ;
item.IEStatus = item.IEStatus ?? rp.IEStatus ;
item.IEPath = item.IEPath ?? rp.IEPath ;
item.FFStatus = item.FFStatus ?? rp.FFStatus ;
item.FFPath = item.FFPath ?? rp.FFPath ;
item.GCStatus = item.GCStatus ?? rp.GCStatus ;
item.GCPath = item.GCPath ?? rp.GCPath ;
}
coalesced.Add(item) ;
}
At the end of the day, the source list is unchanged and coalesced should contain a single ResultProperties for each distinct test case name, with all the value coalesce on a first-wins strategy (the first non-null value for a property defines the property for the group).
You should be able to do this with GroupBy, like this:
var res = testData
.GroupBy(test => test.TestCaseName)
.Select(g => new ResultProperties {
TestCaseName = g.Key
, Screen = g.Select(test => test.Screen).FirstOrDefault()
, IEStatus = g.Select(test => test.IEStatus).FirstOrDefault()
, IEPath = g.Select(test => test.IEPath).FirstOrDefault()
, FFStatus = g.Select(test => test.FFStatus).FirstOrDefault()
, FFPath = g.Select(test => test.FFPath).FirstOrDefault()
, GCStatus = g.Select(test => test.GCStatus).FirstOrDefault()
, GCPath = g.Select(test => test.GCPath).FirstOrDefault()
}).ToList();
The idea is to form groups based on the name of the test, and then grab the first non-null entry for each attribute in the group.

Avoiding null exception in EF that is thrown by your query

I have a query like this :
result =
firstIdeaRepository.FindBy(
i => i.FirstIdeaState == FirstIdeaState && i.Date >= start && i.Date <= end)
.AsEnumerable()
.Select(j => new RptListOfCompanyBasedOnFirstIdeaState()
{
Name =
companyRepository.FindBy(i => i.UserId == j.UserId)
.FirstOrDefault()
DateOfMeeting =
calenderRepository.ConvertToPersianToShow(
meetingReposiotry.FindBy(s => s.FirstIdeaId == j.Id)
.FirstOrDefault()
.Date),
DateOfExit =
calenderRepository.ConvertToPersianToShow(j.DateOfExit.Value),
ReasonOfExit = j.ReasonOfExit,
}).ToList();
return result;
As you can see i use FirstOrDefault() and j.DateOfExit.Value and sometimes my Date doesn't have any values or sometime my other variables are null too because i use firstordefaut() like
companyRepository.FindBy(i => i.UserId == j.UserId).FirstOrDefault().
So my query throws a null exception and the result can't be created ,how can i handle this exception and for example if the .NET detects the null value ignores it by default or uses a default values for that ?
Best regards.
I would make the following changes:
result =
firstIdeaRepository.FindBy(
i => i.FirstIdeaState == FirstIdeaState && i.Date >= start && i.Date <= end)
.AsEnumerable()
.Select(j => new RptListOfCompanyBasedOnFirstIdeaState()
{
Name =
companyRepository.FindBy(i => i.UserId == j.UserId)
.FirstOrDefault()
DateOfMeeting =
callenderRepository.ConvertToPersianToShow(
meetingReposiotry.FindBy(s => s.FirstIdeaId == j.Id)
// project a new sequence first, before calling `FirstOrDefault`:
.Select(s => s.Date)
.FirstOrDefault(),
DateOfExit =
j.DateOfExit.HasValue ?
callenderRepository.ConvertToPersianToShow(j.DateOfExit.Value) :
null,
ReasonOfExit = j.ReasonOfExit,
}).ToList();
When you use FirstOrDefault, there's a possibility that you'll get null back (in the case of reference types), and so you need to plan for that in your code.
For example, when assigning DateOfMeeting, you could project the results (using .Select) before using .FirstOrDefault, so that you're not ever accessing the Date property on what could be a null value.
As for DateOfExit, I've used the conditional operator to determine whether to call the calendarRepository's method at all. This assumes that DateOfExit is nullable.
Unrelated: "Calendar" is spelled with one "l" and not two.
Since you're using a nullable date, you can try filtering by values that have date, something like:
.FindBy(s => s.FirstIdeaId == j.Id && s.Date.HasValue)
This will ensure that you don't get any records with null date.
As I mentioned in comments, other cases need to be handled on case-by-case basis. Judging by the code you've shown, maybe you can handle Name as:
Name = companyRepository.FindBy(i => i.UserId == j.UserId).FirstOrDefault() ?? "anonymous";
and so on.
Another example:
If you do want to get the record even if DateOfMeeting is null, then add a check for HasValue in subsequent part or default it to some date:
DateOfExit = j.DateOfExit.HasValue ?
callenderRepository.ConvertToPersianToShow(j.DateOfExit.Value)
: (DateTime)null, // you need to make `DateOfExit` nullable and then handle that downstream
// or (default with current date)
DateOfExit = j.DateOfExit.HasValue ?
callenderRepository.ConvertToPersianToShow(j.DateOfExit.Value)
: callenderRepository.ConvertToPersianToShow(DateTime.Now),
// or (default with empty date)
DateOfExit = j.DateOfExit.HasValue ?
callenderRepository.ConvertToPersianToShow(j.DateOfExit.Value)
: callenderRepository.ConvertToPersianToShow(new DateTime()),
Moral of the story: figure out what the default value should be in case of null and then substitute that accordingly in the query when calling FirstOrDefault().
The broadest solution would be to use the idea of an null object with the DefaultIfEmpty<T>(T DefaultValue) method in your query. An example would be:
var defaultMeeting = new Meeting() { Date = new DateTime() };
var dateOfMeeting = meetingRepository.FindBy(s => s.FirstIdeaId == j.Id)
.DefaultIfEmpty(defaultMeeting)
.FirstOrDefault()
.Date;

Can't Concatenate String in LINQ query

I am just trying to concatenate a string on to a column returned from the database like so:
var aaData =
(from pr in ctx.PaymentRates
where pr.ServiceRateCodeId == new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96")
select new
{
Id = pr.Id,
Rate = pr.YearOneRate + "helloWorld"
})
.ToList();
It gives me this error:
Unable to cast the type 'System.Nullable`1' to type 'System.Object'.
LINQ to Entities only supports casting EDM primitive or enumeration
types.
So, then I tried this:
var aaData =
(from pr in ctx.PaymentRates
where pr.ServiceRateCodeId == new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96")
select new
{
pr = pr
})
.AsEnumerable()
.Select(x => new
{
Id = x.pr.Id,
Rate = x.pr.YearOneRate + "helloWorld"
})
.ToList();
But, now it gives me this error:
Object reference not set to an instance of an object.
On this line:
.Select(x => new
How can I concatenate these strings in LINQ?
The quick solution
Regarding your second code chunk I need to point out to you that you're actually doing two left outer joins on the Countries table/set, one for homeC and one for hostC.
That means that you are willing to accept null values for those two variables.
In other words, since they can be null you are somehow allowing this right here to crash with NullReferenceException, should those variables turn out to be null:
.Select(x => new
{
Id = x.pr.Id,
HomeCountry = x.homeC.Name,
HostCountry = x.hostC.Name,
Rate = x.pr.YearOneRate + "helloWorld"
})
The error (NullReferenceException or as you saw it's message: "Object reference not set to an instance of an object.") is not here
.Select(x =>
but rather here
x.homeC.Name and x.hostC.Name
where you will most certainly dereference a null reference.
That's just Visual Studio's way of pointing out the best statement that fits around the error.
So, the quickest solution would be to do this:
.Select(x => new
{
Id = x.pr.Id,
HomeCountry = (x.homeC != null) ? x.homeC.Name : "HomeCountry not found",
HostCountry = (x.hostC.Name != null) ? x.hostC.Name : "HostCountry not found",
Rate = x.pr.YearOneRate + "helloWorld"
})
Notice the modification which ensures that you will still be able to extract some information from result set records for which homeC and hostC are null.
EDIT
Regarding the first query you posted:
var aaData =
(from pr in ctx.PaymentRates
where pr.ServiceRateCodeId == new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96")
select new
{
Id = pr.Id,
Rate = pr.YearOneRate + "helloWorld"
})
.ToList();
my guess is that your 'YearOnRate' property is of type 'Nullable< of something >" (maybe decimal -- so for instance it is maybe a decimal? YearOnRate { get; set; }) and the corresponding column in the database is a nullable one.
If that is the case, then I think (in this first version of your endeavour) you could try to do this:
Rate = (pr.YearOnRate != null) ? pr.YearOneRate.Value + "helloWorld" : "[null]helloWorld"
and get away with it.
My guess is that either x.homeC, x.hostC, or x.pr are null. If you're fine using AsEnumerable to convert to Linq-to-Objects then you could just change your projection to
.Select(x => new
{
Id = (x.pr.HasValue ? x.pr.Id : 0),
HomeCountry = (x.homeC.HasValue ? x.homeC.Name : null),
HostCountry = (x.hostC.HasValue ? x.hostC.Name : null),
Rate = (x.pr.HasValue ? x.pr.YearOneRate : null) + "helloWorld"
})
My problem was, I wasn't using .AsEnumerable() properly. The code below works:
var aaData =
(from pr in ctx.PaymentRates
from homeC in ctx.Countries.Where(x => x.Id == pr.HomeCountryId).DefaultIfEmpty()
from hostC in ctx.Countries.Where(x => x.Id == pr.HostCountryId).DefaultIfEmpty()
from curr in ctx.Currencies.Where(x => x.Id == pr.YearOneCurrencyId).DefaultIfEmpty()
where pr.ServiceRateCodeId.Value.Equals(new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96"))
select new { pr, homeC, hostC, curr })
.AsEnumerable()
.Select(x => new
{
Id = (string)(x.pr.Id.ToString() + "test"),
HomeCountry = (x.homeC != null ? x.homeC.Name : ""),
HostCountry = (x.hostC != null ? x.hostC.Name : ""),
Rate = (x.pr.YearOneRate ?? 0) + " (" + x.curr.Code + ")"
})
.ToList();
You have used DefaultIfEmpty on both homeC and hostC so you can get a null reference when you call homeC.Name or hostC.Name
Try using HomeCountry = homeC == null ? null : homeC.Name instead
If pr.YearOneRate is not a string and you want the concatenation done by the database engine you need to tell Linq-to-Entities to generate sql to convert it. If you are using Sql Server you can use this:
SqlFunctions.StringConvert(pr.YearOneRate) + "helloWorld"
If you don't need the concatenation done in the database then you can use AsEnumerable() before the Select so that you are running Linq-To-Objects

Categories