"Unable to create a constant value of type .. Only primitive types are supported ..'' in EF query? - entity-framework

I have a work around for this issue, however I would appreciate it if someone could explain why this is happening and how I would design this for large datasets where my work around would not be viable.
The full error is:
Unable to create a constant value of type 'THPT_Razor.Models.WinType'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
and I am using EF v4.0.
The commented lines are the offending code and the work around is the "For loop"
Thank you in advance.
List<WinType> _atype = db.WinTypes.Where(wt => wt.IsWin == false).ToList();
List<WinType> _wtype = db.WinTypes.Where(wt => wt.IsWin == true).ToList();
string test = _wtype.Where(wt => wt.Value ==0).Select(wt => wt.Description).SingleOrDefault();
List<WinCheckDetails> wcd = db.Wins.Include("UserProfiles").Where(w => w.venueLogId == logid).Select(w => new WinCheckDetails
{
//awarddesc = w.atypeid.HasValue ? _atype.Where( wt=> wt.Value == w.atypeid).Select(wt => wt.Description).SingleOrDefault():string.Empty,
//windesc = _wtype.Where(wt => wt.Value == w.typeid).Select(wt => wt.Description).Single(),
atypeid = w.atypeid,
typeid = w.typeid,
WinId = w.WinId,
other = w.other,
posterid = w.posterid,
confirmed = w.confirmed,
posttime = w.posttime,
game = w.game,
playerid = w.UserProfile.PlayerID,
firstname = w.UserProfile.FirstName,
lastname = w.UserProfile.LastName,
fullname = w.UserProfile.FirstName + " " + w.UserProfile.LastName
}).OrderBy(o => o.game).ToList();
foreach (WinCheckDetails wc in wcd)
{
wc.awarddesc = _atype.Where(wt => wt.Value == wc.atypeid).Select(wt => wt.Description).SingleOrDefault();
wc.windesc = _wtype.Where(wt => wt.Value == wc.typeid).Select(wt => wt.Description).SingleOrDefault();
}

_atype and _wtype are lists of WinType in memory because you are applying ToList() to the queries. With respect to database queries they are collections of constant values because to perform the query in the database they have to be transmitted to the database server as the values they are in memory. EF doesn't support to transfer such constant values or collections of values from memory to the database unless they are values of primitive types (int for example). That's the reason why you get an exception.
Did you try to use _atype and _wtype as IQueryable instead of lists:
IQueryable<WinType> _atype = db.WinTypes.Where(wt => !wt.IsWin);
IQueryable<WinType> _wtype = db.WinTypes.Where(wt => wt.IsWin);
List<WinCheckDetails> wcd = db.Wins
.Where(w => w.venueLogId == logid)
.Select(w => new WinCheckDetails
{
awarddesc = w.atypeid.HasValue
? _atype.Where(wt=> wt.Value == w.atypeid)
.Select(wt => wt.Description).FirstOrDefault()
: string.Empty,
windesc = _wtype.Where(wt => wt.Value == w.typeid)
.Select(wt => wt.Description).FirstOrDefault(),
// ... (unchanged)
}).OrderBy(o => o.game).ToList();
I have removed the Include because it will be ignored anyway when you perform a projection with Select. Also I have replaced SingleOrDefault and Single by FirstOrDefault because both are not supported in a projection (and First neither), only FirstOrDefault is supported.
I am not sure if that will work. But it should remove your exception (but maybe you'll get another one...).

Related

How to sort on DB side, if entities are not connected via Navigation (since it's not possible)

I want to have my EfCore query translated into the following SQL query:
select
c.blablabla
from
codes c
left join lookups l on c.codeId = l.entityid and l.languageCode = <variable - language code of current thread> and l.lookuptype = 'CODE'
where
..something..
order by
l.displayname
Note: tables 'codes' and 'lookups' are not connected! 'lookups' contains a lot of different lookup data in different languages!
I am stuck into limitations of EfCore (like 'NavigationExpandingExpressionVisitor' failed). I don't want to make in-memory filtering, it looks silly to me... Am I missing something obvious?
In perspective, I'd like to make universal method to help sort by displayname (or other lookup name) for different kind of entities - not only codes.
Seems like I figured it out. If there's a better approach - please let me know:
protected override IQueryable<FixCode> SortByDisplayName(IQueryable<FixCode> queryable, string languageCode = null)
{
return queryable
.GroupJoin(
DbContext.FixCodeValues.Where(x =>
x.DomainId == CentralToolConsts.Domains.CENTRAL_TOOLS
&& x.CodeName == CentralToolsFieldTypes.CODE_ORIGIN
&& (x.LanguageCode == languageCode || x.LanguageCode == CentralToolsDbLanguageCodes.English)),
//TODO: this will be a 'selector' parameter
code => code.CodeOriginId,
codeOrigin => codeOrigin.StringValue,
(c, co) => new
{
Code = c,
CodeOrigin = co
}
)
.SelectMany(
x => x.CodeOrigin.DefaultIfEmpty(),
(x, codeOrigin) => new { Code = x.Code, CodeOrigin = codeOrigin }
)
.OrderBy(x => x.CodeOrigin.ShortName)
.Select(x => x.Code);
}

Is it possible to combine multiple IQueryable into a single one without using Union?

Good evening. Is it possible to combine multiple IQueryable into a single one without using Union in EF Core 2? The main problem is that it doesn't allow to use methods like .Union(), .Except() etc.
It's important that IQueryable should be executed as a single query.
This is what I want to get: toCreateSubquery.Union(toDeleteSubquery)
The queries I want to combine are listed below.
var toCreateSubquery =
validLinks
.Where(
rl => !existingLinks.Any(
el => el.Contract == rl.Contract && el.Estate == rl.Estate)) // Except() doesn't work'
.Select(x => new {x.Contract, x.Estate, Type = ActionType.Create});
var toDeleteSubquery =
existingLinks
.Where(el => !validLinks.Any(rl => el.Contract == rl.Contract && el.Estate == rl.Estate))
.Select(x => new {x.Contract, x.Estate, Type = ActionType.Delete});
This is the visualization of the problem I'm solving.
I want to get the union of these sets without intersection and be able to distinguish belonging to one of these sets.
Just in case, I attach the code of getting these sets:
var validLinks = from ac in _context.AreaContracts
from e in _context.Estate
where (from al in e.AreaLinks select al.Area.Id).Contains(ac.Area.Id) ||
e.Geometry.Intersects(
ac.Area.Geometry)
select new { Contract = ac.Contract.Id, Estate = e.Id };
var existingLinks = from sd in _context.SquareDistributions
select new { Contract = sd.Contract.Id, Estate = sd.Estate.Id };
Thank you for your attention.

Oring muliple things in a Where clause

I have this code
private CurrencyConversionResult GetNumberOfCurrencyUnitsPerEuro(CurrencyType from, CurrencyType to)
{
...
IEnumerable<ExchangeRate> rate = info.ExchangeRates.Where(e => e.CurrencySymbol == from.ToString() || e.CurrencySymbol == to.ToString()).ToList();
...
I want to change the signature of this method to
private CurrencyConversionResult GetNumberOfCurrencyUnitsPerEuro(IEnumerable<CurrencyType> from, CurrencyType to)
So, what I want to do now is get all ExchangeRates where e.CurrencySymbol is equal to to or any of the froms. Question is I don't know how to write in that in one statement so that there is only 1 database call. Any ideas?
var symbols = from.Select(f => f.ToString());
var rate = info.ExchangeRates
.Where(e => symbols.Contains(e.CurrencySymbol) ||
e.CurrencySymbol == to.ToString())
.ToList();
Not sure if Any can be translated by EF (it does not work on local sequences with Linq to SQL) but you can also try:
var rate = info.ExchangeRates
.Where(e => from.Any(f => e.CurrencySymbol == f.ToString()) ||
e.CurrencySymbol == to.ToString())
.ToList();

Zend Db joinLeft with parameterized value

I need to joinLeft within Zend_Db ala:
$select->joinLeft(array('ATAG' => 'ad_tags'),
array('ADM.id = ATAG.ad_id AND ADM.site_id = ATAG.site_id AND ATAG.tag_id = ?', $input_vars['tag']),
array('tag_id'))
->order('ATAG.tag_id DESC')
->limit('1');
However, I can't use an array as the second parameter, because it only tags a string; how can I pass in the value, without actually embedding it in the string?
Based on your example you need to use quoteInto (considering you're in Zend_Db_Table):
$select->joinLeft(array('ATAG' => 'ad_tags'),
'ADM.id = ATAG.ad_id AND ADM.site_id = ATAG.site_id AND ' .
$this->getAdapter()->quoteInto('ATAG.tag_id = ?',$input_vars['tag']),
array('tag_id'))
->order('ATAG.tag_id DESC')
->limit('1');
You can also do the same with:
$select->from(array('ADM' => 'adm_table'),'*')
->joinLeft(array('ATAG' => 'ad_tags'), 'ADM.id = ATAG.ad_id', 'ATAG.tag_id')
->where('ADM.site_id =ATAG.site_id')
->where('ATAG.tag_id = ?',$input_vars['tag'])
->limit(1);
If you're in Zend_Db_Table you need to set
$select->setIntegrityCheck(false);
(Note that adm_table is just an example, since you did not tell me the table name)

Entity Framework calling MAX on null on Records

When calling Max() on an IQueryable and there are zero records I get the following exception.
The cast to value type 'Int32' failed because the materialized value is null.
Either the result type's generic parameter or the query must use a nullable type.
var version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e => e.Version);
Now I understand why this happens my question is how is the best way to do this if the table can be empty. The code below works and solves this problem, but its very ugly is there no MaxOrDefault() concept?
int? version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Select(e => (int?)e.Version)
.Max();
Yes, casting to Nullable of T is the recommended way to deal with the problem in LINQ to Entities queries. Having a MaxOrDefault() method that has the right signature sounds like an interesting idea, but you would simply need an additional version for each method that presents this issue, which wouldn't scale very well.
This is one of many mismatches between how things work in the CLR and how they actually work on a database server. The Max() method’s signature has been defined this way because the result type is expected to be exactly the same as the input type on the CLR. But on a database server the result can be null. For that reason, you need to cast the input (although depending on how you write your query it might be enough to cast the output) to a Nullable of T.
Here is a solution that looks slightly simpler than what you have above:
var version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e =>(int?)e.Version);
Try this to create a default for your max.
int version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e =>(int?)e.Version) ?? 0;
You could write a simple extension method like this, it returns the default value of type T if no records exist and is then apply Max to that or the query if records exist.
public static T MaxOrEmpty<T>(this IQueryable<T> query)
{
return query.DefaultIfEmpty().Max();
}
and you could use it like this
maxId = context.Competition.Select(x=>x.CompetitionId).MaxOrEmpty();
I couldnt take no for an answer :) I have tested the below and it works, I havent checked the SQL generated yet so be careful, I will update this once I have tested more.
var test = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.MaxOrDefault(x => x.Version);
public static TResult? MaxOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
where TResult : struct
{
return source
.Select(selector)
.Cast<TResult?>()
.Max();
}
Try this:
IEnumerable<AlertsResultset> alerts = null;
alerts = (from POA in SDSEntities.Context.SDS_PRODUCT_ORDER_ALERT
join A in SDSEntities.Context.SDS_ALERT on POA.ALERT_ID equals A.ALERT_ID
orderby POA.DATE_ADDED descending
select new AlertsResultset
{
ID = POA.PRODUCT_ORDER_ALERT_ID == null ? 0:POA.PRODUCT_ORDER_ALERT_ID ,
ITEM_ID = POA.ORDER_ID.HasValue ? POA.ORDER_ID.Value : POA.PRODUCT_ID.Value,
Date = POA.DATE_ADDED.Value,
orderType = SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().ORDER_TYPE,
TransactionNumber = POA.PRODUCT_ID.HasValue ? (SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().TRANSACTION_NUMBER) : (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().TRANSACTION_NUMBER),
Publisher = POA.PRODUCT_ID.HasValue ?
(
SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().PRODUCT_TYPE_NUMBER == "ISSUE" ? (from prod in SDSEntities.Context.SDS_PRODUCT
join ji in SDSEntities.Context.SDS_JOURNAL_ISSUE on prod.PRODUCT_ID equals ji.PRODUCT_ID
join j in SDSEntities.Context.SDS_JOURNAL on ji.JOURNAL_ID equals j.JOURNAL_ID
where prod.PRODUCT_ID == POA.PRODUCT_ID
select new { j.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME : (from prod in SDSEntities.Context.SDS_PRODUCT
join bi in SDSEntities.Context.SDS_BOOK_INSTANCE on prod.PRODUCT_ID equals bi.PRODUCT_ID
join b in SDSEntities.Context.SDS_BOOK on bi.BOOK_ID equals b.BOOK_ID
where prod.PRODUCT_ID == POA.PRODUCT_ID
select new { b.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME
)
: (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().PUBLISHER_NAME),
Alert = A.ALERT_NAME,
AlertType = A.ALERT_TYPE,
IsFlagged = POA.IS_FLAGGED.Value,
Status = POA.ALERT_STATUS
});
how about
var version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e => (int?)e.Version);
less ugly, more elegant
I want to suggest a merge from the existing answers:
#divega answer works great and the sql output is fine but because of 'don't repeat yourself'
an extension will be a better way like
#Code Uniquely showed. But this solution can output more complex sql as you needed.
But you can use the following extension to bring both together:
public static int MaxOrZero<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, int>> selector)
{
var converted = Expression.Convert(selector.Body, typeof(int?));
var typed = Expression.Lambda<Func<TSource, int?>>(converted, selector.Parameters);
return source.Max(typed) ?? 0;
}
You can use:
FromSqlRaw("Select ifnull(max(columnname),0) as Value from tableName");