Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Wat gebeurd er als je dit doet?
1
| var q = context.someTable.Count(s => s.SomeEnum.Value == (int)SomeEnum.SomeValue); |
Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Maar het kwam er op neer dat de Linq provider van EF kan niet goed overweg kan met Func's. De reden waarom ontgaat me even.
Overigens kun je dit wel deels omzeilen blijkbaar: merk op, 2 jaar oude vraag. maar ik denk dat de workaround nog valide is, mits je dit probleem ook hebt in de gevallen waarbij je niet Count gebruikt.
http://stackoverflow.com/...ternal-net-framework-data
[ Voor 41% gewijzigd door D-Raven op 09-01-2012 15:55 ]
Ik denk dat ik eruit ben. Als ik de "var" verander in een "dynamic" krijg ik netjes de foutmelding "Extension methods cannot be dynamically dispatched". Doordat ik geen tussentijdse variabele gebruik denk ik dat de compiler daarom at runtime een type moet gaan verzinnen en dat werkt dan dus niet. Met de tussentijdse variabele heeft ie wel de type informatie, en kan hij wel zijn werk gewoon doen.
Iets anders kan ik me niet bedenken.
[ Voor 81% gewijzigd door Grijze Vos op 09-01-2012 16:04 ]
Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Hoe dan ook, het blijft vreemd....
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten
Misschien was dit een probleem bij LINQ.
Schiet tussen de palen en je scoort!
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com
Dat is precies de verklaring die ik destijds in Jon Skeet's post gelezen had.EfBe schreef op maandag 09 januari 2012 @ 17:00:
De oorzaak is het feit dat bij de 1e 'Test' gezien wordt als onderdeel van de query. De 2e, die goed gaat, stopt de lambda in een variabele die in de expression tree te zien is als een normale constant en ge-inlined wordt. Dit kan niet bij de 1e versie, want 'Test' wordt daar gezien als een functie die onderdeel is van de query en dus probeert de provider die te vertalen naar een SQL statement. Dat dit resulteert in een internal error en niet in een normale error is Microsoft's schuld, maar ja.... entity framework...
Is ook een van de meest voorkomende fouten bij linq en 1 van de rederen dat Linq soms best wel sucked. De andere is een loop variabele (foreach variable) in een closure gebruiken.D-Raven schreef op maandag 09 januari 2012 @ 17:04:
[...]
Dat is precies de verklaring die ik destijds in Jon Skeet's post gelezen had.
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com
Die loop variabele vind ik nog wel redelijk transparant, nouja, laat ik het zo zeggen. Hij is goed uit te leggen.EfBe schreef op maandag 09 januari 2012 @ 17:08:
[...]
Is ook een van de meest voorkomende fouten bij linq en 1 van de rederen dat Linq soms best wel sucked. De andere is een loop variabele (foreach variable) in een closure gebruiken.
De fout waar we het over hebben is een stuk lastiger. IMO.
Helemaal niet.D-Raven schreef op maandag 09 januari 2012 @ 19:27:
[...]
De fout waar we het over hebben is een stuk lastiger. IMO.
Kijk eens mee. Wat is de daadwerkelijke expression? Is dat:
1
| s => s.SomeEnum.Value == flag |
of is de expression
1
| Test((int)SomeEnum.SomeValue) |
Het laatste. Het eerste zou kunnen, maar het tweede is gewoon geldig, en daarom wordt dit compile time gebruikt.
[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]
Simpel testvoorbeeldje:
1
2
3
4
5
6
7
8
9
10
11
12
| public static Expression<Func<Entity1, Boolean>> Test(int flag) { return s => s.Id == flag; } ... var a = new Model1Container(); a.Entity1.AddObject(new Entity1() { Id = 1, Name = "test" }); a.SaveChanges(); int noproblem = a.Entity1.Count(Test(1)); var expr = Test(1); int good = a.Entity1.Count(x => x.Id == a.Entity1.Count(expr)); int bad = a.Entity1.Count(x => x.Id == a.Entity1.Count(Test(1))); |
Deze code crashed pas bij bad met error 1025.
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten
Laat ik het zo zeggen, closures krijg ik nog wel uitgelegd zonder uitgebreid voorbeeld.
Dit laatste is gewoon een stuk lastiger uit te leggen. Misschien leg ik het dan slecht uit oid, maar ik heb meerdere collega's die hier gewoon geen vinger achter krijgen en uiteindelijk maar onthouden dat als ze zoiets aan het doen zijn, ze dit eerst maar in een aparte variabele moeten stoppen.. en de rest maar vergeten.
Je hebt gelijk, ik heb in de oorspronkelijke code een inner gedeelte. Ik had de code wat gereduceerd om het probleem te illustreren.pedorus schreef op maandag 09 januari 2012 @ 21:36:
Het probleem is ook dat de code in de TS de fout helemaal niet geeft. Je hebt een inner gedeelte nodig, anders wordt er gewoon meteen geëvalueerd.
Het rare is dat de code met tussenvariabele wel gewoon correct vertaald wordt in een stukje SQL code.
[ Voor 12% gewijzigd door Grijze Vos op 10-01-2012 09:38 ]
Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Is niet raar, omdat de variabele het resultaat van de method aanroep heeft. Maar wanneer jij een method call in een linq query gebruikt, wordt die functie niet aangeroepen. Zet maar een breakpoint in Test en run het 1e stukje code. Je zult zien dat Test nooit gerund wordt. Wat gebeurt is dat een MethodCallExpression wordt ge-creeerd in de expression tree als argument voor Count. De linq provider gaat die method call dan evalueren en gaat zoeken naar een vertaling ervoor. Dat dit crasht is eigenlijk een bug: het had een fatsoenlijke foutmelding moeten geven, nl. geen mapping bekend voor 'Test'. Je kunt nl. 'Test' ook mappen op een SQL functie, en die vertaling wordt dan gebruikt als vertaling voor de MethodCallExpression.Grijze Vos schreef op dinsdag 10 januari 2012 @ 08:57:
[...]
Je hebt gelijk, ik heb in de oorspronkelijke code een inner gedeelte. Ik had de code wat gereduceerd om het probleem te illustreren.
Het rare is dat de code met tussenvariabele wel gewoon correct vertaald wordt in een stukje SQL code.
Een linq provider kan normaliter wel local methods evalueren overigens. Dit noemt men 'funcletizing', omdat de oorspronkelijke eerste visitor die dit deed zo heette (in linq to sql). Deze zoekt naar expression tree sub-bomen die niet afhankelijk zijn van query sources. Deze sub-bomen worden dan in-memory gecompileerd (omdat hun uitkomst niet afhankelijk is van de data afkomstig van 1 of meerdere sequences in de query) en gerund en de uitkomst wordt dan gebruikt in de feitelijke expression tree. Jouw 'Test' zou hier eigenlijk wel voor in aanmerking moeten komen.
Als ik tijd heb zal ik kijken of mijn linq to llblgen provider dit wel goed oplost.
[ Voor 23% gewijzigd door EfBe op 10-01-2012 09:48 ]
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com
Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Zoals te verwachten was werkt het gewoonGrijze Vos schreef op dinsdag 10 januari 2012 @ 09:52:
Dat is dus wat ik zou verwachten dat ie zou doen. Ik ben benieuwd of LLBLGen3 dit wel goed doet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| private enum EmployeeId { Sjaak = 1, Piet = 2, } public static System.Linq.Expressions.Expression<Func<OrderEntity, bool>> TestMethod(int value) { return o => o.EmployeeId == value; } [Test] public void GetCountOfOrdersWithFunctionTest() { using(DataAccessAdapter adapter = new DataAccessAdapter()) { LinqMetaData metaData = new LinqMetaData(adapter); var amount = metaData.Order.Count(TestMethod((int)EmployeeId.Sjaak)); Assert.AreEqual(109, amount); } } |
Geeft:
: Initial expression to process:
value(SD.LLBLGen.Pro.LinqSupportClasses.DataSource2`1[NW26.Adapter.EntityClasses.OrderEntity]).Count(o => (o.EmployeeId = Convert(value(LinqTester.AdapterTests.EntityFetches+<>c__DisplayClass0).value)))
Generated Sql query:
Query: SELECT TOP 1 COUNT(*) AS [LPAV_] FROM [Northwind].[dbo].[Orders] [LPLA_1] WHERE ( ( ( ( [LPLA_1].[EmployeeID] = @p1))))
Parameter: @p1 : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.
Wat me opviel was overigens dat de expression tree al de TestMethod call result in zich had. De expression tree was nl. exact hetzelfde wanneer ik dit deed:
1
2
3
4
5
6
7
8
9
10
11
12
| [Test] public void GetCountOfOrdersWithFunctionTest() { using(DataAccessAdapter adapter = new DataAccessAdapter()) { LinqMetaData metaData = new LinqMetaData(adapter); var expr = TestMethod((int)EmployeeId.Sjaak); var amount = metaData.Order.Count(expr); Assert.AreEqual(109, amount); } } |
Dus het lijkt erop dat er wat anders aan de hand is, of dat de code die je hebt opgegeven anders is dan ik heb gebruikt in mn bovenstaande voorbeeld.
(edit) het kan natuurlijk zijn dat in jouw geval de method niet is ge-inlined door de compiler en bij mij wel. Zal kijken of ik dat kan voorkomen met een attribute.
(edit) met [MethodImpl(MethodImplOptions.NoInlining)], komt nog steeds de method result als lambda in de expression te staan ipv de method call.
[ Voor 7% gewijzigd door EfBe op 10-01-2012 10:48 ]
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com
[ Voor 10% gewijzigd door R4gnax op 10-01-2012 13:19 ]
Jouw voorbeeld werkt ook met Entity Framework, je gebruikt nml geen functie binnen een lambda... (Net als het voorbeeld in de TS, zie mijn vorige post) Beetje flauwe manier van testen dus.
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten
aahhh, maar dat had ik gemist. Niet flauw, ik heb gewoon de TS query gebruikt. Ik zal met jouw voorbeeld opnieuw testen en kijken wat de expression tree is.pedorus schreef op dinsdag 10 januari 2012 @ 17:04:
[...]
Jouw voorbeeld werkt ook met Entity Framework, je gebruikt nml geen functie binnen een lambda... (Net als het voorbeeld in de TS, zie mijn vorige post) Beetje flauwe manier van testen dus.
(edit)
Expression tree is:
value(SD.LLBLGen.Pro.LinqSupportClasses.DataSource2`1[NW26.Adapter.EntityClasses.OrderEntity]).Count(x => (x.OrderId = value(LinqTester.AdapterTests.EntityFetches+<>c__DisplayClass2).metaData.Order.Count(TestMethod(1))))
wanneer ik doe:
1
2
3
4
5
6
7
8
9
10
11
| [Test] public void GetCountOfOrdersWithFunctionTest() { using(DataAccessAdapter adapter = new DataAccessAdapter()) { LinqMetaData metaData = new LinqMetaData(adapter); var amount = metaData.Order.Count(x=>x.OrderId == metaData.Order.Count(TestMethod((int)EmployeeId.Sjaak))); Assert.AreEqual(109, amount); } } |
en die crasht ook in mijn code, omdat hij een in-memory candidate (de testmethod call) niet verwacht op die plek, want die zou al ge-evalueerd moeten zijn. (bug te fixen voor mij, niet relevant verder voor deze thread).
De expression tree laat wel zien dat de method call naar TestMethod in de boom zit en dus wel degelijk de oorzaak is. Ook weer opgelost!
* EfBe gaat nu 'even' een bug fixen
(edit) F1x0red. (build 3.1.01102012)
1
2
3
| Generated Sql query: Query: SELECT TOP 1 COUNT(*) AS [LPAV_] FROM [Northwind].[dbo].[Orders] [LPLA_1] WHERE ( ( ( ( [LPLA_1].[OrderID] = (SELECT COUNT(*) AS [LPAV_] FROM [Northwind].[dbo].[Orders] [LPLA_2] WHERE ( ( [LPLA_2].[EmployeeID] = @p1))))))) Parameter: @p1 : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1. |
*pfew*
[ Voor 63% gewijzigd door EfBe op 10-01-2012 19:21 ]
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com
Ik heb maar besloten op die paar plekken waar ik het wou toepassen maar gewoon met de hand mijn expressies te schrijven. Waar ik het voor wilde gebruiken is het queryen van een integer die als backing field diende voor een Enum. Aangezien EF4.0 nog geen enum support heeft.R4gnax schreef op dinsdag 10 januari 2012 @ 13:18:
Wat je ook nog kunt doen is de expressie-expansie tools uit LinqKit gebruiken. Deze lopen over je gegenereerde expression tree heen en zetten automatisch aanroepen van externe expressies om in inline expressies. Dat werkt gewoon met Entity Framework of Linq-to-SQL queryables.
@EfBe, wanneer komt LLBLGen met een code first oplossing?
Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Niet, want code first is niet zinnig, wanneer je een designer hebt die dingen veel sneller voor je produceert dan code dat kan. En als je wilt typen, gebruik je toch quickmodel, statements tikken om je model te creeeren?:Grijze Vos schreef op donderdag 12 januari 2012 @ 14:58:
@EfBe, wanneer komt LLBLGen met een code first oplossing?
Customer.Orders 1n Order.Customer
en ik heb 2 entities gemaakt en 1 relatie met 2 navigators erop.
Code first lijkt leuk in het begin. Maar ga maar eens een 100+ entity model beheren vanuit 'code first' oogpunt. Het overzicht raak je snel kwijt. Voor de mensen die dat onzin vinden: je class model != entity model. Bij code first is dat wel het geval, en dat levert vroeg of laat toch problemen op, vooral nadat je code in productie is gegaan en wijzigingen niet zomaar kunnen worden doorgevoerd door 'even' de database opnieuw te creeeren. Bv: maak van een 1:n relatie een 1:1 relatie. Je database wijzigt op een onverwachte plek: je krijgt er een UC bij. Dit kun je niet even migreren in productie. Vanuit je 'code' lijkt het dan logisch dat dit 'even' doorgevoerd wordt maar voor je live model in je productie database is dat wel even andere koek: migratie van data, wat je eerst moet testen in een testomgeving alvorens dat uit te rollen op een productiedatabase.
Tuurlijk heeft EF code-first migrations, althans die komt binnenkort. Maar is dat wel wat je wilt? DB wijzigingen in C# code? Na een aantal wijzigingen op je model, zeker bij grotere modellen loopt dat op, wordt het een onoverzichtelijke brei. Het hele idee dat je 'in de code editor' wel even je model gaat bouwen is onzin, en alleen 'logisch' in de wereld van diegenen die liever dagen spenderen aan het met de hand schrijven van classes en mappings terwijl een programma dat veel sneller voor ze kan doen (en ook nog foutloos).
Maar ieder z'n kicks natuurlijk.
[ Voor 10% gewijzigd door EfBe op 12-01-2012 15:12 ]
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com
(Transformeren naar een EF model is vele malen complexer dan POCO's genereren.)
[ Voor 19% gewijzigd door Grijze Vos op 12-01-2012 15:16 ]
Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info
Je hebt dus louter entities en geen mappings / relational model data ? Je kunt dan niets anders dan code first gebruiken en je database volledig je model laten volgen. Lijkt me geen goede basis voor een onderhoudbaar systeem.Grijze Vos schreef op donderdag 12 januari 2012 @ 15:15:
Nee, ik wil eigenlijk alleen de persistence layer van een O/R mapper. Ik heb eigen modellen van waaruit ik code genereer. Ik genereer nu EF Code First vanuit deze modellen.
(Transformeren naar een EF model is vele malen complexer dan POCO's genereren.)
Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com