Can't Concatenate String in LINQ query - c#

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

Related

Entity Framework | Include -> Select Empty Field

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();

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.

Linq - EntityFramework NotSupportedException

I have a query that looks like this:
var caseList = (from x in context.Cases
where allowedCaseIds.Contains(x => x.CaseId)
select new Case {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
Notifier = x.NotifierId.HasValue ? new Notifier { Name = x.Notifier.Name } : null // This line throws exception
}).ToList();
A Case class can have 0..1 Notifier
The query above will result in the following System.NotSupportedException:
Unable to create a null constant value of type 'Models.Notifier'. Only entity types, enumeration types or primitive types are supported
in this context.
At the moment the only workaround I found is to loop the query result afterwards and manually populate Notifierlike this:
foreach (var c in caseList.Where(x => x.NotifierId.HasValue)
{
c.Notifier = (from x in context.Notifiers
where x.CaseId == c.CaseId
select new Notifier {
Name = x.Name
}).FirstOrDefault();
}
But I really don't want to do this because in my actual scenario it would generate hundreds of additional queries.
Is there any possible solution for a situation like this?.
I think you need to do that in two steps. First you can fetch only the data what you need with an anonymous type in a single query:
var caseList = (from x in context.Cases
where allowedCaseIds.Contains(x => x.CaseId)
select new {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
NotifierName = x.Notifier.Name
}).ToList();
After that, you can work in memory:
List<Case> cases = new List<Case>();
foreach (var c in caseList)
{
var case = new Case();
case.CaseId = c.CaseId;
case.NotifierId = c.NotifierId;
case.NotifierName = c.NotifierId.HasValue ? c.NotifierName : null;
cases.Add(case);
}
You could try writing your query as a chain of function calls rather than a query expression, then put an .AsEnumerable() in between:
var caseList = context.Clases
.Where(x => allowedCaseIds.Contains(x.CaseId))
.AsEnumerable() // Switch context
.Select(x => new Case() {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
Notifier = x.NotifierId.HasValue
? new Notifier() { Name = x.Notifier.Name }
: null
})
.ToList();
This will cause EF to generate an SQL query only up to the point where you put the .AsEnumerable(), further down the road, LINQ to Objects will do all the work. This has the advantage that you can use code that cannot be translated to SQL and should not require a lot of changes to your existing code base (unless you're using a lot of let expressions...)

C# ?? operator and ora-12704: character set mismatch

I have asp.net mvc 4 application with EF 4, .net 4
This code
public List<ListItem> GetViolatedArticlesByLaw(int lawId, string culture)
{
culture = culture.ToLower();
var ans =
(from art in context.TITLENORMALAWs.Where(l => l.PARENTID == lawId)
select new ListItem
{
ID = art.ID,
ParentID = lawId,
Value = (art.NUM_STATSTR ?? ""),
});
Debug.WriteLine( ((System.Data.Objects.ObjectQuery)ans).ToTraceString() );
return ans.ToList();
}
throws ora-12704: character set mismatch.
It runs perfectly fine if I select from List, like this: from art in context.TITLENORMALAWs.Where(l => l.PARENTID == lawId).ToList()
This is the SQL generated:
SELECT
"Extent1"."ID" AS "ID",
:p__linq__1 AS "C1",
CASE WHEN ("Extent1"."NUM_STATSTR" IS NULL) THEN '' ELSE "Extent1"."NUM_STATSTR" END AS "C2"
FROM "AISSMK"."TITLENORMALAW" "Extent1"
WHERE ("Extent1"."PARENTID" = :p__linq__0)
It produces the same error in sqldeveloper and if I change this piece THEN '' ELSE to this THEN n'' ELSE it runs ok.
NUM_STATSTR in table definition is NVARCHAR2(30)
How can I make linq generate proper sql? Or do I have to call ToList() before selecting and there is no other way?
#Orif I think you should try to build the query manually instead of using the LINQ-to-SQL generators.
Try using the ExecuteQuerymethod on the DataContext class and try to add a cast to NVARCHAR
For more help read here, https://social.msdn.microsoft.com/Forums/en-US/20d456f0-9174-4745-bbc5-571f68879e27/net-strings-sql-paramater-type-always-nvarchar?forum=linqtosql
In my case, the issue was that empty strings are treated as null by Oracle, so your code Value = (art.NUM_STATSTR ?? "") actually ends up looking like Value = (art.NUM_STATSTR ?? null). Our workaround looks like this:
var ans =
(from art in context.TITLENORMALAWs
where art.PARENTID == lawId
select new
{
ID = art.ID,
ParentID = lawId,
Value = (art.NUM_STATSTR ?? ""),
})
.ToList()
.Select(a => new ListItem{
ID = a.ID,
ParentID = a.ParentID,
Value = a.Value ?? "",
});

Nested query in entity framework

I am getting the following exception:
The nested query is not supported. Operation1='Case' Operation2='Collect'
with this query
var Games = context.Games.Select(a => new GameModel
{
Members = (a.Type == 1 ? (a.UsersInGames.Where(b => b.GameID == a.ID && b.StatusID == 1).Select(c => new Member
{
ID = c.UserID,
email = c.UserInfo.EmailAddress,
screenName = c.UserInfo.ScreenName
})) :
(a.Teams.Where(b => b.GameID == a.ID).SelectMany(b => b.UsersInTeams.Where(c => c.StatusID == 1)).Select(d => new Member
{
ID = d.UserID,
email = d.UserInfo.EmailAddress,
screenName = d.UserInfo.ScreenName
)))
})
when I don't include the condition in selecting Members, the query works fine. Is there a way I can do the conditional inside the query?
You're overestimating the power of LINQ translation to SQL. Not everything is translatable and there is no compiler warning for that due to the way LINQ works.
Nested collections are usually either a) not supported or b) end up in horrible SELECT N+1 queries. What you ask EF to do is to return an object tree. SQL does not support tree like results so you run into the object-relational impedance mismatch and it hurts.
I advise you to fetch the nested collection data as a second, completely separate query. That allows you more control and is guaranteed to work.
As a non-essential side-note, you will probably not be able to convince EF to use the ?: operator over sequences. That is very hard to translate. Think how you would write this as SQL - very hard and convoluted.
It looks like Linq to EF doesn't support the following
context.Games.Select(g => new
{
Field = g.IsX? queryable1 : queryable2
});
But, here's a hack you can use to get it to work:
context.Games.Select(g => new
{
Field = queryable1.Where(q => g.IsX)
.Concat(queryable2.Where(q => !g.IsX))
});
I faced the same problem. The solution was to load both results and determine what to use after the query (I know it has performance downside), but at least you can do it temporarily if deadline attacks you:
At the LINQ side
var Games = context.Games.Select(a => new GameModel
{
// carries type1 results
Members = a.UsersInGames.Where(b => b.GameID == a.ID && b.StatusID == 1).Select(c => new Member
{
ID = c.UserID,
email = c.UserInfo.EmailAddress,
screenName = c.UserInfo.ScreenName
})),
//You need to create this temporary carrier to carry type 2 results
MembersOfType2 = a.Teams.Where(b => b.GameID == a.ID).SelectMany(b => b.UsersInTeams.Where(c => c.StatusID == 1)).Select(d => new Member
{
ID = d.UserID,
email = d.UserInfo.EmailAddress,
screenName = d.UserInfo.ScreenName
})))
})
}
After that you may loop Gamesand make the assignment Members = MembersOfType2 if Type == 1 for a certain game.
I had this error too. I had code like this:
var Games = context.Games.Select(a => new GameModel
{
Members = (!filters.GetDatailedDataToo ? null : new List<MemberModel>())
};
This error occurs when null is used in ? : operation.
This is not that case, written up here, but I've wasted lot of time, I think anyone uses this case, who searches this error text..

Categories