Black Friday = Pricewatch Bekijk onze selectie van de beste Black Friday-deals en voorkom een miskoop.

[C#/DLinq] DataContexts en Identity Tracking

Pagina: 1
Acties:

  • BramFokke
  • Registratie: Augustus 2007
  • Laatst online: 04-10-2024
Er zijn verscheidene strategieën als het aankomt op de levensduur van de .NET 3.5 DataContext.

Een voor de hand liggende methode is om één globale DataContext gebruiken:
C#:
1
2
3
4
5
6
7
8
9
10
11
public static class Global {
    public static MyDataContext DB {
        get {
            if(_DB == null) {
               _DB = new MyDataContext("<Connection string>");
            }
            return _DB;
        }
    }
    private static MyDataContext _DB;
}


Het voordeel hiervan is dat je niet de DataContext als parameter hoeft mee te geven door het halve programma en dergelijke code kan schrijven:
C#:
1
2
3
4
5
public partial class Product {
    public static Product ByID(int id) {
        return Global.DB.Products.Where(p => p.ID == id).Single();
    }
}


Dit is de methode die ik momenteel gebruik. Op zich werkt het allemaal vrij goed maar er zijn twee grote problemen, waardoor deze benadering verre van ideaal is (en trouwens ook wordt afgeraden door Microsoft zelf).
  • Data is 'stale' (verouderd) nadat je een query doet. Door Identity Tracking worden object niet opnieuw van de database opgehaald tenzij je daar expliciet om vraagt. Dit valt te ondervangen door objecten expliciet te refreshen maar het doet wel afbreuk aan de transparantie van DLinq.
  • Vervelender is dat de benadering thread-unsafe is. Strategisch gebruik van locks kunnen dat probleem verhelderen maar doordat DLinq het queryen abstraheert is lang niet altijd duidelijk wanneer de queries daadwerkelijk worden uitgevoerd. Daarnaast kan dit potentieel leiden tot deadlock situaties.
De enige manier om het multithread probleem écht op te lossen, is voor iedere thread een of meer DataContexts te gebruiken. Dat kan ook transparant, door de volgende constructie:
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/// <summary>
/// This class encapsulates a per-thread scoped datacontext storage.
/// </summary>
/// <typeparam name="T"></typeparam>
public class DataContextPool<T> where T: DataContext, new() {

    /// <summary>
    /// Create a new context. Can be changed to allow for more elaborate code to set up the data context
    /// </summary>
    public Func<T> CreateContext = () => new T();

    private Dictionary<Thread, Stack<Scope>> scopes = new Dictionary<Thread,Stack<Scope>>();
    private Stack<Scope> GetStack() {
        Stack<Scope> result;
        if(!scopes.TryGetValue(Thread.CurrentThread, out result)) {
            result = new Stack<DataContextPool<T>.Scope>();
            scopes.Add(Thread.CurrentThread, result);
        }
        return result;
    }

    public Scope EnterScope() {
        Scope scope = new DataContextPool<T>.Scope(this);
        GetStack().Push(scope);
        return scope;
    }

    public T CurrentContext {
        get {
            Stack<Scope> stack = GetStack();
            if(stack.Count == 0) {
                throw new InvalidOperationException("No scope defined. Use EnterScope() first");
            }
            return stack.Peek().Context;
        }
    }

    private void ExitScope(Scope scope) {
        Stack<Scope> stack = GetStack();
        Scope top = stack.Peek();
        if(top != scope) {
            throw new InvalidOperationException("ExitScope() not called in right order. Use the using() keyword to avoid this message.");
        }
        stack.Pop();
    }

    /// <summary>
    /// This class encapsulates a 'data context scope'
    /// </summary>
    public class Scope : IDisposable {

        public Scope(DataContextPool<T> pool) {
            this.pool = pool;
            Context = pool.CreateContext();
        }

        private DataContextPool<T> pool;

        /// <summary>
        /// Context
        /// </summary>
        public T Context {
            get { return _Context; }
            private set { _Context = value; }
        }
        private T _Context;

        public void Dispose() {
            pool.ExitScope(this);
        }
    }
}

Het idee is dat je per thread een stack van DataContexts hebt. Zo kun je een bewerking een nieuwe scope laten openen. Als je die bewerking wilt cancelen, dan dispose je gewoon de DataContext (want de DataContext heeft om een of andere reden geen CancelChanges() methode). Dit is een methode om de Microsoft-aanbeveling dat een DataContext zo kort mogelijk moet leven te implementeren.

Alles is leuk en aardig, maar er is één probleem: object identity tracking. Een van de voordelen van een DataContext is dat als je twee maal dezelfde rij uit de database haalt, je tweemaal hetzelfde object terugkrijgt. Dat is handig, want als je op de ene plaats in je programma de status van een object verandert, dan wil je ook dat die verandering overal in het programma zo doorwerkt. En dat kan niet als je meerdere DataContexts hebt. Want ieder object dat door DLinq wordt gemanaged kan bij maximaal één DataContext horen. Je kan objecten migreren van de ene naar de andere DataContext met Attach() maar dan moet je dat object eerst Detach()en van de oude DataContext.

In veel gevallen kan het probleem verholpen worden door ueberhaupt niet aan te passen maar alleen aan de database toe te voegen (immutability). Maar dat is niet altijd zaligmakend want helemaal immutable kan een database nooit zijn.

Is het ueberhaupt onmogelijk om DLinq te gebruiken en objecten te hebben die tijdens de lifecycle van de applicatie hetzelfde blijven (dezelfde instance)? Wat zijn jullie strategieën om dit probleem op te lossen?

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:11
Blijkbaar dezelfde problematiek als waar je mee te maken krijgt als je gebruik maakt van NHibernate. Voor rich client app's ga je in NHibernate meestal gebruik maken van een 'Session per Conversation' of een Session per UnitOfWork.

https://fgheysels.github.io/