Skip to content

Commit

Permalink
Merge pull request #28 from Ian-Webster/18-fix-odd-formatting-in-code…
Browse files Browse the repository at this point in the history
…-samples-in

18 Fix odd formatting in code samples in readme.md
  • Loading branch information
Ian-Webster committed Feb 8, 2024
2 parents a9b7949 + e10eab1 commit ec2143e
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 8 deletions.
2 changes: 1 addition & 1 deletion DataAccess.Repository/DataAccess.Repository.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<Description>A simple base repository to be used in other projects requiring data access</Description>
<PackageProjectUrl>https://github.com/Ian-Webster/DataAccess</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ian-Webster/DataAccess</RepositoryUrl>
<VersionPrefix>3.0.0</VersionPrefix>
<VersionPrefix>3.0.1</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>

Expand Down
71 changes: 64 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ using DataAccess.Sample.Domain.Entities;
public class MovieRepository: IMovieRepository
{
private readonly IRepository<Movie> _movieRepository;
// inject RepositoryFactory for creating IRepository instances
public MovieRepository(RepositoryFactory<MovieContext> repositoryFactory)
// inject UnitOfWork for creating IRepository instances
public MovieRepository(UnitOfWork<MovieContext> unitOfWork)
{
// create an instance of IRepository for our entity (Movie)
_movieRepository = repositoryFactory.GetRepositoryByType<Movie>();
_movieRepository = unitOfWork.Repository<Movie>();
}

public async Task<Movie?> GetMovieById(Guid movieId, CancellationToken token)
Expand All @@ -107,15 +107,72 @@ In your IoC bootstrapping you need to;
```
2. Set up your services;
```csharp
// add RepositoryFactory (this will be needed by your repository class)
builder.Services.AddScoped<RepositoryFactory<MovieContext>>();
// add your repositories
builder.Services.AddScoped<IMovieRepository, MovieRepository>();
// add RepositoryFactory (this will be needed by your repository class)
builder.Services.AddScoped<UnitOfWork<LibraryDatabaseContext>>();
builder.Services.AddScoped<RepositoryFactory<MovieContext>>();
// add your repositories
builder.Services.AddScoped<IMovieRepository, MovieRepository>();
```
## Thread safety
The DBContext class [is not thread safe](https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#avoiding-dbcontext-threading-issues), the refactored version of this library does help improve thread safety but there are still potential issues, take the following code as an example;

```csharp
public class BookController : ControllerBase
{
private readonly IBookRepository _bookRepository;

public BookController(IBookRepository bookRepository)
{
_bookRepository = bookRepository;
}

[HttpGet("theadTest")]
public async Task<ActionResult> ThreadTest()
{
var task1 = Task.Run(() => _bookRepository.GetAllBooks(new CancellationToken()));
var task2 = Task.Run(() => _bookRepository.GetAllBooks(new CancellationToken()));

await Task.WhenAll(task1, task2);

return Ok();
}
}
```
the line `await Task.WhenAll(task1, task2);` will throw an exception "System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913", the reason for this is that `_bookRepository` is scoped per request but we are making two separate calls to the database re-using the same DBContext leading to our thread error.

To fix the issue we must ensure that each of the tasks receives it's own instance of DBContext, we modify the `ThreadTest` method to look like this;

```csharp
[HttpGet("theadTest")]
public async Task<ActionResult> ThreadTest()
{
var task1 = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IBookRepository>();
await repo.GetAllBooks(Token);
});

var task2 = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IBookRepository>();
await repo.GetAllBooks(Token);
});

await Task.WhenAll(task1, task2);

return Ok();
}
```

in addition we must in inject `IServiceScopeFactory` into our controller.

With the above change in place we ensure each of the two tasks receives their own instance of UnitOfWork and therefore their own instance of a DBContext.

## Version history

- 3.0.1 - update documentation
- 3.0.0
- refactored repository factory to follow unit of work pattern
- added new functionality to IRepository for optional take on List, projection and paging
Expand Down

0 comments on commit ec2143e

Please sign in to comment.