Access dependency-injected Entity Framework database from static class method

In my project, I have a static converter method to convert client and database objects into each other. One of those static methods needs to access the database. Before introducing dependency injection into my project, that was quite simple:

internal async static Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col) {
    using MpaContext db = new MpaContext();

    return new ViewerColumn() {
        // ...
        SourceColumnID = await db.SourceColumns
            .Where(sc => sc.Key == col.DataField)
            .Select(sc => sc.ID)
            .SingleAsync()
    };
}

I want to change this by introducing dependency injection project-wide. My first approach was to simply add the database context as a separate parameter:

internal async static Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col, MpaContext context) {
    using MpaContext db = context;
    // ...
}

This, however, leads to problems, if the context from the parameter gets disposed somewhere else. So my idea was to dependency-inject the context to the class inself. This, however, doesn't work, because you obviously can't use parameters for static constructors.

Here's how the method is called (currently with the context parameter):

// Controller method with dependency injection
[HttpPut("ViewerRoles/{vrID}")]
public async Task<ActionResult> UpdateViewSettings(int vrID, ViewerRoleSettings updatedData) {
    using MpaContext db = _mpaContext;

    await storedViewerRole.ApplyViewerRoleSettingsAsync(updatedData, _mpaContext);
}

// ViewerRole.cs
internal async Task ApplyViewerRoleSettingsAsync(ViewerRoleSettings updatedData, MpaContext context) {
    // Create new entries
    foreach (Client.ViewerColumnSettings col in updatedData.ViewerColumns) {
        ViewerColumns.Add(await ViewerColumn.FromClientColumn(col, context));
    }
}

This approach fails, because the context gets disposed in UpdateViewSettings and in FromClientColumn.

What's the best-practice approach for such a case? I could dispose the context only, if it wasn't open beforehand, but that sounds stupid to me.

1 answer

  • answered 2020-03-31 10:36 Nkosi

    Dependency Inversion / Dependency Injection does not play well with static.

    Make an abstraction and derived implementation with injected context

    public class ViewerColumnService : IViewerColumnService {
        private readonly MpaContext db ;
    
        public ViewerColumnService  (MpaContext db) {
            this.db = db;
        }
    
        public async Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col) {
            return new ViewerColumn() {
                // ...
                SourceColumnID = await db.SourceColumns
                    .Where(sc => sc.Key == col.DataField)
                    .Select(sc => sc.ID)
                    .SingleAsync()
            };
        }
    }
    

    Register this new service and explicitly inject it where it is needed. Stop manually disposing of the context by wrapping it in a using statement. Let the DI container handle the lifetime of the components.