Use GenericRepository with include and an expression - C# - c#

I'm trying to implement the generic repository pattern.
public interface IGenericRepository<T> where T : class
{
Task<IEnumerable<T>> GetAllIncludingAsync(Expression<Func<T, bool>> expression, Expression<Func<object, bool>>[] includes, CancellationToken cancellationToken = default);
Task<T?> GetIncludingAsync(Expression<Func<T, bool>> expression, Expression<Func<T, object>>[] includes, CancellationToken cancellationToken = default);
}
And this is the class that implements the interface
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly MyDbContext _dbContext;
private readonly DbSet<T> _entitySet;
public GenericRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
_entitySet = _dbContext.Set<T>();
}
public async Task<IEnumerable<T>> GetAllIncludingAsync(Expression<Func<T, bool>> expression, Expression<Func<object, bool>>[] includes, CancellationToken cancellationToken = default)
{
IQueryable<T> query = _dbContext.Set<T>();
foreach (var item in includes)
{
query = (IQueryable<T>)query.Include(item);
}
return await query.ToListAsync();
}
public async Task<T?> GetIncludingAsync(Expression<Func<T, bool>> expression, Expression<Func<T, object>>[] includes, CancellationToken cancellationToken = default)
{
IQueryable<T> query = _dbContext.Set<T>();
foreach (var item in includes)
{
query = query.Include(item);
}
return await query.FirstOrDefaultAsync(expression);
}
}
So far so good. But when I want to use the Repository in my service I don't know what to do.
IEnumerable<Booking>? bookings = await _unitOfWork
.BookingRepository
.GetAllIncludingAsync(x => x.Id == personId, y => y.Inc); //here I want to include Person
Don't know how I can use the method from the repository.
The BookingRepository is just a repository that implements the GenericRepository

Change the parameter method like this:
Func<IQueryable<T>, IIncludableQueryable<T, object>> includes
And you will be able to call it like this:
GetAllIncludingAsync(x => x.Id == personId,x => x.Include(y => y.Person).Include(y => y.OtherTable))

Related

add repository response model includes?

I have a working add repository. I want to write a separate add repository without touching it, but after adding it in this repository, I want it to return a value that is included in the response model. How can I do it ?
MyRepository
public class Repository<T> : IRepository<T> where T : EntityBase
{
private readonly ApplicationContext Context;
public Repository(ApplicationContext context)
{
Context = context;
}
public async Task<T> AddAsync(T entity)
{
Context.Set<T>().Add(entity);
await Context.SaveChangesAsync();
return entity;
}
public async Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null,
IList<string> includes = null, bool disableTracking = true)
{
IQueryable<T> query = Context.Set<T>().Where(x => !x.IsDelete);
if (predicate != null) query = query.Where(predicate);
if (includes != null)
query = includes.Aggregate(query, (current, include) => current.Include(include));
if (disableTracking) query = query.AsNoTracking();
return await query.ToListAsync();
}

How to use database transactions in current approach?

I am writting an API backend application using .NET Core
BaseRepository:
public class BaseRepository<T, TPrimaryKey> : IBaseRepository<T, TPrimaryKey> where T : class where TPrimaryKey : struct
{
private readonly DatabaseContext _dbContext;
public BaseRepository(DatabaseContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IEnumerable<T>> GetAll()
{
return await _dbContext.Set<T>().ToListAsync();
}
public IQueryable<T> GetQueryable()
{
return _dbContext.Set<T>();
}
public async Task<T> Find(TPrimaryKey id)
{
return await _dbContext.Set<T>().FindAsync(id);
}
public async Task<T> Add(T entity, bool saveChanges = true)
{
await _dbContext.Set<T>().AddAsync(entity);
if (saveChanges)
await _dbContext.SaveChangesAsync();
return await Task.FromResult(entity);
}
public async Task Edit(T entity, bool saveChanges = true)
{
_dbContext.Entry(entity).State = EntityState.Modified;
if (saveChanges)
await _dbContext.SaveChangesAsync();
}
public async Task Delete(T entity, bool saveChanges = true)
{
if (entity == null)
throw new NullReferenceException();
_dbContext.Set<T>().Remove(entity);
if (saveChanges)
await _dbContext.SaveChangesAsync();
}
public async Task<IEnumerable<T>> BulkInsert(IEnumerable<T> entities, bool saveChanges = true)
{
foreach (T entity in entities)
{
await _dbContext.Set<T>().AddAsync(entity);
}
if (saveChanges)
await _dbContext.SaveChangesAsync();
return await Task.FromResult(entities);
}
public async Task BulkUpdate(IEnumerable<T> entities, bool saveChanges = true)
{
foreach (T entity in entities)
{
_dbContext.Entry(entity).State = EntityState.Modified;
}
if (saveChanges)
await _dbContext.SaveChangesAsync();
}
public async Task Save()
{
await _dbContext.SaveChangesAsync();
}
}
IBaseRepository:
public interface IBaseRepository<T, E> where T : class where E : struct
{
Task<IEnumerable<T>> GetAll();
IQueryable<T> GetQueryable();
Task<T> Find(E id);
Task<T> Add(T entity, bool saveChanges = true);
Task Edit(T entity, bool saveChanges = true);
Task Delete(T entity, bool saveChanges = true);
Task<IEnumerable<T>> BulkInsert(IEnumerable<T> entities, bool saveC
Task BulkUpdate(IEnumerable<T> entities, bool saveChanges = true);
Task Save();
}
IServiceBase:
public interface IServiceBase<TEntity, TPrimaryKey>
{
Task<TEntity> GetById(TPrimaryKey id);
Task<TEntity> GetSingle(Expression<Func<TEntity, bool>> whereCondition);
Task<IEnumerable<TEntity>> GetAll();
IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> whereCondition);
IQueryable<TEntity> GetAllQueryable();
IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> whereCondition);
Task<TEntity> Create(TEntity entity);
Task Delete(TEntity entity);
Task Update(TEntity entity);
Task<long> Count(Expression<Func<TEntity, bool>> whereCondition);
Task<long> Count();
Task<IEnumerable<TEntity>> BulkInsert(IEnumerable<TEntity> entities);
Task BulkUpdate(IEnumerable<TEntity> entities);
}
for example IAddressServices:
public interface IAddressService : IServiceBase<Address, Guid>
{
Task<Address> VerifyAddress(Address address);
}
ServiceBase:
public abstract class ServiceBase<TEntity, TRepository, TPrimaryKey> : IServiceBase<TEntity, TPrimaryKey>
where TEntity : class
where TPrimaryKey : struct
where TRepository : IBaseRepository<TEntity, TPrimaryKey>
{
public TRepository Repository;
public ServiceBase(IBaseRepository<TEntity, TPrimaryKey> rep)
{
Repository = (TRepository)rep;
}
public virtual async Task<TEntity> GetById(TPrimaryKey id)
{
return await Repository.Find(id);
}
public async Task<TEntity> GetSingle(Expression<Func<TEntity, bool>> whereCondition)
{
return await Repository.GetQueryable().Where(whereCondition).FirstOrDefaultAsync();
}
public async Task<IEnumerable<TEntity>> GetAll()
{
return await Repository.GetAll();
}
public IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> whereCondition)
{
return Repository.GetQueryable().Where(whereCondition);
}
public IQueryable<TEntity> GetAllQueryable()
{
return Repository.GetQueryable();
}
public IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> whereCondition)
{
return Repository.GetQueryable().Where(whereCondition);
}
public virtual async Task<TEntity> Create(TEntity entity)
{
return await Repository.Add(entity);
}
public virtual async Task Delete(TEntity entity)
{
await Repository.Delete(entity);
}
public virtual async Task Update(TEntity entity)
{
await Repository.Edit(entity);
}
public async Task<long> Count(Expression<Func<TEntity, bool>> whereCondition)
{
return await Repository.GetQueryable().Where(whereCondition).CountAsync();
}
public async Task<long> Count()
{
return await Repository.GetQueryable().CountAsync();
}
public async Task<IEnumerable<TEntity>> BulkInsert(IEnumerable<TEntity> entities)
{
return await Repository.BulkInsert(entities);
}
public async Task BulkUpdate(IEnumerable<TEntity> entities)
{
await Repository.BulkUpdate(entities);
}
}
and concrete implementations of services:
AddressService:
public class AddressService : ServiceBase<Address, IBaseRepository<Address, Guid>, Guid>, IAddressService
{
public AddressService(IBaseRepository<Address, Guid> rep) : base(rep)
{
}
public async Task<Address> VerifyAddress(Address address)
{
//logic
}
}
ProductController:
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
private readonly IAddressService _addressService;
private readonly ILogger _logger;
private readonly IMapper _mapper;
public ProductController (IProductService productService,
IAddressService addressService,
ILogger<ProductController> logger,
IMapper mapper)
{
_packageService = packageService;
_addressService = addressService;
_logger = logger;
_mapper = mapper;
}
[HttpGet]
public async Task<IActionResult> GetAllProductsWithAddresses()
{
try
{
var products = await _productService.GetAllQueryable().Include(x => x.Address).ToListAsync();
return Ok(_mapper.Map<List<ProductResponse>>(products));
}
catch (Exception e)
{
_logger.LogError($"An unexpected error occured: ${e}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
Lets say for example if I had an POST endpoint in ProductController where I need to insert data in 3 different database tables: Address, ProductSize and ProductImage. I would have 3 services and I would call _addressService.Add(address), _productSize.Add(productSize) and _productImageService(image) in my controller. How can I support transactions here if DatabaseContext is located in BaseRepository, what is the best practice?
How can I support transactions here if DatabaseContext is located in BaseRepository, what is the best practice?
Best practice is to throw out all that junk and just have your controller talk to the DbContext, or have the controller talk to a business service that talks to the DbContext.
Barring that DI should inject the same DbContext instance in each service or repository, so you can expose Unit-of-work methods on any one of them. Or you could introduce an additional service for managing cross-repo operations. EG
public class UnitOfWork
{
DbContext db;
public UnitOfWork(DbContext db)
{
this.db = db;
}
IDbContextTransaction BeginTransaction() => db.Database.BeginTransaction();
void CommitTransaction() => db.Database.CommitTransaction();
int SaveChanges() => db.SaveChanges();
}

Generic repository implementation with base repository as abstract class

net core project. I am trying to implement generic repository pattern. I have base repository which is abstract. So I am overriding base repository and creating my other repositories. The implementation looks below.
BaseRepository.cs
public abstract class BaseRepository<T>: IBaseRepository<T>
where T : class
{
private readonly DbContext dbContext;
private readonly DbSet<T> dbSet;
public BaseRepository(DbContext dbContext)
{
this.dbContext = dbContext;
this.dbSet = dbContext?.Set<T>();
}
public async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = this.dbSet;
foreach (Expression<Func<T, object>> include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return await query.ToListAsync().ConfigureAwait(false);
}
}
}
Repository.cs
public class Repository<T> : BaseRepository<T>, IRepository<T>
where T : class
{
private readonly DbContext dbContext;
private readonly DbSet<T> dbSet;
public Repository(DbContext context) : base(context)
{
}
public async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = this.dbSet;
foreach (Expression<Func<T, object>> include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return await query.ToListAsync().ConfigureAwait(false);
}
}
}
UnitOfWork.cs
public class UnitOfWork<TContext> : IUnitOfWork<TContext>
where TContext : DbContext, IDisposable
{
private readonly MyContext context;
private Dictionary<(Type type, string name), object> _repositories;
public UnitOfWork(MyContext myContext)
{
if (this.context == null)
{
this.context = myContext;
}
}
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
{
return (IRepository<TEntity>)GetOrAddRepository(typeof(TEntity), new Repository<TEntity>(Context));
}
public async Task<int> CompleteAsync()
{
try
{
return await this.context.SaveChangesAsync().ConfigureAwait(false);
}
catch (DbUpdateConcurrencyException ex)
{
ex.Entries.Single().Reload();
return await this.context.SaveChangesAsync().ConfigureAwait(false);
}
}
internal object GetOrAddRepository(Type type, object repo)
{
_repositories ??= new Dictionary<(Type type, string Name), object>();
if (_repositories.TryGetValue((type, repo.GetType().FullName), out var repository)) return repository;
_repositories.Add((type, repo.GetType().FullName), repo);
return repo;
}
}
}
Then In my startup.cs file when I register
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(this.Configuration["AzureSQLConnectionString"]));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped<IUnitOfWork, UnitOfWork>();
UnitofWork throws me error using the generic type 'UnitofWork' requires 1 type of argument. Then I added services.AddScoped<IUnitOfWork, UnitOfWork<MyContext>>(). Now Its throwing error in GetAsync method in Repository.cs. The error is Parameter: source is required, it cannot be empty. Then I found
IQueryable query = this.dbSet;
query is empty. So what I understood is I have some issues with establishing connection to db. Can someone help me what exactly I am doing wrong here? Any help would be greatly appreciated. Thank you
Change these classes as follows:
public abstract class BaseRepository<T>: IBaseRepository<T>
where T : class
{
protected readonly DbContext dbContext;
protected readonly DbSet<T> dbSet;
public BaseRepository(DbContext dbContext)
{
this.dbContext = dbContext;
this.dbSet = dbContext?.Set<T>();
}
public virtual async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = this.dbSet;
foreach (Expression<Func<T, object>> include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return await query.ToListAsync().ConfigureAwait(false);
}
}
public class Repository<T> : BaseRepository<T>, IRepository<T>
where T : class
{
public Repository(DbContext context) : base(context)
{
}
//override GetAsync if different than base. In your case it is not.
}

The LINQ expression 'DbSet<Contacts>\n .Where(c => __res_RelatedContacts_0\n.Any(r => r.IdVal.Equals(c.ContactId)))' could not be translated

I am using asp.net core 3.1 and EFCore 3.1.1.
Code :
public class SampleDbContext : DbContext
{
public DbSet<Articles> Articles
{
get;
set;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new NumberToStringConverter<int>();
// For Articles
modelBuilder.Entity<Articles>().OwnsMany(p => p.RelatedCountries, a =>
{
a.WithOwner().HasForeignKey("Articlesid");
a.Property<int>("id");
a.Property(o => o.IdVal);
}
);
}
}
public class Articles
{
public int ArticleId
{
get;
set;
}
public ICollection<RelatedEntityId> RelatedContacts
{
get;
set;
}
}
public class RelatedEntityId
{
public int IdVal
{
get;
set;
}
}
My Service class :
public class ArticleService : IArticleService
{
private readonly SampleDbContext _dbContext;
private readonly ICacheService<Images, ImageDTO> _imageCacheService;
private readonly ICacheService<Countries, CountryDTO> _countryCacheService;
private readonly ICommonService _commonService;
private readonly IMapper _mapper;
public ArticleService(SampleDbContext dbContext, ICacheService<Images, ImageDTO> imageCacheService, ICacheService<Countries, CountryDTO> countryCacheService, ICommonService commonService, IMapper mapper)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_imageCacheService = imageCacheService ?? throw new ArgumentNullException(nameof(imageCacheService));
_countryCacheService = countryCacheService ?? throw new ArgumentNullException(nameof(countryCacheService));
_commonService = commonService ?? throw new ArgumentNullException(nameof(commonService));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public async Task<ArticleDTO> GetArticleDetailsAsync(int articleId, int defaultLanguageId, List<int> localeLanguageIdList)
{
// Get articles
var articles = await _dbContext.Articles.Where(a => a.ArticleId.Equals(articleId) && a.IsPublished.Equals(true)).OrderByDescending(a => a.ArticleId).Select(a => new
{
a.ArticleId, a.PublishedDate, a.Author, a.ImageId, a.State, a.Type, a.SubType, a.ResourcePosition, a.DisclaimerId, a.CreatedDate, a.UpdatedDate, a.NotificationSentDate, a.Title, a.TeaserText, a.Content, a.RelatedContacts, a.LanguageId
}
).AsNoTracking().ToListAsync();
var res = articles.Where(a => a.LanguageId.Equals(defaultLanguageId) && Convert.ToDateTime(a.PublishedDate) <= DateTime.UtcNow).FirstOrDefault();
var contacts = new List<ContactDTO>();
if (res.RelatedContacts.Count > 0)
{
contacts = await _dbContext.Contacts.Where(co => res.RelatedContacts.Any(r => r.IdVal.Equals(co.ContactId))).ToListAsync();
}
}
}
Error Message:
"message": "GraphQL.ExecutionError: The LINQ expression 'DbSet\n .Where(c => __res_RelatedContacts_0\n .Any(r => r.IdVal.Equals(c.ContactId)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.\n ---> System.InvalidOperationException: The LINQ expression 'DbSet\n .Where(c => __res_RelatedContacts_0\n .Any(r => r.IdVal.Equals(c.ContactId)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.\n at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& )\n at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\n at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)\n at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)\n at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)\n at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_01.<ExecuteAsync>b__0()\n at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func1 compiler)\n at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler)\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetAsyncEnumerator(CancellationToken cancellationToken)\n at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator()\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)\n at Author.Query.Persistence.ArticleService.GetArticleDetailsAsync(Int32 articleId, Int32 defaultLanguageId, List1 localeLanguageIdList) in /src/QueryStack/Author.Query.Persistence/ArticleService.cs:line 88\n at Author.Query.Persistence.ArticleService.GetArticleAsync(Int32 articleId, String countryName) in /src/QueryStack/Author.Query.Persistence/ArticleService.cs:line 47\n at GraphQL.DataLoader.DataLoaderBase1.DispatchAsync(CancellationToken cancellationToken)\n at Author.Query.New.API.GraphQL.Resolvers.ArticlesResolver.<>c__DisplayClass3_1.<b__2>d.MoveNext() in /src/QueryStack/Author.Query.New.API/GraphQL/Resolvers/ArticlesResolver.cs:line 41\n--- End of stack trace from previous location where exception was thrown ---\n at GraphQL.Types.ResolveFieldContext1.TryAsyncResolve[TResult](Func2 resolve, Func`2 error)\n --- End of inner exception stack trace ---"
Can anyone help me to know how to fix this issue?

BsonDocument to dynamic Expression or in-memory caching of results

I am trying to write a proxy class for IMongoCollection so that I can use an in-memory cache for some of the method implementations. The issue, however, is that almost all the filters are of type FilterDefinition<T> which means we can call Render on them to get a BsonDocument. I am wondering if there is a way to convert the filter BsonDocument to dynamic Expression so that I can run it against my in-memory List<T>. Or maybe there is a better approach to do in-memory caching which I am not aware of. Thank you.
Update:
I was tempted to write a solution as #simon-mourier suggested but the problem with this hacky solution is that C# mongo driver returns IAsyncCursor<T> for find operations which is basically a stream of BsonDocuments and after each read it is pointing to the last index and disposes itself. And there is no way to reset the stream to its initial position. Which means the following code works the first time but after that, we get an exception of the cursor is at the end of stream and already disposed.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using DAL.Extensions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace DAL.Proxies
{
public static class MongoCollectionProxy
{
private static readonly Dictionary<Type, object> _instances = new Dictionary<Type, object>();
public static IMongoCollection<T> New<T>(IMongoCollection<T> proxy)
{
return ((IMongoCollection<T>)_instances.AddOrUpdate(typeof(T), () => new MongoCollectionBaseProxyImpl<T>(proxy)));
}
}
public class MongoCollectionBaseProxyImpl<T> : MongoCollectionBase<T>
{
private readonly IMongoCollection<T> _proxy;
private readonly ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>();
public MongoCollectionBaseProxyImpl(IMongoCollection<T> proxy)
{
_proxy = proxy;
}
public override Task<IAsyncCursor<TResult>> AggregateAsync<TResult>(PipelineDefinition<T, TResult> pipeline,
AggregateOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.AggregateAsync(pipeline, options, cancellationToken);
}
public override Task<BulkWriteResult<T>> BulkWriteAsync(IEnumerable<WriteModel<T>> requests,
BulkWriteOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.BulkWriteAsync(requests, options, cancellationToken);
}
[Obsolete("Use CountDocumentsAsync or EstimatedDocumentCountAsync instead.")]
public override Task<long> CountAsync(FilterDefinition<T> filter, CountOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.CountAsync(filter, options, cancellationToken);
}
public override Task<IAsyncCursor<TField>> DistinctAsync<TField>(FieldDefinition<T, TField> field,
FilterDefinition<T> filter, DistinctOptions options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.DistinctAsync(field, filter, options, cancellationToken);
}
public override async Task<IAsyncCursor<TProjection>> FindAsync<TProjection>(FilterDefinition<T> filter,
FindOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
// ReSharper disable once SpecifyACultureInStringConversionExplicitly
return await CacheResult(filter.Render().ToString(), () => _proxy.FindAsync(filter, options, cancellationToken));
}
public override async Task<TProjection> FindOneAndDeleteAsync<TProjection>(FilterDefinition<T> filter,
FindOneAndDeleteOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await InvalidateCache(_proxy.FindOneAndDeleteAsync(filter, options, cancellationToken));
}
public override async Task<TProjection> FindOneAndReplaceAsync<TProjection>(FilterDefinition<T> filter,
T replacement,
FindOneAndReplaceOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await InvalidateCache(_proxy.FindOneAndReplaceAsync(filter, replacement, options,
cancellationToken));
}
public override async Task<TProjection> FindOneAndUpdateAsync<TProjection>(FilterDefinition<T> filter,
UpdateDefinition<T> update,
FindOneAndUpdateOptions<T, TProjection> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return await InvalidateCache(_proxy.FindOneAndUpdateAsync(filter, update, options, cancellationToken));
}
public override Task<IAsyncCursor<TResult>> MapReduceAsync<TResult>(BsonJavaScript map, BsonJavaScript reduce,
MapReduceOptions<T, TResult> options = null,
CancellationToken cancellationToken = new CancellationToken())
{
return _proxy.MapReduceAsync(map, reduce, options, cancellationToken);
}
public override IFilteredMongoCollection<TDerivedDocument> OfType<TDerivedDocument>()
{
return _proxy.OfType<TDerivedDocument>();
}
public override IMongoCollection<T> WithReadPreference(ReadPreference readPreference)
{
return _proxy.WithReadPreference(readPreference);
}
public override IMongoCollection<T> WithWriteConcern(WriteConcern writeConcern)
{
return _proxy.WithWriteConcern(writeConcern);
}
public override CollectionNamespace CollectionNamespace => _proxy.CollectionNamespace;
public override IMongoDatabase Database => _proxy.Database;
public override IBsonSerializer<T> DocumentSerializer => _proxy.DocumentSerializer;
public override IMongoIndexManager<T> Indexes => _proxy.Indexes;
public override MongoCollectionSettings Settings => _proxy.Settings;
private async Task<TResult> CacheResult<TResult>(string key, Func<Task<TResult>> result)
{
return _cache.ContainsKey(key) ? (TResult) _cache[key] : (TResult) _cache.AddOrUpdate(key, await result());
}
private TResult InvalidateCache<TResult>(TResult result)
{
_cache.Clear();
return result;
}
}
}
if your only concern is creating a decorator class which applies some sort of caching strategy in order to avoid some database access I think that you should try a simpler approach to the problem.
I'm not saying that trying to write a decorator for the interface IMongoCollection<T> is wrong per se, I'm just saying that it's not the simplest solution to your problem.
A better approach could be, instead, shrinking your focus to the specific needs of your application. In the following paragraph I'll try to explain my point of view.
Let's suppose that your application must frequently access a User collection and that the user data don't change very often, so that they are good candidates for a simple caching strategy. At that point you can decide to define an abstraction like IUserRepository and to shape that abstraction for your application needs. Consider, for instance, this interface definition:
public interface IUserRepository
{
User GetById(Guid userId);
ReadOnlyCollection<User> GetAll();
}
At this point you will write a concrete implementation which uses MongoDB as a persistence layer:
public class MongoUserRepository: IUserRepository
{
private readonly IMongoCollection<User> userCollection;
public MongoUserRepository(IMongoCollection<User> userCollection)
{
this.userCollection = userCollection ?? throw new ArgumentNullException(nameof(userCollection));
}
// interface members implementation omitted for simplicity
}
Then, you should define a decorator for IUserRepository implementing the caching aspect, as follows:
public class UserRepositoryCachingDecorator: IUserRepository
{
private readonly IUserRepository decoratee;
private readonly IMemoryCache cache;
public UserRepositoryCachingDecorator(IUserRepository decoratee, IMemoryCache cache)
{
this.decoratee = decoratee ?? throw new ArgumentNullException(nameof(decoratee));
this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
}
// interface members implementation omitted for simplicity
}
In my opinion this is a better approach, because it's much simpler than trying to write a general purpose proxy over IMongoCollection<T> and it only requires you to focus on the specific needs of your application.

Categories