Giter Site home page Giter Site logo

Filtering capabilities about instantapis HOT 13 OPEN

csharpfritz avatar csharpfritz commented on July 22, 2024
Filtering capabilities

from instantapis.

Comments (13)

ScottKane avatar ScottKane commented on July 22, 2024 1

@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.

csharpfritz avatar csharpfritz commented on July 22, 2024

This feels like something that should be handed off to a standard framework like GraphQL that this library enables.

from instantapis.

ScottKane avatar ScottKane commented on July 22, 2024

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.

ScottKane avatar ScottKane commented on July 22, 2024

Sorry for the code dump but this touches filtering and repositories at the same time.

from instantapis.

davidbuckleyni avatar davidbuckleyni commented on July 22, 2024

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.

ScottKane avatar ScottKane commented on July 22, 2024

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.

murugaratham avatar murugaratham commented on July 22, 2024

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.

ScottKane avatar ScottKane commented on July 22, 2024

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.

murugaratham avatar murugaratham commented on July 22, 2024

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.

ScottKane avatar ScottKane commented on July 22, 2024

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.

ScottKane avatar ScottKane commented on July 22, 2024

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.

murugaratham avatar murugaratham commented on July 22, 2024

@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.

mmassari avatar mmassari commented on July 22, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.