[C#/LINQ/EF4] Meer dan 1 .Include werkt soms wel, soms niet

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Om een database te migreren en daarbij een aantal velden toe te voegen schrijf ik een programma dat elk record uit de originele database plukt en deze opslaat in de nieuwe database. De database bestaat uit 3 tabellen. Een 'hoofdtabel' met een 0..* relatie naar de twee 'childtabellen'.

Om het simpel te houden en in memory te kunnen evalueren trek ik alle data in delen uit de originele tabel op een volgende manier:

code:
1
2
3
4
5
6
var oldObjects = oldDb.tblMain
                     .Include("tblChild1")
                     .Include("tblChild2")
                     .Where(x => x.Migrated == null)
                     .Take(bufferSize)
                     .ToList();


De oldObjectes worden per aantal 'bufferSize' opgehaald als ze nog niet geprocessed zijn (want 'migrated==null'), geanalyseerd, naar een nieuwe database ge-insert en vervolgens doe ik:

code:
1
2
3
foreach (var oldTourDataObject in oldTourDataObjects)
     oldTourDataObject.Migrated = (byte)MIGRATIONSTEPS.MIGRATION_COMPLETE;
oldDb.SaveChanges();


De loop draait opnieuw tot oldObjects.Count == 0.

Er gaat echter iets fout met het Entity Framework in dit alles. Als ik tel hoeveel childs er gekoppeld zijn na het doorlopen van alle records uit de hoofdtabel dan mis ik er soms (!!) een aantal. Ik weet dat ik uit 168 hoofdrecords 194 child1's en 3614 child2's moet hebben. Soms klopt dit, meestal niet. Er zijn wel altijd het verwacht aantal hoofdrecords van 168 aanwezig!

In de tabel met hoofdrecords zitten in het echt 660.000 entries, in child1 zitten er 194 en in child2 zitten er 8.6 miljoen. Na heel wat hoofdbrekens ben ik erachter gekomen dat elk hoofdrecord met alleen een child1 of alleen een child2 of zonder childs juist worden geprocessed. De problemen komen voor bij de records zie zowel child1 als child2 records hebben. Bij de records waarvan het fout gaat en niet zowel de child1's en childs2's aanwezig zijn is in dat geval wel altijd een van beide childs aanwezig. OF de childs1's zijn er dan OF de childs2's (en voor zo ver ik weet ook allemaal van dat type).

Ik heb al vanalles geprobeerd en het probleem lijkt te zitten in het samen gebruiken van meerdere 'includes' en de 'take' en in het Entity Framework. Via de MSSQL database profiler is de query die EF naar de database stuurt elke keer gelijk. Zowel in het foute als goede geval dus. Zonder de 'take' werkt het in de test ook altijd goed, maar dat kan niet op de echte data want die tabellen zijn veel te groot.

Echter, zonder de 'include' mee te geven werkt het voor zo ver ik nu kan zien wel altijd goed. Misschien is het een eigenschap van .net 4. Omdat ik de zaak via ToList omzet naar een in memory lijst verwacht ik voor de child tabellen een '.Include' te moeten geven. Zonder de includes kwam er geen data in (weet ik niet zeker, misschien lazy-loadde hij het toch wel, maar een 'include' is expliciet en voorkomt juist lazy loading en lege records!).

edit:

Wat hierboven staat lijkt niet waar. De hoofdrecords zitten in de list, maar de childs worden bij processen zo te zien gelazy-load. Dat is dus echt traag want voor elke child gaat hij naar de DB om die data op te halen ipv dat dat al in de platgeslagen memory-list zit... :(


Een andere test met een tabel met bijna 600.000 records waaraan 0 of 1 child hangt wordt met ditzelfde mechanisme zonder enig probleem volledig overgezet.

Iemand inzicht in wat hier gebeurt of met hetzelfde bijltje gehakt?

Edit2:
Dit lijkt er misschien op maar geen oplossing omdat ik de .Take() niet buiten de .ToList() kan plaatsen: http://stackoverflow.com/...with-skip-take-load-issue

[ Voor 6% gewijzigd door Contagion op 06-08-2011 01:30 . Reden: 'include' toch nodig anders alsnog lazy-loading ]


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Nog wat zitten vogelen en het is eigenlijk nog gekker:

code:
1
2
3
4
5
6
7
int bufferSize=100;
var oldObjects = oldDb.tblMain
                     .Include("tblChild1")
                     .Include("tblChild2")
                     .Where(x => x.Migrated == null)
                     .Take(bufferSize)
                     .ToList();


Je zou toch verwachten dat er door de .Take altijd 100 elementen in de de lijst zitten (of bij de laatste iteratie minder dan 100 voor de overgebleven records). Maar dat is niet het geval. Soms wel, maar soms ook 101 of 104 of nog een totaal ander aantal?! 8)7

Dat kan toch helemaal niet. Straks de profiler er eens bij pakken en zien wat de SELECT TOP [x] zegt...

[ Voor 6% gewijzigd door Contagion op 07-08-2011 15:53 ]


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Ok, interessant. De SQL Query die voor deze eenvoudige LINQ-regel wordt gegereneerd is 121 regels lang. Als je die vanuit de SQL profiler in de SQL management studio kopieert en laat uitvoeren, dan zijn de resultaten niet elke keer gelijk. Ook dat lijkt me onmogelijk om een tabel die statische data bevat waar op dit moment door niemand mee wordt gewerkt?!?

De door EF gegenereerde SELECT geeft bij het opeenvolgend draaien van 10 runs het volgende aantal resultaten:

* 2044
* 2044
* 2044
* 2154
* 2145
* 2344

* 2044
* 2044
* 2044
* 2044

Ik vermoed dat zodra er meer dan 2044 resultaten zijn er ook meer dan 100 hoofdobjecten worden gereconstrueerd in de lijst in c# en ik denk dat in de teveel gegenereerde resultaten OF de child1 of de child2's niet juist zijn.

HOE KAN DIT? :?

Acties:
  • 0 Henk 'm!

  • ColdSTone|IA
  • Registratie: December 2002
  • Laatst online: 28-12-2017

ColdSTone|IA

lui..

Waar wordt het aantal results beperkt? Ergens een top [x] ?
Zijn er verschillende execution plans voor de verschillende aantallen resultaten?

Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
In de hele query die EF genereert staat 2x (waarom 2x?) een TOP(100).

Ik heb zelf geen execeution plan geschreven, als dat is wat je bedoelt. Het lijkt wel of SQL Server zelf een execution plan bepaalt en de query optimaliseert (dat mag ook wel, wat ik hier wil kan ook in 5 regels SQL en niet in 121 die EF er van maakt, maar goed) waardoor er telkens andere resultaten uitkomen.

EDIT:
Als je OPTION (MAXDOP 1) toevoegt aan de door EF gemaakte query, dan krijg ik wel elke keer hetzelfde aantal resultaten (2326 stuks, wel weer anders dan het aantal wat zonder MAXDOP het meest voorkomt). De MAXDOP instelling kan je echter niet via het Entity Framework forceren dus het lijkt een verloren zaak.

Toch blijf ik het vreemd vinden dat een parallel uitgevoerde query een verschillend aantal resultaten kan opleveren.

EDIT 2:
Dit zal hem wel zijn: FIX: A query that uses a parallel query plan returns different results every time that you run the query in SQL Server 2005, SQL Server 2008 R2 or SQL Server 2008
Totaal 'obscured' door de extreem ingewikkelde SQL query die het Entity Framework maakt van de simpele LINQ QUERY 'neem de eerste x aantal tblMain resultaten waarvoor geldt dat ze nog niet afgehandeld zijn en haal ook de daaraan gekoppelde kinderen op'.

In het execution plan zie ik inderdaad parallele taken. Maandag proberen de fix te installeren zoals genoemd in de MS KB. Maar al met al toch weer een reden om van het Entity Framework af te gaan stappen. Dit soort dingen kosten je daaagen om te vinden en te debuggen, terwijl wat je eigenlijk wil ophalen zo extreem simpel is en ook snel met een handmatig SQL commando te schrijven is. Zucht.... :'(

[ Voor 71% gewijzigd door Contagion op 07-08-2011 17:02 ]


Acties:
  • 0 Henk 'm!

  • alwinuzz
  • Registratie: April 2008
  • Laatst online: 05-09 09:24
Gebruik gewoon NHibernate >:)

Ik kan je helaas niet helpen met je EF troubles..

Acties:
  • 0 Henk 'm!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Beetje mosterd na de maaltijd, maar wij zijn allang afgestapt van de ORM frameworken. Ze willen allemaal alles ondersteunen en mede daardoor is configuratie al snel complex en de resultaten zijn meestal niet wat je verwacht.

Wij werken al ruim een jaar met dapper.net (geschreven door het bedrijf achter StackOverflow), een zeer klein (400 regels) ORM framework met als enigste taak de resultset omzetten naar entities. De queries zul je zelf moeten schrijven, maar vooral select worden zeer eenvoudig gemapped. Als je entity geen projection nodig heeft, kun je je entity direct doorgeven aan dapper en alle properties worden automatisch omgezet naar variabelen.

Echter wij gebruiken geen bit velden voor boolean types, maar gebruiken hiervoor het TinyInt data type. Middels automapper is het erg eenvoudig om database entities (waar de boolean dus als byte wordt opgevraagd) om te zetten naar de domain entity welke je gebruikt in je business code.

Dapper.net (en massive) zijn extenties op IDbConnection en werken met elke database waarvoor drivers beschikbaar zijn voor .NET. Deze micro ORM frameworken hebben echter wel als nadeel dat je .NET 4.0 moet gebruiken omdat zij afhankelijk zijn van 'dynamic' objects..

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Inderdaad mosterd na de maaltijd ;) want ik ben teruggestapt naar good old low level 'SqlCommand', maar zeker goed om te lezen, thanks! NHibernate is een bekende maar Dapper kende ik niet. Iets om te gaan proberen alleen draait de huidige applicatie nog op .NET 3.5.
Pagina: 1