Comments (13)
@murugaratham haha sorry we are totally on the same page, I'm not expecting someone else to do it, I just don't want to start on a PR that ultimately wont get approved. I think we need some input from @csharpfritz
from instantapis.
This feels like something that should be handed off to a standard framework like GraphQL that this library enables.
from instantapis.
I don't think this is something specific to GraphQl, would be just as nice to use something like the following so that repository/filtering kind of go hand in hand.
public class MyClass
{
public string Name { get; set; }
public string Description { get; set; }
}
public interface ISpecification<T> where T : class, IEntity
{
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
}
public abstract class FilterSpecification<T> : ISpecification<T> where T : class, IEntity
{
public Expression<Func<T, bool>> Criteria { get; set; }
public List<Expression<Func<T, object>>> Includes { get; } = new();
public List<string> IncludeStrings { get; } = new();
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression) => Includes.Add(includeExpression);
protected virtual void AddInclude(string includeString) => IncludeStrings.Add(includeString);
}
public class MyClassFilterSpecification : FilterSpecification<MyClass>
{
public MyClassFilterSpecification(string searchString) // This could maybe be a MyClassFilterOptions object in the future but just a simple search string for now.
{
if (!string.IsNullOrEmpty(searchString))
Criteria = p => p.Name.Contains(searchString) ||
p.Description.Contains(searchString);
else
Criteria = p => true;
}
}
This would work well with a repository pattern, consider the following repository:
public interface IEntity { }
public interface IEntity<TId> : IEntity
{
public TId Id { get; set; }
}
public interface IRepositoryAsync<T, in TId> where T : class, IEntity<TId>
{
IQueryable<T> Entities { get; }
Task<T> GetByIdAsync(TId id);
Task<List<T>> GetAllAsync();
Task<List<T>> GetPagedResponseAsync(int pageNumber, int pageSize);
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
public interface IUnitOfWork<TId> : IDisposable
{
IRepositoryAsync<T, TId> Repository<T>() where T : IEntity<TId>;
Task<int> Commit(CancellationToken cancellationToken);
Task<int> CommitAndRemoveCache(CancellationToken cancellationToken, params string[] cacheKeys);
Task Rollback();
}
public class UnitOfWork<TId> : IUnitOfWork<TId>
{
private readonly IAppCache _cache;
private readonly ApplicationContext _dbContext;
private Hashtable _repositories;
private bool _disposed;
public UnitOfWork(
ApplicationContext dbContext,
IAppCache cache)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_cache = cache;
}
public IRepositoryAsync<TEntity, TId> Repository<TEntity>() where TEntity : IEntity<TId>
{
_repositories ??= new Hashtable();
var type = typeof(TEntity).Name;
if (_repositories.ContainsKey(type)) return (IRepositoryAsync<TEntity, TId>) _repositories[type];
var repositoryType = typeof(RepositoryAsync<,>);
var repositoryInstance = Activator.CreateInstance(
repositoryType.MakeGenericType(
typeof(TEntity),
typeof(TId)),
_dbContext);
_repositories.Add(
type,
repositoryInstance);
return (IRepositoryAsync<TEntity, TId>) _repositories[type];
}
public async Task<int> Commit(CancellationToken cancellationToken) => await _dbContext.SaveChangesAsync(cancellationToken);
public async Task<int> CommitAndRemoveCache(CancellationToken cancellationToken, params string[] cacheKeys)
{
var result = await _dbContext.SaveChangesAsync(cancellationToken);
foreach (var cacheKey in cacheKeys) _cache.Remove(cacheKey);
return result;
}
public Task Rollback()
{
_dbContext.ChangeTracker.Entries()
.ToList()
.ForEach(x => x.Reload());
return Task.CompletedTask;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
if (disposing)
_dbContext.Dispose();
_disposed = true;
}
}
public class RepositoryAsync<T, TId> : IRepositoryAsync<T, TId> where T : IEntity<TId>
{
private readonly ApplicationContext _dbContext;
public RepositoryAsync(ApplicationContext dbContext) => _dbContext = dbContext;
public IQueryable<T> Entities => _dbContext.Set<T>();
public async Task<T> AddAsync(T entity)
{
await _dbContext.Set<T>().AddAsync(entity);
return entity;
}
public Task DeleteAsync(T entity)
{
_dbContext.Set<T>().Remove(entity);
return Task.CompletedTask;
}
public async Task<List<T>> GetAllAsync() => await _dbContext.Set<T>().ToListAsync();
public async Task<T> GetByIdAsync(TId id) => await _dbContext.Set<T>().FindAsync(id);
public async Task<List<T>> GetPagedResponseAsync(int pageNumber, int pageSize)
{
return await _dbContext.Set<T>()
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.AsNoTracking()
.ToListAsync();
}
public Task UpdateAsync(T entity)
{
var selected = _dbContext.Set<T>().Find(entity.Id);
if (selected is not null)
_dbContext.Entry(selected).CurrentValues.SetValues(entity);
return Task.CompletedTask;
}
}
public static IQueryable<T> Specify<T>(this IQueryable<T> query, ISpecification<T> spec)
where T : class, IEntity
{
var queryableResultWithIncludes = spec.Includes.Aggregate(
query,
(current, include) => current.Include(include));
var secondaryResult = spec.IncludeStrings.Aggregate(
queryableResultWithIncludes,
(current, include) => current.Include(include));
return secondaryResult.Where(spec.Criteria);
}
This would allow the following in your business logic:
var data = await _unitOfWork.Repository<MyClass>()
.Entities.Specify(new MyClassFilterSpecification("some search string"))
.Select(expression)
.AsNoTracking();
from instantapis.
Sorry for the code dump but this touches filtering and repositories at the same time.
from instantapis.
Dot net in general tends to be stirring away from the repo pattern cause it can be daunting to new comers. Thats why ef core built most of into the system so we wouldnt have to do the repo pattern anymore @csharpfritz can correct me if am wrong here.
from instantapis.
Last I heard was that it was a strong recommendation to be using the repository pattern for data access full stop, regardless of it coming from EF. That way if you slot in something else that doesn't have it built in for you, you already have everything you need but I'm happy to be wrong on this one.
from instantapis.
What I felt is that, this library enables very rapid api implementation, but with just crud seems to basic, having filtering & sorting will drive it to have more real world adoption.
with or without repository, it’s the underlying implementation that this library potentially can provide, it would be a game changer if so
from instantapis.
For me it's a case of what is the goal, are we trying to speed up prototyping, or trying to make your life as a .NET dev easier. I would argue that to achieve the 2nd, you're probably doing the first anyways. I think giving more options for what can be scaffolded is always going to be good. It's not like someone who wants a quick prototype is any worse off, they just don't set up the filtering config
from instantapis.
What this library essentially does IMO is removing a lot of boiler plate, less code less bug and higher productivity, but basic CRUD is good to meet 60-70% of use case (pluck out of thin air estimates), but sorting & filtering is pretty much in a lot of other use cases.
@csharpfritz felt that this could be left in developers hands, I respect that, since graphql, OData exists to do just that.
from instantapis.
As you can see from my code, there is still a lot to type to get filtering up and running. To me, if this product doesn't support the things I need, I and others are unlikely to use it. Because if I use it to prototype and then have to go and write all that crap manually after to support filtering, why not just do that from the start.
from instantapis.
I will happily work on the feature, I am just not a fan of the whole "it's not really in the scope of the project" when it totally is. Filtering/Pagination options are the difference between this being "good for POC" and legit just "good everywhere"
from instantapis.
@ScottKane don’t get me wrong, I’m with you on this, I am the issue author 🤣
but rather then trying to get maintainers and collaborators to build this, we can just +1 this issue to let them know about the demand or rather desire to have this feature.
If I get some time, I might think about a pr for it
from instantapis.
Maybe will be easy add an option to enable OData with a configuration.
Something like this: UseOData(p=>p.AllowFiltering, p.AllowSorting, p.AllowPaging, p.AllowSelecting)
So in your GET endpoint you will have all those great stuff already done by OData in a standard and optimized way.
If you don't like OData response format it can be add a switch like RemoveMetadata() and you will get clean json.
from instantapis.
Related Issues (20)
- Add support for query parameters on JSON-first APIs
- Remove 'Fritz' from the project, namespace, and NuGet package names HOT 17
- Spaces vs. Tabs HOT 2
- Add Config support for JSON APIs
- Allow authentication/authorization to be configured HOT 1
- Allow OpenAPI configuration of individual APIs
- Allow injection of business logic HOT 2
- Allow mapping of data to a ViewModel HOT 2
- 编译缺少文件 HOT 7
- IModel HOT 5
- Integration with any mapping library like Automapper HOT 2
- IncludeTable/ExcludeTable - appropriate noun? HOT 3
- Validation layer HOT 3
- Custom API Endpoints HOT 1
- Support for Startup.cs - ConfigureServices and Configure HOT 8
- .WithTags("Nome of the API group");
- MapInstantAPIs fails when Dbset properties with [Keyless] attribute
- MapInstantApis fails when key is not named id when using fluent interface [Bug] HOT 5
- KeyNotFoundException: The given key 'xxxx.DataModel.Models.AspNetUserLogin' was not present in the dictionary. HOT 1
- Allow custom Dtos for endpoints
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from instantapis.