Laden...

[gelöst] ASP.NET Core Dependency Injection Singelton in Scoped verwenden?

Erstellt von Olii vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.107 Views
O
Olii Themenstarter:in
76 Beiträge seit 2017
vor 4 Jahren
[gelöst] ASP.NET Core Dependency Injection Singelton in Scoped verwenden?

Hallo, ich probiere mich immer noch an ASP.NET Core mit GraphQL und Dapper.GraphQL aus.

Beim herumspielen ist mir aufgefallen das wenn man Dapper verwenden möchte, ein Nuget existiert, dass Dapper.GraphQL heißt. Soweit so gut. Ich bin dem Beispiel gefolgt und bin dann auf einen Punkt gestoßen der bei mir für Unsicherheit sorgt.

Beim Registrieren der Schema/Types etc. in der Middleware bzw. unter Startup.cs ConfigureServices, müssen für Dapper.GraphQL die entsprechenden Types etc. über services.AddDapperGraphQL registriert werden. Das ganze scheint als Singelton zu erfolgen. Das Schema allerdings habe ich nur über services.AddScoped registriert, da ich z.B. in dem Beispiel von Abt ebenfalls die Registrierung über Scoped gesehen habe.

Scoped wird bei jedem Request erstellt, werden ein Singelton erstellt wird sobald dieser zum ersten mal gebraucht wird, und dieser wird dann die ganze Zeit über erhalten.

Hier einmal ein paar Auszüge:

Startup.cs


public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ISchema, MyQuerySchema>();

            services.AddSingleton<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));

            services.AddDapperGraphQL(options =>
            {
                // Add GraphQL types
                options.AddType<UserAccountType>();
                options.AddType<MyGraphQuerySchema>();

                // Add query builders for dapper
                options.AddQueryBuilder<UserAccount, UserAccountQueryBuilder>();
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

Controller


private readonly ISchema _schema;

        public GraphQLController(ISchema schema)
        {
            _schema = schema;
        }

        public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
        {
            var inputs = query.Variables.ToInputs();

            ExecutionResult result = await new DocumentExecuter().ExecuteAsync(_ =>
            {
                _.Schema = _schema;
                _.Query = query.Query;
                _.OperationName = query.OperationName;
                _.Inputs = inputs;
            }).ConfigureAwait(false);

            if(result.Errors?.Count > 0)
            {
                return BadRequest();
            } 

            return Ok(result);
        }

MyQuerySchema


public class MyQuerySchema : Schema
    {
        public MyQuerySchema(IDependencyResolver resolver):base(resolver)
        {
            Query = resolver.Resolve<MyGraphQuerySchema>();
        }
    }

Den Resolver verwende ich hier damit ich später auch mit Mutations arbeiten kann. Das habe ich so in mehreren Beispielen gesehen.

Meine Frage ist nun um es noch einmal zusammen zu fassen: Macht es Sinn in einem Scoped, Singelton zu verwenden? Denn MyQuerySchema ist ja mit einer der wichtigsten Teile über den dann mehr oder weniger die anderen Dinge laufen wie MyGraphQLQuerySchema also wo das Mapping etc. stattfindet. Von Dapper.GraphQL bin ich nägmlich gezwungen einige Teile als Singelton zu registrieren. Oder ergibt es mehr Sinn einfach alles als Singelton fest zu legen?

Beide Fälle funktionieren, dass habe ich getestet, aber es könnte ja später doch vielleicht zu Problemen führen? Ich würde gerne möglichst sauber lernen und nicht so viel fuschen.

Wenn ich etwas unklar definiert habe, bitte bescheid geben damit ich es ändern kann.

Note: Wenn jemand entsprechendes Lesematerial hat, freue ich mich natürlich ebenfalls. Die offizielle MS Doc habe ich natürlich auch schon dazu gelesen. Mich würde nur mal das zusammenspiel in so einem Fall interessieren.

16.807 Beiträge seit 2008
vor 4 Jahren

Beim herumspielen ist mir aufgefallen das wenn man Dapper verwenden möchte, ein Nuget existiert, dass Dapper.GraphQL heißt. Soweit so gut. Ich bin dem Beispiel gefolgt und bin dann auf einen Punkt gestoßen der bei mir für Unsicherheit sorgt.

Hört sich nach ner ganz schlechten Idee an.
Dapper ist Datenbankschicht - GraphQL ist die Anssichtsschickt.
Allein das Konzept ist eine Verletzung der [Artikel] Drei-Schichten-Architektur

Auch die Grundidee, dass GraphQL aus mehreren Quellen Daten konsolidiert, um die Anfragemenge zu minimieren, würde DapperGraphQL völlig unterlaufen.

Macht es Sinn in einem Scoped, Singelton zu verwenden?

Aus einem Scoped darf man einen Singleton verwenden, aber nicht umgekehrt.
Ansonsten wird der Scoped automatisch zu einer Singleton-Referenz (im Singleton selbst).
Das kann zu Race Conditions führen.

Oder ergibt es mehr Sinn einfach alles als Singelton fest zu legen?

Datenbank(verbindungen) niemals* als Singleton definieren.
Gibt Ausnahmen, die man an einer Hand abzählen kann. Hier sehe ich keine.

Beide Fälle funktionieren, dass habe ich getestet, aber es könnte ja später doch vielleicht zu Problemen führen?

Das Konzept Datenbank Entitäten in den API Return zu pressen ist ein Fehler, korrekt 😃

O
Olii Themenstarter:in
76 Beiträge seit 2017
vor 4 Jahren

Hört sich nach ner ganz schlechten Idee an.
Dapper ist Datenbankschicht - GraphQL ist die Anssichtsschickt.
Allein das Konzept ist eine Verletzung der
>

Aber mit EntityFramework würde es doch auf das selbe hinauslaufen oder? Dapper.GraphQL bringt nur einen SQL Builder und einen Mapper mit, mit dem dann aus der GraphQL Query ein SQL erstellt wird.

Vielleicht verstehe ich dich auch falsch, aber an der Stelle wo Daten nun abgefragt werden, also wie in deinem Beispiel bei MyGraphQLQuerySchema, sah ich Beispiele mit EF bei denen an dieser Stelle nun Daten abgefragt werden, oder man verwendet Repositorys oder wie du ein Mediater. In dem Fall wollte ich einfach anstatt EF, Dapper verwenden. Dazu gibt es dann das Nuget Dapper.GraphQL womit aus der GraphQL Query ein SQL erstellt wird. Und damit kann ich halt meine Daten holen.

Weil man muss ja irgendwie aus der Query ein gültiges SQL erstellen.

Oder meinst du das, dass Abfragen der Daten in dieser Konstellation nicht schön ist? Sonder das man z.B. ein Mediater etc. an dieser Stelle verwenden sollte der einem bei der Datenbeschaffung hilft?

Ich wollte das mit dem Mediater noch ausprobieren aber das ganze erst so simple wie möglich halten damit ich nicht so durcheinander komme 😄

Quasi wie in disem "Ofiziellen" Beispiel:


Field<ListGraphType<PersonType>>(
    "people",
    description: "A list of people.",
    resolve: context =>
    {
        // Create an alias for the 'Person' table.
        var alias = "person";
        // Add the 'Person' table to the FROM clause in SQL
        var query = SqlBuilder.From($"Person {alias}");
        // Build the query, using the GraphQL query and SQL table alias.
        query = personQueryBuilder.Build(query, context.FieldAst, alias);

        // Create a mapper that understands how to map the 'Person' class.
        var personMapper = new PersonEntityMapper();

        // Open a connection to the database
        using (var connection = serviceProvider.GetRequiredService<IDbConnection>())
        {
            // Execute the query with the person mapper
            var results = query.Execute(connection, personMapper, context.FieldAst);
            
            // `results` contains a list of people.
            return results;
        }
    }
);

Source: https://github.com/landmarkhw/Dapper.GraphQL/blob/master/README.md

Das Konzept Datenbank Entitäten in den API Return zu pressen ist ein Fehler, korrekt 😃

Wie meinst du das, ich kann dir da gerade nicht ganz folgen.

16.807 Beiträge seit 2008
vor 4 Jahren

Aber mit EntityFramework würde es doch auf das selbe hinauslaufen oder?
Dapper.GraphQL bringt nur einen SQL Builder und einen Mapper mit, mit dem dann aus der GraphQL Query ein SQL erstellt wird.

Richtig. Genauso kacke.

Man bindet keine Datenbankschicht - egal über welchen Provider - direkt an die API.
Damit untergräbst Du - egal mit welchem Provider - völlig das Konzept der Schichtentrennung.

Oder meinst du das, dass Abfragen der Daten in dieser Konstellation nicht schön ist? Sonder das man z.B. ein Mediater etc. an dieser Stelle verwenden sollte der einem bei der Datenbeschaffung hilft?

Der Mediator ist die Schnittstelle der Logik.
Der Handler eines Mediators beinhaltet vollständig die Logik der Anwendung (bzw. die Menge an Handlern).

Quasi wie in disem "Ofiziellen" Beispiel:

  
Field<ListGraphType<PersonType>>(  
    "people",  
    description: "A list of people.",  
    resolve: context =>  
    {  
        // Create an alias for the 'Person' table.  
        var alias = "person";  
        // Add the 'Person' table to the FROM clause in SQL  
        var query = SqlBuilder.From($"Person {alias}");  
        // Build the query, using the GraphQL query and SQL table alias.  
        query = personQueryBuilder.Build(query, context.FieldAst, alias);  
  
        // Create a mapper that understands how to map the 'Person' class.  
        var personMapper = new PersonEntityMapper();  
  
        // Open a connection to the database  
        using (var connection = serviceProvider.GetRequiredService<IDbConnection>())  
        {  
            // Execute the query with the person mapper  
            var results = query.Execute(connection, personMapper, context.FieldAst);  
              
            // `results` contains a list of people.  
            return results;  
        }  
    }  
);  
  

Source:
>

Und das ist halt genau das was ich meine: Zum Teufel gehört kein SQL Code in das Schema.
Auch sieht man hier Service Locator Anti-Pattern.
Ganz klassischer Fail der [Artikel] Drei-Schichten-Architektur

Das Schema ist nur der Relay zur Logik.

O
Olii Themenstarter:in
76 Beiträge seit 2017
vor 4 Jahren

Achso okey. wir sind zwar ein wenig vom Thema abgekommen aber ich fasse noch einmal zusammen:

Die Beispiele die unter anderem dargestellt wurde auf Github etc. sind nicht sauber, da in der Präsentationsschicht sofort Logik und Datenbankzugriff mit drin ist.

Besser wäre es mit z.B. einem Mediater zu arbeiten in Verbindung mit vielleicht dem Repository Pattern und mit diesen dann die Logik und den Datenbankzugriff zu steuern.
Somit müsste der SQLBuilder etc. der von Dapper.GraphQL mitgebracht wird, mit in den Mediator rutschen, bzw. halt aus dem Schema fliegen.

Ein Singelten kann in ruhe in einem Scoped verwendet werden.

Setze ich diese Punkte so um, habe ich eine (hoffentlich) präsentable Lösung für mein Problem.

16.807 Beiträge seit 2008
vor 4 Jahren

Jo, Schema ruft Logik (zB. Mediator) auf und der Handler spricht mit der Datenbank.