Laden...

Kann man DbSets aus unterschiedlichen DbContexts "verbinden"?

Erstellt von Davaaron vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.191 Views
D
Davaaron Themenstarter:in
106 Beiträge seit 2016
vor 3 Jahren
Kann man DbSets aus unterschiedlichen DbContexts "verbinden"?

[Verwendet wird Sqlite In-Memory, es soll aber für SqlServer, Sqlite und InMemory funktionieren]

Hi,

ich stehe gerade vor dem Problem, zwei Db-Kontexte zu verbinden. Genauer es geht darum, dass es in einem DbContext "PersonDbContext" ein DbSet<Person> gibt, dessen Klasse "Person" Properties besitzt, die in dem anderen DbContext "DocumentDbContext" als DbSet definiert sind.
Angenommen eine Person kann sich eine Medienlizenz auf einer Plattform kaufen. Die Person ist in PersonDbContext definiert und die "MediaLicense" in "DocumentDbContext". Ziel ist es, eine Person inklusive einer "MediaLicense" zu laden, wobei das "MediaLicense-Objekt" als Property an der Person hängt. Also:


var person = personDbContext.Persons.Include(x => x.License).FirstOrDefault();
Assert.True(person.License!=null);

Jedoch bekomme ich einen Fehler "FOREIGN KEY constraint failed" wenn ich der Person eine LicenseId hinzufüge..

So seede ich die Daten: zuerst SeedDocuments, dann SeedPersons (DocumentService benutzt DocumentDbContext, PersonService benutzt PersonDbContext):


 private static void SeedPersons(PersonService persService, DocumentService docService)
        {
            var persons = persService.GetPersons();
            if (!persons.Any())
            {
                var license = docService.GetMediaLicenses().FirstOrDefault();
                var docs = docService.GetDocuments();
                persService.Insert(new PersonModule.Entites.Person()
                {
                    Id = Guid.NewGuid(),
                    FirstName = "A",
                    Lastname = "B",
                    //LicenseId = license.Id
                });
            }
        }

        private static void SeedDocuments(DocumentService docService)
        {
            var licenses = docService.GetMediaLicenses();
            if (!licenses.Any())
            {

                docService.Insert(new DocumentModule.Entities.MediaLicense()
                {
                    Id = Guid.NewGuid(),
                    Code = "Code1"
                });

                docService.Insert(new DocumentModule.Entities.MediaLicense()
                {
                    Id = Guid.NewGuid(),
                    Code = "Code2"
                });

                docService.Insert(new DocumentModule.Entities.MediaLicense()
                {
                    Id = Guid.NewGuid(),
                    Code = "Code3"
                });
            }
        }

Wenn ich die auskommentierte Zeile "//LicenseId = license.Id" einfüge, dann kommt die Fehlermeldung. Wenn ich statt einer Id direkt das Objekt setze, also statt "LicenseId = license.Id" setze ich "License = license", dann wird ein Eintrag hinzugefügt und das Speichern & Laden funktioniert. Allerdings gibt es Situationen, in denen nicht das ganze Objekt vorhanden ist, sondern nur die Id, weshalb es gut wäre, wenn es auch funktionieren würde, wenn ich nur die LicenseId setze. Hat jemand eine Idee, wie ich zum Ziel komme? Muss ich einen eigenen DbInterceptor schreiben oder den DbContext nur richtig konfigurieren? Wenn ja, hat jemand eine Idee Ahnung wie?

Klassendefinitionen:

Klasse Person:


 public class Person
    {
        public Guid Id { get; set; }
        public string FirstName { get; set; }
        public string Lastname { get; set; }
        public ICollection<Document> Documents { get; set; }

        public Guid? LicenseId { get; set; }
        public MediaLicense License { get; set; }

        public Adress Adress { get; set; }
    }

PersonDbContext


public class PersonDbContext : DbContext
    {

        public DbSet<Person> Persons { get; set; }
        public DbSet<Adress> Adresses { get; set; }

        public PersonDbContext(DbContextOptions options) : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Person>().ToTable("Persons");
            modelBuilder.Entity<Adress>().ToTable("Adresses");


        }


    }

Klasse MediaLicense


public class MediaLicense
    {
        public Guid Id { get; set; }

        public string Code { get; set; }

    }

Klasse DocumentDbContext


public class DocumentDbContext : DbContext
    {
        public DbSet<Document> Documents { get; set; }
        public DbSet<MediaLicense> Licenses { get; set; }
        public DocumentDbContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Document>().ToTable("Documents");
        }
    }

16.835 Beiträge seit 2008
vor 3 Jahren

Das wird nicht wirklich funktionieren (können).
Unter der Haube ist das so, dass zwei völlig getrennte SQL Verbindungen verwendet werden; als seien es zwei völlig verschiedene Server.

T
2.224 Beiträge seit 2008
vor 3 Jahren

Ich würde dir empfehlen, die Personen bezogenen Daten auch in den Personen Context zu packen.
Erst dann kannst du auch über den gleichen Context die Daten direkt abrufen und EF kann dann auch über die FK Eigenschaften die Objekte verabreiten.

Alternativ, wenn du bei den getrennten Context Einträgen bleiben willst/musst, müsstest du z.B. die Person manuell befüllen.
Würde aber Optimierungen innerhalb von EF zunichte machen, da die Daten über eine eigene Verbindung nachgeladen werden müssen.

Anbei, ist der In-Memory DB ein eigener Provider oder der Default?
Der Default Provider ist nur zum testen und auch nicht auf Performance getrimmt.
Dieser sollte auch nie im Produktiv Einsatz genutzt werden.

EF Core In-Memory Hinweis

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.835 Beiträge seit 2008
vor 3 Jahren

Danke für den Hint T-Virus, dazu wollte ich was schreiben aber habs vergessen:

InMemory wird bei EFCore nur für das Testen unterstützt; es gibt keinen produktiv freigegebenen InMemory Provider für EFCore (oder EF5/6).

Der InMemory Provider von EFCore unterstützt auch nur ein Subset der Funktionalitäten von T-SQL und dem SQL Server.
Leider sind das teilweise so gravierende Funktionalitäten die fehlen, dass man hier nicht mal richtiges Integrationstesten machen kann.

D
Davaaron Themenstarter:in
106 Beiträge seit 2016
vor 3 Jahren

Puh, schade.
Ich hatte die Idee, meine Anwendung in kleine Module zu unterteilen und pro Modul sollte es ein DbContext geben. Natürlich gibt es da hin und wieder Überschneidungen, 100%ige Bounded Context sind leider nicht möglich.

Ich probiere mal, inwieweit ich den DbInterceptor ausreizen kann. Meine App unterstützt Dependency Injection, vielleicht kann ich einen eigenen DbInterceptor schreiben, der auf die besonderen Entitäten bzw. Properties achtet und aus dem richtigen Kontext liest und füllt.

16.835 Beiträge seit 2008
vor 3 Jahren

Du wirst aber den Mechanismus in EFCore nicht (so einfach) anpassen können; vermutlich eher gar nicht.
Der DbContext ist die Identifikation des Verbindungspoolings in EFCore.

Wenn Du ein Pluginmechanismus willst, dann mach das doch einfach ordentlich mit OOP Design.
Warum brauchen Deine Module einen eigenen Context statt dass Du den Modulen einen Context zur Verfügung stellst?

D
Davaaron Themenstarter:in
106 Beiträge seit 2016
vor 3 Jahren

Der DbContext wird mittlerweile zu groß. Viele Bereiche benötigen jedoch nur immer nur einen Teil des DbContexts, daher die Überlegung mit dem Aufteilen.

16.835 Beiträge seit 2008
vor 3 Jahren

Jo, kannst ja Interfaces zur Verfügung stellen. Wo ist das Problem?

2.079 Beiträge seit 2012
vor 3 Jahren

... z.B. Repositories

Jedes Repository bekommt den DBContext als Dependency übergeben und ruft sich die DBSets ab, die es so braucht. Dann braucht man keine Massen an DBSets im DBContext.

Auf die Weise bleibt der DBContext klein und man hat die vielen Funktionalitäten in sehr einfach unittestbaren Repositories gekapselt.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
Davaaron Themenstarter:in
106 Beiträge seit 2016
vor 3 Jahren

Die Architektur ist etwas das Problem. Aber dann weiß ich ja, wo ich anfangen muss.

Danke für eure Hilfe. Ohne euch hätte ich wahrscheinlich noch ewig probiert, die zwei DbSets irgendwie zu verbinden.