Skip to content

Commit 0155822

Browse files
committed
Enhance EfRepository with batch, patch, and paging support
Added UpdateRangeAsync, RemoveRangeAsync, and PatchAsync for batch and partial updates. Introduced Query and Query(predicate) for flexible querying, and GetPagedAsync for paginated results. Reworked ExistsAsync and improved argument validation. These changes increase repository flexibility and support advanced data access patterns.
1 parent a6a4649 commit 0155822

1 file changed

Lines changed: 82 additions & 5 deletions

File tree

src/TinyRepository/EfRepository.cs

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
using Microsoft.EntityFrameworkCore;
33
using TinyRepository.Entities;
44
using TinyRepository.Interfaces;
5+
using TinyRepository.Paging;
56

67
namespace TinyRepository;
78

89
public class EfRepository<T, TKey> : IRepository<T, TKey>
9-
where T : class, IEntity<TKey>
10-
where TKey : IEquatable<TKey>
10+
where T : class, IEntity<TKey>
11+
where TKey : IEquatable<TKey>
1112
{
1213
protected readonly DbContext _context;
1314
protected readonly DbSet<T> _dbSet;
@@ -20,6 +21,7 @@ public EfRepository(DbContext context)
2021

2122
public virtual async Task<T?> GetByIdAsync(TKey id, CancellationToken cancellationToken = default)
2223
{
24+
// FindAsync returns tracked entity by default
2325
return await _dbSet.FindAsync([id], cancellationToken);
2426
}
2527

@@ -48,11 +50,26 @@ public virtual void Update(T entity)
4850
_dbSet.Update(entity);
4951
}
5052

53+
public virtual Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
54+
{
55+
// EF Core does not have an async UpdateRange; updating entity states is in-memory and quick.
56+
_dbSet.UpdateRange(entities);
57+
58+
return Task.CompletedTask;
59+
}
60+
5161
public virtual void Remove(T entity)
5262
{
5363
_dbSet.Remove(entity);
5464
}
5565

66+
public virtual Task RemoveRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
67+
{
68+
// Removing ranges is in-memory (marking entities Deleted). Return completed task for async signature.
69+
_dbSet.RemoveRange(entities);
70+
return Task.CompletedTask;
71+
}
72+
5673
public virtual async Task RemoveByIdAsync(TKey id, CancellationToken cancellationToken = default)
5774
{
5875
var entity = await GetByIdAsync(id, cancellationToken);
@@ -63,15 +80,75 @@ public virtual async Task RemoveByIdAsync(TKey id, CancellationToken cancellatio
6380
}
6481
}
6582

66-
public virtual async Task<bool> ExistsAsync(TKey id, CancellationToken cancellationToken = default)
83+
public virtual async Task<bool> PatchAsync(TKey id, Action<T> patchAction, CancellationToken cancellationToken = default)
6784
{
68-
var entity = await GetByIdAsync(id, cancellationToken);
85+
if (patchAction == null)
86+
{
87+
throw new ArgumentNullException(nameof(patchAction));
88+
}
6989

70-
return entity is not null;
90+
// Use FindAsync to get a tracked entity (so changes are tracked)
91+
var entity = await _dbSet.FindAsync(new object[] { id }, cancellationToken);
92+
93+
if (entity is null)
94+
{
95+
return false;
96+
}
97+
98+
patchAction(entity);
99+
100+
// Mark modified to ensure changes are persisted even if change tracking misses something
101+
_context.Entry(entity).State = EntityState.Modified;
102+
return true;
103+
}
104+
105+
public virtual IQueryable<T> Query(bool asNoTracking = true)
106+
{
107+
return asNoTracking ? _dbSet.AsNoTracking() : _dbSet;
108+
}
109+
110+
public virtual IQueryable<T> Query(Expression<Func<T, bool>> predicate, bool asNoTracking = true)
111+
{
112+
var q = asNoTracking ? _dbSet.AsNoTracking() : _dbSet;
113+
114+
return q.Where(predicate);
115+
}
116+
117+
public virtual async Task<PagedResult<T>> GetPagedAsync(int pageNumber, int pageSize, Func<IQueryable<T>, IOrderedQueryable<T>>? orderBy = null,
118+
Expression<Func<T, bool>>? filter = null, bool asNoTracking = true, CancellationToken cancellationToken = default)
119+
{
120+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageNumber);
121+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageSize);
122+
123+
var query = asNoTracking ? _dbSet.AsNoTracking().AsQueryable() : _dbSet.AsQueryable();
124+
125+
if (filter is not null)
126+
{
127+
query = query.Where(filter);
128+
}
129+
130+
var totalCount = await query.CountAsync(cancellationToken);
131+
132+
if (orderBy is not null)
133+
{
134+
query = orderBy(query);
135+
}
136+
137+
var skip = (pageNumber - 1) * pageSize;
138+
var items = await query.Skip(skip).Take(pageSize).ToListAsync(cancellationToken);
139+
140+
return new PagedResult<T>(items, totalCount, pageNumber, pageSize);
71141
}
72142

73143
public virtual async Task<int> CountAsync(CancellationToken cancellationToken = default)
74144
{
75145
return await _dbSet.CountAsync(cancellationToken);
76146
}
147+
148+
public virtual async Task<bool> ExistsAsync(TKey id, CancellationToken cancellationToken = default)
149+
{
150+
var entity = await GetByIdAsync(id, cancellationToken);
151+
152+
return entity is not null;
153+
}
77154
}

0 commit comments

Comments
 (0)