Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select request on nullable foreign key #6301

Closed
DSorin opened this issue Aug 11, 2016 · 10 comments
Closed

Select request on nullable foreign key #6301

DSorin opened this issue Aug 11, 2016 · 10 comments
Assignees

Comments

@DSorin
Copy link

DSorin commented Aug 11, 2016

I want to perform a select on a nullable foreign key to build some anonymous type without having to retrieve all the field in database =>

var qsd = _context.AncdAnalyseCommandesDetail
    .Include(i => i.AncdPro).ThenInclude(pro => pro.ProSseg)
        .ThenInclude(sseg => sseg.SsegSeg).ThenInclude(seg => seg.SegMar)
    .Include(i => i.AncdAnc).ThenInclude(analyse => analyse.AncCli)
    .Include(i => i.AncdAnc).ThenInclude(analyse => analyse.AncAnp)
    **.Include(i => i.Product).ThenInclude(pro => pro.Brand)**
    .Select(s => new
      {
        ProId = s.AncdProId,
        ProNom = s.AncdPro.ProNom,
       **ProductBrandName  = s.Product.Brand.BrandName** 
        ProSSeg = s.AncdPro.ProSseg.SsegLibelle,
        ProSeg = s.AncdPro.ProSseg.SsegSeg.SegLibelle,
        ProMarche = s.AncdPro.ProSseg.SsegSeg.SegMar.MarLibelle,
        ClidId = s.AncdAnc.AncCliId,
        CliNom = s.AncdAnc.AncCli.CliNomSociete,
        DN = true,
        AncdPuMoy = s.AncdPuMoy,
        AncdPvcMoy = s.AncdPvcMoy,
        PeriodeLibelle = s.AncdAnc.AncAnp.AnpLibelle
      }).Take(100000).ToList();

Here the Product's Brand can be null, I always get an exception.
I tried some workaround without success :

  1. Create a custom property in Product
  public string ProductBrandName  {
      get { return Brand != null ? Brand.BrandName: "Inconnue"; }
    }

It's working, but my product object is completely initialized so it's not performance wise.

  1. doing something like
    C# ProductBrandName = s.Product.Brand.BrandName ?? ""
    won't work

Une exception de type 'System.InvalidCastException' s'est produite dans System.Private.CoreLib.ni.dll mais n'a pas été gérée dans le code utilisateur

Unable to cast object of type 'System.Linq.Expressions.Expression1[System.Func2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor
+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2
[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+
TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Storage.ValueBuffer,Microsoft.EntityFrameworkCore.Storage.ValueBuffer],Microsoft.EntityFrameworkCore.Storage.ValueBuffer],
Microsoft.EntityFrameworkCore.Storage.ValueBuffer],SFO.DAN.DTO_CRM.LinearSurvey.ProProduits]
,System.Collections.Generic.IEnumerable`1[SFO.DAN.DTO_CRM.LinearSurvey.MarqMarques]],SFO.DAN.DTO_CRM.LinearSurvey.MarqMarques],System.Int32]]' to type 'System.Linq.Expressions.ConstantExpression'.

If I remove the Brand part, the SQL request is correctly generated, and I'm under 1 second to generate my 100 000 item list so I would like to keep it that way ^^

(I translated the bold part of my code which is in French, so the stack trace do not match exactly!)

Any suggestion ?

@DSorin
Copy link
Author

DSorin commented Aug 12, 2016

I someone need it :

Something like that :

var query = (from person in _context.ProProduits
                  join pet in _context.MarqMarques on person.ProMarId equals pet.MarqId 
                  into gj from subpet in gj.DefaultIfEmpty(new DTO_CRM.LinearSurvey.MarqMarques())
                  select new { person.ProNom, PetName = subpet.MarqLibelle }
                  ).Take(10)   

will generate the correct query EXCEPT that take(10) is on client side ...
while

var azezeaeq = (from product in _context.ProProduits
                      from brand in _context.MarqMarques
                                    .Where(w => product.ProMarId == w.MarqId)
                      select new { product.ProNom, PetName = brand.MarqLibelle }
                      ).Take(50);

Generate a CROSS JOIN request instead of a left outer join, but it's process the take(50) in the request.

@divega divega added this to the 1.1.0 milestone Aug 15, 2016
@maumar
Copy link
Contributor

maumar commented Aug 23, 2016

@DSorin does this also happen if you query just for the brand name, like so?:

var qsd = _context.AncdAnalyseCommandesDetail
    .Include(i => i.Product).ThenInclude(pro => pro.Brand)
    .Select(s => new
      {
       ProductBrandName  = s.Product.Brand.BrandName 
      }).Take(100000).ToList();

if so, could you share code for relevant entities: AncdAnalyseCommandesDetail, Product, Brand and dbcontext (or more specifically contents of OnModelCreating used to configure those 3 entities above)?

ProductBrandName = s.Product != null ? s.Product.Brand.BrandName : null

Some extra info: you don't actually need to put Include calls in your query, EF will figure out which navigations it needs based on the query itself - the Includes are actually ignored, because no entities are materialized, just their individual properties.

As to the solution you proposed, using groupjoin is doing client eval because of: #4588

@DSorin
Copy link
Author

DSorin commented Aug 23, 2016

Hi maunar,

@DSorin does this also happen if you query just for the brand name, like so?:

var qsd = _context.AncdAnalyseCommandesDetail
.Include(i => i.Product).ThenInclude(pro => pro.Brand)
.Select(s => new
{
ProductBrandName = s.Product.Brand.BrandName
}).Take(100000).ToList();

I do not get the error, but the take is client side + it's performing a "select * from...." kind of request :

SELECT *
      FROM [ANCD_analyse_commandes_detail] AS [i]
      INNER JOIN [PRO_Produits] AS [i.AncdPro] ON [i].[ancd_pro_id] = [i.AncdPro].[pro_id]
      LEFT JOIN [MARQ_Marques] AS [i.AncdPro.ProMar] ON [i.AncdPro].[pro_mar_id] = [i.AncdPro.ProMar].[marq_id]
      ORDER BY [i.AncdPro].[pro_mar_id]

And the left outer join is correctly generated.

My table : ANCD_analyse_commandes_details contains 5 483 944 records so i cannot perform neither a "select *" or making the .take on client side.

I use Scaffold-DbContext for generate all my context, from a SQL2012 database =>

I zipped you a console App with the relevant file in it. It's only the 3 tables here, if you need the entire context let me know ~~~

ConsoleApp1.zip

@maumar
Copy link
Contributor

maumar commented Aug 23, 2016

@DSorin since the issue doesn't repro in isolation, I will need the entire context (and entities). We can do it over email if that's more convenient for you. My address is [my_github_username][at]microsoft[d0t]com.

Also, once this bug is fixed, you will still get select * and filtering/paging done on the client, due to #4588 - we lose track of everything after first optional navigation. That bug is scheduled to be fixed for the next release, as many people are in similar situation to yours.

@maumar
Copy link
Contributor

maumar commented Aug 26, 2016

@DSorin when/if you send me a repro, please also post something here. I want to make sure my spam filter won't eat email from outside the organization :)

@DSorin
Copy link
Author

DSorin commented Aug 29, 2016

Hi maumar,

You will find attached the entire context, this is a database existing since early 2000' so a lots of people worked on it. For the best and the worst ! My guest is, if you can manage all the trap in there the Framework should be quite stable.
Sadly i can't provide you any data since it's order/pricing/etc ... of our customers. I hope the context will be enough for your team to work on.
Projects.zip

You will notice that when using a "single block" context, initialize time for the first request when you validate the model is almost >10 secondes ...

FYI :
1/ I switched my ASP.MVC core project from farmework.core to framework 4.6 since i needed to go live in September. But i really like the idea to have a lot of flexibility on the hosting server, so I planned to upgrade back to framework.core as soon as possible.

2/ Here the kind of request i'm working with, ( mostly when i'm doing DashBord and reporting ). The code below work fine in EF6 so i would expect the same of EFcore if i want to use it on our software.

//All products and customers filtered on input data
      var CliProAll = from client in _context.CLI_Clients
                      from produit in _context.PRO_Produits
                      where _context.UCD_UniversClientsDetails.Any(a => a.ucd_uc_id == filterldc.UcId && a.ucd_cli_id == client.cli_id) &&
                          _context.UPD_UniversProduitsDetails.Any(a => a.upd_up_id == filterldc.UpId && a.upd_pro_id == produit.pro_id)
                      select new { cliid = client.cli_id, proId = produit.pro_id };

      // Get all ordering filtered on date 
      var commandes = from ancd in _context.ANCD_analyse_commandes_detail
                      join anc in _context.ANC_analyse_commandes on ancd.ancd_anc_id equals anc.anc_id
                      join anp in _context.ANP_Analyse_Periode on new { id = anc.anc_anp_id, type = 2 } equals new { id = anp.ANP_id, type = anp.ANP_type }
                      where _context.UCD_UniversClientsDetails.Any(a => a.ucd_uc_id == filterldc.UcId && a.ucd_cli_id == anc.anc_cli_id) &&
                          _context.UPD_UniversProduitsDetails.Any(a => a.upd_up_id == filterldc.UpId && a.upd_pro_id == ancd.ancd_pro_id) &&
                      (filter.FilterDateDebut == null || anp.ANP_date_debut >= filter.FilterDateDebut) &&
                      (filter.FilterDateFin == null || anp.ANP_date_fin <= filter.FilterDateFin)
                      group new { ancd.ancd_pvc_moy, ancd.ancd_nb_presence, ancd.ancd_promo } by new { proID = ancd.ancd_pro_id, cliID = anc.anc_cli_id } into g
                      select new
                      {
                        ProID = g.Key.proID,
                        cliId = g.Key.cliID,
                        ancd_pvc_moy = g.Average(a => a.ancd_pvc_moy),
                        ancd_nb_presence = g.Sum(s => s.ancd_nb_presence),
                        promoInsideTry = g.Max(m => m.ancd_promo ? 1 : 0)
                      };

      // Get all package
      var assortiments = from cli in _context.CLI_Clients
                         join astd in _context.ASTD_AssortData on cli.cli_assorID equals astd.ASTD_IdAssort
                         join pro in _context.PRO_Produits on astd.ASTD_IDProduit equals pro.pro_id
                         join sseg in _context.SSEG_Sous_Segment on pro.pro_sseg_id equals sseg.sseg_id
                         join astc in _context.ASTC_AssortClient on new { segID = sseg.sseg_seg_id, cliId = cli.cli_id } equals new { segID = astc.ASTC_IdSegment, cliId = astc.ASTC_IdClient }
                         where _context.UCD_UniversClientsDetails.Any(a => a.ucd_uc_id == filterldc.UcId && a.ucd_cli_id == cli.cli_id) &&
                          _context.UPD_UniversProduitsDetails.Any(a => a.upd_up_id == filterldc.UpId && a.upd_pro_id == pro.pro_id)
                         select new
                         {
                           proId = pro.pro_id,
                           cliId = cli.cli_id,
                           classClient = astc.ASTC_NumClass == 1 ? astd.ASTD_Class1 ?? "Z" :
                            astc.ASTC_NumClass == 2 ? astd.ASTD_Class2 ?? "Z" :
                            astc.ASTC_NumClass == 3 ? astd.ASTD_Class3 ?? "Z" :
                            astc.ASTC_NumClass == 4 ? astd.ASTD_Class4 ?? "Z" :
                            astc.ASTC_NumClass == 5 ? astd.ASTD_Class5 ?? "Z" :
                            astc.ASTC_NumClass == 6 ? astd.ASTD_Class6 ?? "Z" :
                            astc.ASTC_NumClass == 7 ? astd.ASTD_Class7 ?? "Z" :
                            astc.ASTC_NumClass == 8 ? astd.ASTD_Class8 ?? "Z" :
                            astc.ASTC_NumClass == 9 ? astd.ASTD_Class9 ?? "Z" :
                            astc.ASTC_NumClass == 10 ? astd.ASTD_Class10 ?? "Z" : "Z"
                         };

      // Get all survey data
      var prodReleves = from relp in _context.RELP_Releve_Produit
                        join rel in _context.REL_Releve on relp.relp_rel_id equals rel.rel_id
                        join relf in _context.RELF_Releve_Fdv on new { relID = rel.rel_id, fdvIdRqt = fdvId } equals new { relID = relf.relf_rel_id, fdvIdRqt = relf.relf_fdv_id }
                        where rel.rel_type == 1 &&
                        (filter.FilterDateDebut == null || rel.rel_date >= filter.FilterDateDebut) &&
                        (filter.FilterDateFin == null || rel.rel_date <= filter.FilterDateFin) &&
                        _context.UCD_UniversClientsDetails.Any(a => a.ucd_uc_id == filterldc.UcId && a.ucd_cli_id == rel.rel_cli_id) &&
                        _context.UPD_UniversProduitsDetails.Any(a => a.upd_up_id == filterldc.UpId && a.upd_pro_id == relp.relp_pro_id)
                        group new { relp.relp_etat, relp.relp_pvc, rel.rel_date, relp.relp_promo } by new { cliID = rel.rel_cli_id, proId = relp.relp_pro_id } into g
                        select new
                        {
                          cliID = g.Key.cliID,
                          proID = g.Key.proId,
                          DNReleve = g.Max(s => s.relp_etat == 3 || s.relp_etat == 1 ? 1 : 0),
                          count_releve = g.Count(),
                          relp_pvc_avg = g.Average(a => a.relp_pvc) ?? 0,
                          date_dernier_releve = g.Max(m => m.rel_date),
                          relp_promo = g.Max(m => m.relp_promo > 0 ? 1 : 0)
                        };


      // Join all DN ( numeric data ?)
      var megaFull = from cpl in CliProAll
                     from cmd in commandes.Where(w => w.cliId == cpl.cliid && w.ProID == cpl.proId).DefaultIfEmpty()
                     from assort in assortiments.Where(w => w.cliId == cpl.cliid && w.proId == cpl.proId).DefaultIfEmpty()
                     from rel in prodReleves.Where(w => w.cliID == cpl.cliid && w.proID == cpl.proId).DefaultIfEmpty()
                     select new
                     {
                       cpl.cliid,
                       cpl.proId,
                       ancd_nb_presence = (int?)cmd.ancd_nb_presence ?? 0,
                       pvc_moy = cmd.ancd_pvc_moy ?? ((decimal?)rel.relp_pvc_avg) ?? 0,
                       promoInsideTry = (int?)cmd.promoInsideTry ?? 0,
                       assort.classClient,
                       count_releve = (int?)rel.count_releve ?? 0,
                       rel.date_dernier_releve,
                       DNReleve = (int?)rel.DNReleve ?? 0,
                       relp_promo = (int?)rel.relp_promo ??0,
                       DNcmd = ((int?)cmd.ancd_nb_presence ?? 0) > 0,
                       PresenceNeeded = (bool?)(assort.classClient == "P") ?? false
                     };

      // Super join 
      var final = from mf in megaFull
                  join produit in _context.PRO_Produits on mf.proId equals produit.pro_id
                  join sseg in _context.SSEG_Sous_Segment on produit.pro_sseg_id equals sseg.sseg_id
                  join seg in _context.SEG_Segment on sseg.sseg_seg_id equals seg.seg_id
                  join marche in _context.MAR_Marche on seg.seg_mar_id equals marche.mar_id
                  join client in _context.CLI_Clients on mf.cliid equals client.cli_id
                  join adresse in _context.ADR_Adresses on client.cli_adr_id equals adresse.adr_id
                  from marque in _context.MARQ_Marques.Where(w => produit.pro_mar_id == w.marq_id).DefaultIfEmpty()
                  from cliFDV in _context.CLIFDV_Clients_FDV.Where(w => mf.cliid == w.CLIFDV_cli_id && w.CLIFDV_fdv_id == fdvId).DefaultIfEmpty()
                  where client.cli_id == 24028
                  select new LinearSurveyModel()
                  {
                    //Produit
                    ProNom = produit.pro_nom,
                    ProMarque = marque.marq_libelle,
                    ProSSeg = sseg.sseg_libelle,
                    ProSeg = seg.seg_libelle,
                    ProMarche = marche.mar_libelle,
                    ProCode = produit.pro_code,
                    ProTarif1 = produit.pro_tarif1 ?? 0,
                    ProTarif2 = produit.pro_tarif2 ?? 0,
                    ProTarif3 = produit.pro_tarif3 ?? 0,
                    //Client                      
                    cli_id = client.cli_id,  
                    CliNomSociete = client.cli_nom_societe,
                    CliCode1 = client.cli_code1,
                    CliCode2 = client.cli_code2,
                    CliSurface = client.cli_surface,
                    clifdv_top = cliFDV.CLIFDV_top,
                    clifdv_top2 = cliFDV.CLIFDV_top2,
                    adrCP = client.ADR_Adresses.adr_cp,
                    adrVille = client.ADR_Adresses.adr_ville,
                    adrDepartement = client.ADR_Adresses.adr_cp.Substring(0, 2),
                    CliCentrale = client.cli_centrale,

                    //DN etc
                    PresenceNeeded = mf.PresenceNeeded ? 1 : 0,
                    Dn = mf.DNcmd || mf.DNReleve > 0 ? 1 : 0,
                    Dn_cmd = mf.DNcmd ? 1 : 0,
                    Dn_releve = mf.DNReleve,
                    Dn_assortiment = (mf.DNcmd && mf.PresenceNeeded) ? 1 : 0,
                    PresenceNeededOuPicking = (mf.DNcmd || mf.PresenceNeeded) ? 1 : 0,
                    Dn_picking = (mf.DNcmd && !mf.PresenceNeeded) ? 1 : 0,
                    Dn_optimise_hors_promo = (mf.promoInsideTry + mf.relp_promo == 0) && (mf.DNcmd || mf.DNReleve > 0) ? 1 : 0,
                    pvc_moy = mf.pvc_moy,
                    ecart_PMCP = (mf.pvc_moy - produit.pro_tarif2) / produit.pro_tarif2 ?? 0,
                    ecart_PMC = (mf.pvc_moy - produit.pro_tarif2) ?? 0,
                    count_releve = mf.count_releve,
                    client_unit_releve = mf.count_releve > 0,
                    date_dernier_releve = mf.date_dernier_releve,
                    promoInside = mf.promoInsideTry + mf.relp_promo >0,
                  };

@maumar
Copy link
Contributor

maumar commented Aug 31, 2016

@DSorin I'm still unable to repro this. Used the project you provided and the queries above - getting no exception, and getting following sqls (i collapsed individual projections into *, for brevity)

query form the project:

SELECT [i].*, [i.AncdPro].*, [i.AncdPro.ProMar].*
FROM [ANCD_analyse_commandes_detail] AS [i]
INNER JOIN [PRO_Produits] AS [i.AncdPro] ON [i].[ancd_pro_id] = [i.AncdPro].[pro_id]
LEFT JOIN [MARQ_Marques] AS [i.AncdPro.ProMar] ON [i.AncdPro].[pro_mar_id] = [i.AncdPro.ProMar].[marq_id]
ORDER BY [i.AncdPro].[pro_mar_id]

(this projects everything and Take(100) is done on the client due to #4588

CliProAll:

SELECT [client].[cli_id], [produit].[pro_id]
FROM [CLI_Clients] AS [client]
CROSS JOIN [PRO_Produits] AS [produit]
WHERE EXISTS (
    SELECT 1
    FROM [UCD_UniversClientsDetails] AS [a]
    WHERE ([a].[ucd_uc_id] = 1) AND ([a].[ucd_cli_id] = [client].[cli_id])) AND EXISTS (
    SELECT 1
    FROM [UPD_UniversProduitsDetails] AS [a0]
    WHERE ([a0].[upd_up_id] = 2) AND ([a0].[upd_pro_id] = [produit].[pro_id]))

commandes:

SELECT [ancd].*, [anc].*, [anp].*
FROM [ANCD_analyse_commandes_detail] AS [ancd]
INNER JOIN [ANC_analyse_commandes] AS [anc] ON [ancd].[ancd_anc_id] = [anc].[anc_id]
INNER JOIN [ANP_Analyse_Periode] AS [anp] ON ([anc].[anc_anp_id] = [anp].[ANP_id]) AND (2 = [anp].[ANP_type])
WHERE ((EXISTS (
    SELECT 1
    FROM [UCD_UniversClientsDetails] AS [a]
    WHERE ([a].[ucd_uc_id] = @__filterldcUcId_0) AND ([a].[ucd_cli_id] = [anc].[anc_cli_id])) AND EXISTS (
    SELECT 1
    FROM [UPD_UniversProduitsDetails] AS [a0]
    WHERE ([a0].[upd_up_id] = @__filterldcUcId_1) AND ([a0].[upd_pro_id] = [ancd].[ancd_pro_id]))) AND ([anp].[ANP_date_debut] >= @__filter_FilterDateDebut_2)) AND ([anp].[ANP_date_fin] <= @__filter_FilterDateFin_3)
ORDER BY [ancd].[ancd_pro_id], [anc].[anc_cli_id]

assortiments:

SELECT [pro].[pro_id], [cli].[cli_id], CASE
    WHEN [astc].[ASTC_NumClass] = 1
    THEN COALESCE([astd].[ASTD_Class1], N'Z') ELSE CASE
        WHEN [astc].[ASTC_NumClass] = 2
        THEN COALESCE([astd].[ASTD_Class2], N'Z') ELSE CASE
            WHEN [astc].[ASTC_NumClass] = 3
            THEN COALESCE([astd].[ASTD_Class3], N'Z') ELSE CASE
                WHEN [astc].[ASTC_NumClass] = 4
                THEN COALESCE([astd].[ASTD_Class4], N'Z') ELSE CASE
                    WHEN [astc].[ASTC_NumClass] = 5
                    THEN COALESCE([astd].[ASTD_Class5], N'Z') ELSE CASE
                        WHEN [astc].[ASTC_NumClass] = 6
                        THEN COALESCE([astd].[ASTD_Class6], N'Z') ELSE CASE
                            WHEN [astc].[ASTC_NumClass] = 7
                            THEN COALESCE([astd].[ASTD_Class7], N'Z') ELSE CASE
                                WHEN [astc].[ASTC_NumClass] = 8
                                THEN COALESCE([astd].[ASTD_Class8], N'Z') ELSE CASE
                                    WHEN [astc].[ASTC_NumClass] = 9
                                    THEN COALESCE([astd].[ASTD_Class9], N'Z') ELSE N'Z'
                                END
                            END
                        END
                    END
                END
            END
        END
    END
END
FROM [CLI_Clients] AS [cli]
INNER JOIN [ASTD_AssortData] AS [astd] ON [cli].[cli_assorID] = [astd].[ASTD_IdAssort]
INNER JOIN [PRO_Produits] AS [pro] ON [astd].[ASTD_IDProduit] = [pro].[pro_id]
INNER JOIN [SSEG_Sous_Segment] AS [sseg] ON [pro].[pro_sseg_id] = [sseg].[sseg_id]
INNER JOIN [ASTC_AssortClient] AS [astc] ON ([sseg].[sseg_seg_id] = [astc].[ASTC_IdSegment]) AND ([cli].[cli_id] = [astc].[ASTC_IdClient])
WHERE EXISTS (
    SELECT 1
    FROM [UCD_UniversClientsDetails] AS [a]
    WHERE ([a].[ucd_uc_id] = 1) AND ([a].[ucd_cli_id] = [cli].[cli_id])) AND EXISTS (
    SELECT 1
    FROM [UPD_UniversProduitsDetails] AS [a0]
    WHERE ([a0].[upd_up_id] = 1) AND ([a0].[upd_pro_id] = [pro].[pro_id]))

prodReleves:

SELECT [relp].*, [rel].*, [relf].*
FROM [RELP_Releve_Produit] AS [relp]
INNER JOIN [REL_Releve] AS [rel] ON [relp].[relp_rel_id] = [rel].[rel_id]
INNER JOIN [RELF_Releve_Fdv] AS [relf] ON ([rel].[rel_id] = [relf].[relf_rel_id]) AND (@__fdvId_0 = [relf].[relf_fdv_id])
WHERE (((([rel].[rel_type] = 1) AND ([rel].[rel_date] >= @__filter_FilterDateDebut_1)) AND ([rel].[rel_date] <= @__filter_FilterDateFin_2)) AND EXISTS (
    SELECT 1
    FROM [UCD_UniversClientsDetails] AS [a]
    WHERE ([a].[ucd_uc_id] = @__filterldcUcId_3) AND ([a].[ucd_cli_id] = [rel].[rel_cli_id]))) AND EXISTS (
    SELECT 1
    FROM [UPD_UniversProduitsDetails] AS [a0]
    WHERE ([a0].[upd_up_id] = @__filterldcUcId_4) AND ([a0].[upd_pro_id] = [relp].[relp_pro_id]))
ORDER BY [rel].[rel_cli_id], [relp].[relp_pro_id]

I tried it on current bits as well as released version and seeing similar results. What version of EF core are you using?

If you run the query from the project you zipped (using that model you provided) against your actual database, you see the exception?

The data itself shouldn't really matter - the error you are seeing is in the query compilation phase, so I'm really curious whats the difference here.

@DSorin
Copy link
Author

DSorin commented Aug 31, 2016

@maumar ,

Yes i still get the exception with the console app I provided.
Exception :

L'exception System.InvalidCastException n'a pas été gérée
  HResult=-2147467262
  Message=Unable to cast object of type 'System.Linq.Expressions.Expression`1[System.Func`2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[Microsoft.EntityFrameworkCore.Storage.ValueBuffer,Microsoft.EntityFrameworkCore.Storage.ValueBuffer],Microsoft.EntityFrameworkCore.Storage.ValueBuffer],Microsoft.EntityFrameworkCore.Storage.ValueBuffer],ConsoleApp1.Models.ProProduits],System.Collections.Generic.IEnumerable`1[ConsoleApp1.Models.MarqMarques]],ConsoleApp1.Models.MarqMarques],System.Int32]]' to type 'System.Linq.Expressions.ConstantExpression'.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
       at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.QueryFlattener.Flatten(MethodCallExpression methodCallExpression)
       at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.OptimizeJoinClause(JoinClause joinClause, QueryModel queryModel, Int32 index, Action baseVisitAction, MethodInfo operatorToFlatten, Boolean outerJoin)
       at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitJoinClause(JoinClause joinClause, QueryModel queryModel, Int32 index)
       at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection`1 bodyClauses, QueryModel queryModel)
       at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
       at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
       at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
       at Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
       at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass19_0`1.<CompileQuery>b__0()
       at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
       at Remotion.Linq.QueryableBase`1.GetEnumerator()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
       at ConsoleApp1.Program.Main(String[] args) in C:\Users\dorian.ANDEOL\Documents\Visual Studio 2015\Projects\ConsoleApp1\Program.cs:line 24
  InnerException: 

AND the log :

Compiling query model: 'from AncdAnalyseCommandesDetail s in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.AncdAnalyseCommandesDetail]) select new <>f__AnonymousType0`12(ProId = [s].AncdProId, ProNom = [s].AncdPro.ProNom, ProductBrandName = [s].AncdPro.ProMar.MarqLibelle, ProSSeg = [s].AncdPro.ProSseg.SsegLibelle, ProSeg = [s].AncdPro.ProSseg.SsegSeg.SegLibelle, ProMarche = [s].AncdPro.ProSseg.SsegSeg.SegMar.MarLibelle, ClidId = [s].AncdAnc.AncCliId, CliNom = [s].AncdAnc.AncCli.CliNomSociete, DN = True, AncdPuMoy = [s].AncdPuMoy, AncdPvcMoy = [s].AncdPvcMoy, PeriodeLibelle = [s].AncdAnc.AncAnp.AnpLibelle) => Take(__p_0)'
Optimized query model: 'from AncdAnalyseCommandesDetail s in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.AncdAnalyseCommandesDetail]) join AncAnalyseCommandes s.AncdAnc in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.AncAnalyseCommandes]) on Property([s], "AncdAncId") equals Property([s.AncdAnc], "AncId") join CliClients s.AncdAnc.AncCli in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.CliClients]) on Property([s.AncdAnc], "AncCliId") equals Property([s.AncdAnc.AncCli], "CliId") join AnpAnalysePeriode s.AncdAnc.AncAnp in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.AnpAnalysePeriode]) on Property([s.AncdAnc], "AncAnpId") equals Property([s.AncdAnc.AncAnp], "AnpId") join ProProduits s.AncdPro in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.ProProduits]) on Property([s], "AncdProId") equals Property([s.AncdPro], "ProId") join MarqMarques s.AncdPro.ProMar in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.MarqMarques]) on IIF(([s.AncdPro] != null), Property([s.AncdPro], "ProMarId"), null) equals Convert(Property([s.AncdPro.ProMar], "MarqId")) into IEnumerable`1 s.AncdPro.ProMar_group from MarqMarques s.AncdPro.ProMar in {[s.AncdPro.ProMar_group] => DefaultIfEmpty()} join SsegSousSegment s.AncdPro.ProSseg in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.SsegSousSegment]) on Property([s.AncdPro], "ProSsegId") equals Property([s.AncdPro.ProSseg], "SsegId") join SegSegment s.AncdPro.ProSseg.SsegSeg in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.SegSegment]) on Property([s.AncdPro.ProSseg], "SsegSegId") equals Property([s.AncdPro.ProSseg.SsegSeg], "SegId") join MarMarche s.AncdPro.ProSseg.SsegSeg.SegMar in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.MarMarche]) on Property([s.AncdPro.ProSseg.SsegSeg], "SegMarId") equals Property([s.AncdPro.ProSseg.SsegSeg.SegMar], "MarId") select new <>f__AnonymousType0`12(ProId = [s].AncdProId, ProNom = [s.AncdPro].ProNom, ProductBrandName = IIF(([s.AncdPro.ProMar] != null), [s.AncdPro.ProMar].MarqLibelle, null), ProSSeg = [s.AncdPro.ProSseg].SsegLibelle, ProSeg = [s.AncdPro.ProSseg.SsegSeg].SegLibelle, ProMarche = [s.AncdPro.ProSseg.SsegSeg.SegMar].MarLibelle, ClidId = [s.AncdAnc].AncCliId, CliNom = [s.AncdAnc.AncCli].CliNomSociete, DN = True, AncdPuMoy = [s].AncdPuMoy, AncdPvcMoy = [s].AncdPvcMoy, PeriodeLibelle = [s.AncdAnc.AncAnp].AnpLibelle) => Take(__p_0)'
The LINQ expression 'DefaultIfEmpty()' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.
The LINQ expression 'DefaultIfEmpty()' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.
The LINQ expression 'from MarqMarques s.AncdPro.ProMar in {[s.AncdPro.ProMar_group] => DefaultIfEmpty()}' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.
The LINQ expression 'join SsegSousSegment s.AncdPro.ProSseg in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ConsoleApp1.Models.SsegSousSegment]) on Property([s.AncdPro], "ProSsegId") equals Property([s.AncdPro.ProSseg], "SsegId")' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.

The data itself shouldn't really matter - the error you are seeing is in the query compilation phase, so I'm really curious whats the difference here.

After reading your message i tried to played a little with my data, and =>
1/ I removed the .Include and it's correctly detecting what navigation property to include so that cool
2/ If i do that i got the exeption :


      var qsd = _context.AncdAnalyseCommandesDetail
    .Select(s => new
    {
      ProId = s.AncdProId,
      ProNom = s.AncdPro.ProNom,
      ProductBrandName = s.AncdPro.ProMar.MarqLibelle,
      ProSSeg = s.AncdPro.ProSseg.SsegLibelle,
      ProSeg = s.AncdPro.ProSseg.SsegSeg.SegLibelle,
      ProMarche = s.AncdPro.ProSseg.SsegSeg.SegMar.MarLibelle,
      ClidId = s.AncdAnc.AncCliId,
      CliNom = s.AncdAnc.AncCli.CliNomSociete,
      DN = true,
      AncdPuMoy = s.AncdPuMoy,
      AncdPvcMoy = s.AncdPvcMoy,
      PeriodeLibelle = s.AncdAnc.AncAnp.AnpLibelle,
    }).Take(100000).ToList();

But if i move the "ProductBrandName = s.AncdPro.ProMar.MarqLibelle," as last in the anonymus select
as this :

  var qsd = _context.AncdAnalyseCommandesDetail
    .Select(s => new
    {
      ProId = s.AncdProId,
      ProNom = s.AncdPro.ProNom,
      ProSSeg = s.AncdPro.ProSseg.SsegLibelle,
      ProSeg = s.AncdPro.ProSseg.SsegSeg.SegLibelle,
      ProMarche = s.AncdPro.ProSseg.SsegSeg.SegMar.MarLibelle,
      ClidId = s.AncdAnc.AncCliId,
      CliNom = s.AncdAnc.AncCli.CliNomSociete,
      DN = true,
      AncdPuMoy = s.AncdPuMoy,
      AncdPvcMoy = s.AncdPvcMoy,
      PeriodeLibelle = s.AncdAnc.AncAnp.AnpLibelle,
      ProductBrandName = s.AncdPro.ProMar.MarqLibelle,
    }).Take(100000).ToList();

I got one query generated !

SELECT [s.AncdPro].[pro_id], [s.AncdPro].[LIAISON], [s.AncdPro].[pro_allow_commande], [s.AncdPro].[pro_allow_releve], [s.AncdPro].[pro_argumentaire], [s.AncdPro].[pro_arret_debut], [s.AncdPro].[pro_arret_fin], [s.AncdPro].[pro_code], [s.AncdPro].[pro_code1], [s.AncdPro].[pro_code2], [s.AncdPro].[pro_code_vmh], [s.AncdPro].[pro_coef], [s.AncdPro].[pro_colisage], [s.AncdPro].[pro_commentaire], [s.AncdPro].[pro_contrat_date], [s.AncdPro].[pro_cor_val], [s.AncdPro].[pro_date_publication], [s.AncdPro].[pro_date_publication_fin], [s.AncdPro].[pro_date_rupture], [s.AncdPro].[pro_date_rupture_fin], [s.AncdPro].[pro_date_stock], [s.AncdPro].[pro_del], [s.AncdPro].[pro_del_date], [s.AncdPro].[pro_del_usr_id], [s.AncdPro].[pro_disponible_debut], [s.AncdPro].[pro_disponible_fin], [s.AncdPro].[pro_dlc], [s.AncdPro].[pro_ean], [s.AncdPro].[pro_ean1], [s.AncdPro].[pro_ean2], [s.AncdPro].[pro_estampCEE], [s.AncdPro].[pro_fabricant], [s.AncdPro].[pro_fou_id], [s.AncdPro].[pro_gratuit], [s.AncdPro].[pro_groupRef], [s.AncdPro].[pro_hauteur], [s.AncdPro].[pro_hip_id], [s.AncdPro].[pro_hors_stats], [s.AncdPro].[pro_largeur], [s.AncdPro].[pro_lot], [s.AncdPro].[pro_mad], [s.AncdPro].[pro_mar_id], [s.AncdPro].[pro_nbImage], [s.AncdPro].[pro_ndp], [s.AncdPro].[pro_nom], [s.AncdPro].[pro_nom_court], [s.AncdPro].[pro_nomenclature_check], [s.AncdPro].[pro_nouveaute_debut], [s.AncdPro].[pro_nouveaute_fin], [s.AncdPro].[pro_noyaudur], [s.AncdPro].[pro_ordre], [s.AncdPro].[pro_ordre2], [s.AncdPro].[pro_pays_fabrication], [s.AncdPro].[pro_pcb], [s.AncdPro].[pro_poids_unitaire], [s.AncdPro].[pro_portionNb], [s.AncdPro].[pro_portionPoids], [s.AncdPro].[pro_ppr], [s.AncdPro].[pro_pro_id], [s.AncdPro].[pro_profondeur], [s.AncdPro].[pro_qte_commande_max], [s.AncdPro].[pro_qte_commande_min], [s.AncdPro].[pro_qte_stock], [s.AncdPro].[pro_renovation_debut], [s.AncdPro].[pro_renovation_fin], [s.AncdPro].[pro_sseg_id], [s.AncdPro].[pro_tarif1], [s.AncdPro].[pro_tarif2], [s.AncdPro].[pro_tarif3], [s.AncdPro].[pro_tva], [s.AncdPro].[pro_uniteCommande], [s.AncdPro].[pro_uniteFacture], [s.AncdPro].[pro_Upoids], [s.AncdPro].[pro_valeur1], [s.AncdPro].[pro_valeur2], [s.AncdPro].[pro_vmh_hm], [s.AncdPro].[pro_vmh_sm], [s.AncdPro].[pro_vmm_hm], [s.AncdPro].[pro_vmm_sm], [s.AncdPro].[pro_zoom], [s.AncdPro].[STATUS], [s.AncdPro].[VERSION], [s.AncdPro.ProMar].[marq_id], [s.AncdPro.ProMar].[marq_code1], [s.AncdPro.ProMar].[marq_concurrent], [s.AncdPro.ProMar].[marq_couleur], [s.AncdPro.ProMar].[marq_fabricant2Code], [s.AncdPro.ProMar].[marq_fabricant2Nom], [s.AncdPro.ProMar].[marq_fabricantId], [s.AncdPro.ProMar].[marq_fichier], [s.AncdPro.ProMar].[marq_libelle], [s.AncdPro.ProMar].[STATUS], [s.AncdPro.ProMar].[VERSION], [s].[ancd_pro_id], [s.AncdPro.ProSseg].[sseg_libelle], [s.AncdPro.ProSseg.SsegSeg].[seg_libelle], [s.AncdPro.ProSseg.SsegSeg.SegMar].[mar_libelle], [s.AncdAnc].[anc_cli_id], [s.AncdAnc.AncCli].[cli_nom_societe], [s].[ancd_pu_moy], [s].[ancd_pvc_moy], [s.AncdAnc.AncAnp].[ANP_libelle]
FROM [ANCD_analyse_commandes_detail] AS [s]
INNER JOIN [ANC_analyse_commandes] AS [s.AncdAnc] ON [s].[ancd_anc_id] = [s.AncdAnc].[anc_id]
INNER JOIN [CLI_Clients] AS [s.AncdAnc.AncCli] ON [s.AncdAnc].[anc_cli_id] = [s.AncdAnc.AncCli].[cli_id]
INNER JOIN [ANP_Analyse_Periode] AS [s.AncdAnc.AncAnp] ON [s.AncdAnc].[anc_anp_id] = [s.AncdAnc.AncAnp].[ANP_id]
INNER JOIN [PRO_Produits] AS [s.AncdPro] ON [s].[ancd_pro_id] = [s.AncdPro].[pro_id]
INNER JOIN [SSEG_Sous_Segment] AS [s.AncdPro.ProSseg] ON [s.AncdPro].[pro_sseg_id] = [s.AncdPro.ProSseg].[sseg_id]
INNER JOIN [SEG_Segment] AS [s.AncdPro.ProSseg.SsegSeg] ON [s.AncdPro.ProSseg].[sseg_seg_id] = [s.AncdPro.ProSseg.SsegSeg].[seg_id]
INNER JOIN [MAR_Marche] AS [s.AncdPro.ProSseg.SsegSeg.SegMar] ON [s.AncdPro.ProSseg.SsegSeg].[seg_mar_id] = [s.AncdPro.ProSseg.SsegSeg.SegMar].[mar_id]
LEFT JOIN [MARQ_Marques] AS [s.AncdPro.ProMar] ON [s.AncdPro].[pro_mar_id] = [s.AncdPro.ProMar].[marq_id]
ORDER BY [s.AncdPro].[pro_mar_id]

So there a select * on PRO_Produits and MARQ_Marques but not on other table.
I just can't figures why...

My guest would be, since we got 2 table with closed named "MAR_Marche" and "MARQ_Marques"
the property are wrongly named in database => pro_mar_id in PRO_Produits is a ForeignKey on MARQ_Marques.
And in "SEG_Segment" you got a property named "seg_mar_id" ForeignKey on MAR_Marche.
=> Could their be a confusion at some point ?
Since it's automated i don't think so but ...

Hope it's help you

@maumar
Copy link
Contributor

maumar commented Aug 31, 2016

@DSorin thanks for the extra info, now I was able to figure it out what's going on.

Issue you are seeing has been fixed after RTM has been released:
9a34e1d

As to the query that works for you and why some entities are projecting all the columns, MARQ_Marques is projected in its entirety because of this issue: #4588
Basically we don't know how to properly translate LOJ so it's materialized on the client and for that we need all its properties.

PRO_Produits is materialized because when we join Produits with Marques, we generate the following query fragment first:

{join MarqMarques s.AncdPro.ProMar in value(EntityQueryable`1[MarqMarques]) on IIF(([s.AncdPro] != null), Property([s.AncdPro], "ProMarId"), null) equals Convert(Property([s.AncdPro.ProMar], "MarqId")) into IEnumerable`1 s.AncdPro.ProMar_group}

We add null protection logic for [s.AncdPro] element before accessing its property "ProMarId". Sometimes this is necessary, e.g. the outer collection is a subquery (and you don't know if there are any nulls in the collection or not - consider example in #6429). When we add the null protection logic, we need to know if a given object is null, and for that we currently need to materialize the object entirely also. But in this case null-ref won't happen so we should be smarter about it and only add it when it's really needed. I will address this in #6429.

As for your situation I would recommend using as few optional navigations as you can, until #4588 is fixed (scheduled to be fixed in 1.1.0 currently, lots of people are hitting it). Since your database has large number of rows you will get pretty bad performance in any query with optional navigation, if that query has any sort of filter/paging.

@maumar
Copy link
Contributor

maumar commented Aug 31, 2016

closing this as the issues have either been fixed in our current codebase or are tracked elsewhere

@maumar maumar closed this as completed Aug 31, 2016
@maumar maumar added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. closed-duplicate and removed type-investigation labels Aug 31, 2016
@ajcvickers ajcvickers modified the milestones: 1.1.0-preview1, 1.1.0 Oct 15, 2022
@ajcvickers ajcvickers added type-unknown and removed closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-unknown labels Oct 15, 2022
@ajcvickers ajcvickers removed this from the 1.1.0 milestone Oct 18, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants