Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[.NET EF] Entities uitwisselen tussen contexts

Pagina: 1
Acties:

  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
Hallo :)

Dit is vast een domme vraag, ik zie vast iets over het hoofd.
Ik heb in een class een multithreraded get- en een save-functie. De Get parset info uit een bron, en koppelt de juiste entities uit de DB eraan, om daarna weergegeven te worden.
De save-functie die daarna aangeroepen wordt krijgt diezelfde elementen weer terug en gaat het daadwerkelijk opslaan. So far, so good. De logica erachter werkt allemaal, ik zit alleen met een klein EF-probleempje.

Eerst waren de functies static en maakte ik dus in een functie de DatabaseContext aan en gebruikte die. Dat ging mis met het opslaan, want dan was de entity in een andere context in gebruik, en dat gaf een fout. Mijn eerste gedachte is dan ook om het een normale class te maken, een property db aan te maken die gewoon alle functies gebruiken, en gewoon niet meer static. So far, so good. Probleem: bij de multithreaded functie maakt hij dan meerdere keren tegelijk van dezelfde entities in dezelfde context, en gaat het daar mis. (het is ook niet threadsafe volgens mij op deze manier)

Mijn vraag is dus: hoe zorg ik ervoor dat ik WEL multithreaded de entities kan koppelen, zonder dat het opslaan mis gaat (dubbele DBContext).

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


  • Megamind
  • Registratie: Augustus 2002
  • Laatst online: 10-09 22:45
bij de multithreaded functie maakt hij dan meerdere keren tegelijk van dezelfde entities in dezelfde context, en gaat het daar mis. (het is ook niet threadsafe volgens mij op deze manier)
Wat gaat er mis? Heb je nu een threading bug of een entity framework bug?

  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
Een EF fout. (Aan deze Command is al een open DataReader gekoppeld die eerst moet worden afgesloten), veroorzaakt door multithreading.
Waarschijnlijk is de huidige manier daarbovenop ook nog niet threadsafe.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Processor1 {
    /* Voorbeeld met static */

    /* MT stuff */
    public static List<Entity> getEntities() {
        var db = new DatabaseContext();
        /* ... */
        entities.Add(new Entity {
            oldEntity = dbRetrievedEntity
        });
    }
    public static void saveEntities(List<Entity> entities) {
        var db = new DatabaseContext();
        foreach(var entity in entities) {
            db.Entities.Add(entity); // fout, *iets* is gekoppeld aan een andere context (ja, die in de get) en dat kan niet
        }
        db.SaveChanges();
    }
}

public class Processor2 {
    /* Voorbeeld met niet-static */
    DatabaseContext db = new DatabaseContext();
    
    public List<Entity> getEntities() {
        var dbRetrievedEntity = db.Entites.FirstOrDefault(/*query*/); // InvalidOperationException (Open datareader die gesloten moet worden)
    }
    /* ... */
}

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


  • Styxxy
  • Registratie: Augustus 2009
  • Laatst online: 22-11 11:22
Geen fout van EF. Fout van jou hoor. Ik ben benieuwd hoe je het multi threaded aan het aanroepen bent. Volgens mij ben je daar wat fout aan het doen.

Overigens, het is logisch dat je niet zo maar Entities van DbContexten kan switchen ...

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Een linq query (dus ook een DbSet<T> query) wordt pas aangeroepen wanneer je hem enumerate. In EF5 heb je streaming, zodat de datareader open blijft tijdens enumeraten (foreach). Dit betekent dus dat de datareader van de context open blijft en de context dus niet gebruikt kan worden door iets anders. In EF6 is dit weer opgelost.

Wanneer je dus niet de query eerst enumerate, bv door .ToList(), dan wordt de query uitgevoerd wanneer je hem elders gebruikt. Het is alleen niet echt duidelijk wat er gebeurt, je code klopt nl. niet echt.

Contexts zijn niet thread safe maar in de code die je toont deel je ze niet per thread dus dat zou niet uit moeten maken, maar aangezien de code niet klopt doe je dus iets in de stukken die je niet toont, dus totdat je de complete methods post kan niet veel gezegd worden waar het specifiek aan ligt.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
Styxxy schreef op zaterdag 22 februari 2014 @ 00:37:
[...]

Geen fout van EF. Fout van jou hoor. Ik ben benieuwd hoe je het multi threaded aan het aanroepen bent. Volgens mij ben je daar wat fout aan het doen.

Overigens, het is logisch dat je niet zo maar Entities van DbContexten kan switchen ...
Ik doelde op een Exception die EF aan mij geeft.....

Hier nog de hele code dan, met wat use cases:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Processor {
    DatabaseContext db = new DatabaseContext();

    public List<Entity> entities = new List<Entity>();

    private void GetEntitiesThread() {
        List<Entity> entities = null;
        try {
            entities = ProcessOne();
        } catch (Exception e) {
            throw e;
        }
        this.entities.AddRange(entities);
    }

    public List<Entity> ProcessAll() {
        List<Task> tasks = new List<Task>();
        int[] allRange = [...];

        foreach(int i in allRange) {
            try {
                Task task = new Task(() => GetEntitiesThread());
                task.Start();
                tasks.Add(task);
            } catch(Exception e) {
                throw e;
            }
        }

        foreach(var task in tasks) {
            task.Wait();
        }
        return this.entities;
    }

    public List<Entity> GetOne() {
        /* Haal data op */
        var entities = new List<Entity>();
        foreach(var item in localArray) { /* Niet iets uit DB gehaald dus */
            Entity entity = new Entity();
            entity.property = db.Properties.FirstOrDefault(/*query*/); // EXCEPTION: Aan deze Command is al een open DataReader gekoppeld die eerst moet worden afgesloten; InvalidOperationException
            /* Nog een boel koppelingen, NERGENS een iteratie in DB-items */
            entities.Add(entity);
        }
        return entities;
    }

    public void SaveEntities(List<Entity> entities) {
        /* Opslaan */
    }
}


Als ik maar één item heb (dus één task), dan gaat het wél goed, ook het opslaan (zelfde context).

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


  • Megamind
  • Registratie: Augustus 2002
  • Laatst online: 10-09 22:45
Ik zie nergens locking terugkomen dus threadsafe zal het nooit worden. Je kan eens kijken naar de EF async methodes.

  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 18-11 20:57
Je maakt nu iets multithreaded, maar vervolgens foreach je door je taken heen om vervolgens op iedere taak te gaan wachten tot hij klaar is? Het voordeel van multithreading valt dan wel weg.

Je zou eens kunnen kijken naar de Task Parallel Library, waarmee het weer een stukje makkelijker wordt om taken in parallel uit te laten voeren.
C#:
1
2
3
Parallel.ForEach(allRange, i => { 
  // Iets doen met je object
});


Het probleem dat je hebt met de open DataReader kan komen doordat je in meerdere threads werkt, waar een DbContext niet tegen kan. Probeer eens een nieuwe instance van je DbContext te instantiëren in de thread waarin je get-code draait i.p.v. in de parent methode.

Wat ik je als laatste zou willen adviseren is om object change tracking in dit geval uit te schakelen. Het lijkt erop dat je het niet nodig hebt voor deze code, change tracking gaat je dan waarschijnlijk meer problemen opleveren dan dat het je helpt. :)

We are shaping the future


  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
Alex) schreef op zondag 23 februari 2014 @ 02:27:
Je maakt nu iets multithreaded, maar vervolgens foreach je door je taken heen om vervolgens op iedere taak te gaan wachten tot hij klaar is? Het voordeel van multithreading valt dan wel weg.
Hij voert nog steeds de taken (7 grote taken (de GetOne)) parallel uit, maar hij gaat pas verder returnen als hij helemaal klaar is (het wordt aangeroepen door een aparte thread die dan netjes threadsafe de UI bijwerkt). Ik kan natuurlijk niet returnen terwijl hij nog niet alles verwerkt heeft. Het heeft dus nog wel voordeel, het is 7x zo snel :)
Je zou eens kunnen kijken naar de Task Parallel Library, waarmee het weer een stukje makkelijker wordt om taken in parallel uit te laten voeren.
C#:
1
2
3
Parallel.ForEach(allRange, i => { 
  // Iets doen met je object
});
Dit heb ik al geprobeerd in losse onderdelen van de GetOne() bijvoorbeeld, maar dit leverde in eerste instantie enkel problemen (collectie was veranderd, enz.). Misschien ligt dat ook aan dat change tracking.
Het probleem dat je hebt met de open DataReader kan komen doordat je in meerdere threads werkt, waar een DbContext niet tegen kan. Probeer eens een nieuwe instance van je DbContext te instantiëren in de thread waarin je get-code draait i.p.v. in de parent methode.
Dat werkt, maar dan gaat het opslaan zeuren dat het uit een andere Context komt ;) (dat is juist mijn hele probleem)
Wat ik je als laatste zou willen adviseren is om object change tracking in dit geval uit te schakelen. Het lijkt erop dat je het niet nodig hebt voor deze code, change tracking gaat je dan waarschijnlijk meer problemen opleveren dan dat het je helpt. :)
Bedankt voor de tip, ik zal er naar kijken!
offtopic:
Morgen dan, nu eerst slapen

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Het gaat mis omdat je 1 enkele context instance gebruikt. Maak gewoon 1 aan in de method (het is alleen de 1e keer traag) zodat je niet meer die instance deelt met alle threads.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
EfBe schreef op zondag 23 februari 2014 @ 10:38:
Het gaat mis omdat je 1 enkele context instance gebruikt. Maak gewoon 1 aan in de method (het is alleen de 1e keer traag) zodat je niet meer die instance deelt met alle threads.
F.West98 schreef op zondag 23 februari 2014 @ 03:21:
[...]

Dat werkt, maar dan gaat het opslaan zeuren dat het uit een andere Context komt ;) (dat is juist mijn hele probleem)

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


  • EfBe
  • Registratie: Januari 2000
  • Niet online
... dan moet je locken, dus iedere access op de context vereist dat je die code wrapped in een lock() {} block. Lock op een simpel object.

Of je roept 'attach' aan op de nieuwe context die je gebruikt voor saven, nadeel is dan weer dat de nieuwe context niet weet of de entities nieuw of geupdate zijn. Tja... EF..

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • Lethalis
  • Registratie: April 2002
  • Niet online
Je kan entities ook attachen aan een nieuwe context in EF (zoals EfBe al aangeeft).

MSDN: Entity Framework Add/Attach and Entity States

Voorbeeld:
code:
1
2
3
4
5
6
7
8
9
10
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" }; 
 
using (var context = new BloggingContext()) 
{ 
    context.Blogs.Attach(existingBlog); 
 
    // Do some more work...  
 
    context.SaveChanges(); 
}

Ask yourself if you are happy and then you cease to be.


  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
Ik heb het opgelost door steeds ook de context mee te geven. Het opslaan kan dan ook met meerdere tasks, dat versnelt alles ook behoorlijk.
Het is niet helemaal netjes, maar voldoende voor wat ik wil.

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


  • Lethalis
  • Registratie: April 2002
  • Niet online
Begrijp ik nu goed dat je een subclass van DbContext shared met meerdere threads?

Let je wel op dat DbContext niet thread safe is?

Ask yourself if you are happy and then you cease to be.


  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 02:35

F.West98

Alweer 16 jaar hier

Topicstarter
Lethalis schreef op zaterdag 08 maart 2014 @ 07:52:
Begrijp ik nu goed dat je een subclass van DbContext shared met meerdere threads?

Let je wel op dat DbContext niet thread safe is?
Ik maak per thread een nieuwe DbContext aan die ik uiteindelijk doorgeef met het hele gedoe, zodat verderop ik dus dezelfde context weer kan gebruiken.
Dus nee, de contexts worden niet geshared met meerdere threads.

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI

Pagina: 1