Autor Beitrag
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mi 24.08.16 18:11 
'n Abend,

ich hänge gerade an einem kleinen Problem mit EF6 und CodeFirst
Und zwar möchte ich Listen in der Datenbank mit verschiedenen Eigenschaften speichern.
Davon gibt es aber verschiedene Listen, weshalb ich eine Basisklasse habe und für die verschiedenen Varianten entsprechende ableitende Klassen.
Soweit so gut, mit Vererbung kommt EF6 ja klar.

Aktuell habe ich drei verschiedene Listen:
- Die Inhalte werden von dem Benutzer manuell editiert
- Die Inhalte werden durch vordefinierte Abfragen basierend auf eigenen Abfragen abgerufen
- Die Inhalte werden durch vom User definierte Abfragen abgerufen

Die erste Liste ist egal, das regelt EF6 automatisch.
Für die anderen beiden Listen brauche ich aber die Möglichkeit, in der Klasse selber Abfragen abzuschicken und die Ergebnisse zu verarbeiten.
Ich brauche dort also Zugriff zu meinem DbContext, aber die bekomme ich den?

Ich möchte ungern den Context irgendwo statisch vor halten.
Ich möchte den eigentlich in einem eigenen Objekt gekapselt verwalten, das dann je nach Bedarf die Verbindung öffnet/schließt und dieses Objekt wird über einen IoC-Container verfügbar sein.
Ich möchte auch nicht die Query dort ausführen, wo ich sie nutze, da das mehrere Stellen sind.


Hat da jemand eine Idee, wie ich den DBContext bekomme oder wie ich mein Vorhaben anders erreichen kann?
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mi 24.08.16 22:17 
Ok, meine "Lösung" sieht jetzt so aus:

ausblenden volle Höhe C#-Quelltext
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:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
public class DbContextLocator
{
    public static DbContextLocator Instance { get; } = new DbContextLocator();

    private readonly ICollection<DbContext> _registeredContexts;
    private readonly IDictionary<object, DbContext> _entityContexts;
    private readonly IDictionary<DbSet, NotifyCollectionChangedEventHandler> _dbSetLocalChangedHandler;

    private DbContextLocator()
    {
        _registeredContexts = new List<DbContext>();
        _entityContexts = new ConcurrentDictionary<object, DbContext>();
        _dbSetLocalChangedHandler = new ConcurrentDictionary<DbSet, NotifyCollectionChangedEventHandler>();
    }

    public TDbContext FindDbContext<TDbContext>(object entity)
        where TDbContext : DbContext
    {
        return FindDbContext(entity) as TDbContext;
    }
    public DbContext FindDbContext(object entity)
    {
        return _entityContexts.ContainsKey(entity) ? _entityContexts[entity] : null;
    }

    public void RegisterContext(DbContext context)
    {
        foreach (var dbSet in GetDbSets(context))
            AddDbSetLocalChangedHandler(dbSet, context);

        lock (_registeredContexts)
        {
            if (!_registeredContexts.Contains(context))
                _registeredContexts.Add(context);
        }
    }
    public void DeregisterContext(DbContext context)
    {
        foreach (var dbSet in GetDbSets(context))
        {
            var observableCollection = dbSet.Local as INotifyCollectionChanged;

            if (_dbSetLocalChangedHandler.ContainsKey(dbSet))
                observableCollection.CollectionChanged -= _dbSetLocalChangedHandler[dbSet];

            foreach (var entity in dbSet)
                _entityContexts.Remove(entity);
        }

        lock (_registeredContexts)
            _registeredContexts.Remove(context);
    }

    private void AddDbSetLocalChangedHandler(DbSet dbSet, DbContext context)
    {
        var observableCollection = dbSet.Local as INotifyCollectionChanged;

        if (!_dbSetLocalChangedHandler.ContainsKey(dbSet))
        {
            _dbSetLocalChangedHandler.Add(dbSet, (sender, e) =>
            {
                foreach (var oldEntity in e.OldItems?.Cast<object>() ?? new object[0])
                    _entityContexts.Remove(oldEntity);

                foreach (var newEntity in e.NewItems?.Cast<object>() ?? new object[0])
                {
                    if (!_entityContexts.ContainsKey(newEntity))
                        _entityContexts.Add(newEntity, context);
                }
            });

            observableCollection.CollectionChanged += _dbSetLocalChangedHandler[dbSet];
        }
    }
    private static IEnumerable<DbSet> GetDbSets(DbContext context)
    {
        var objectContext = ((IObjectContextAdapter)context).ObjectContext;
        var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);

        return from entitySet in container.EntitySets
                let entityType = Type.GetType(entitySet.ElementType.FullName)
                select context.Set(entityType);
    }
}


Was ich im Prinzip mache, ist folgendes:
Vom DbContext alle DbSets abfragen und für jedes DbSet auf die Local-Property ein CollectionChanged-Handler registrieren.
So kann ich für jedes hinzugefügte oder entfernte Entity-Objekt den aktuellen DbContext im Dictionary bereit halten.
In jeder Entity, die das braucht, muss ich dann noch einen Konstruktor-Parameter für den DbContext hinzufügen, damit ich den beim neu erstellen einer Entity mit geben kann.
Damit EF damit arbeiten kann, brauchts aber noch einen parameterlosen Konstruktor, aber der kann auch private sein.

Das funktioniert, zumindest soweit ich das getestet habe, aber es wirkt für mich wie eine ziemlich wackelige Krücke.
Wenn jemand eine bessere Idee hat, wäre ich da sehr dankbar.