How to implement the standard presented in C# with Entityframework?

Asked

Viewed 407 times

6

I’m thinking of a way to apply the pattern (multi-tenant), raised in that question (Standard that has contributed to the reliability of software that needs to meet complex models such as multi-business models), in an application c# with Entityframework.

As the title of the issue presents, the pattern is based on a way to protect the application so that data from a Reseller, its clients companies and its customers do not have their data presented to others.

I believe the pattern multi-tenant ("multi-tenant") would be suitable for this case - although it is a single operator of a single company to access the various tenants. That article briefly describes the philosophy behind the multitenancy.

It is a multi-profile access application.

So I thought I’d do this in a base class for the repositories.
So I started to create something like this:

An interface to set a pattern:

public interface IRepository<TContext, T, TKey> : IDisposable
    where TContext : DbContext, new()
    where T : class
{
    T Get(TKey id);
    IQueryable<T> GetAll(Expression<Func<T, bool>> filter);

    IQueryable<T> Query(params Expression<Func<T, object>>[] includes);

    T Add(T entity);
    List<T> AddRange(List<T> items);
    bool Edit(T entity);
    bool Delete(TKey id);
    bool Delete(T entity);
    int Delete(Expression<Func<T, bool>> where);

    int SaveChanges();
}

And an abstract class, which implements this interface, to be inherited by the repository classes:

public abstract class CustomRepository<TContext, T, TKey> : IRepository<TContext, T, TKey>
    where TContext : DbContext, new()
    where T : class
{
    private TContext _context = null;
    private bool _responsibleContext = false;

    /// <summary>
    /// constructor
    /// </summary>
    public CustomRepository()
    {
        _context = new TContext();
        _responsibleContext = true;
    }

    /// <summary>
    /// constructor with a DbContext param
    /// </summary>
    /// <param name="context">A DbContext param</param>
    public CustomRepository(TContext context)
    {
        _context = context;
        _responsibleContext = false;
    }

    /// <summary>
    /// disposer
    /// </summary>
    public void Dispose()
    {
        if (_responsibleContext && _context != null)
            _context.Dispose();
        GC.SuppressFinalize(this);
    }

    #region other interface implementations ...

    public T Get(TKey id)
    {
        return _context.Set<T>().Find(id);
    }

    public IQueryable<T> GetAll(Expression<Func<T, bool>> filter)
    {
        return Query().Where(filter);
    }

    public IQueryable<T> Query(params Expression<Func<T, object>>[] includes)
    {
        IQueryable<T> set = _context.Set<T>();
        foreach (var include in includes)
            set = set.Include(include);
        return set;
    }

    public T Add(T entity)
    {
        _context.Set<T>().Add(entity);
        SaveChanges();
        return entity;
    }

    public List<T> AddRange(List<T> items)
    {
        _context.Set<T>().AddRange(items);
        SaveChanges();
        return items;
    }

    public bool Edit(T entity)
    {
        var result = false;
        _context.Entry<T>(entity).State = EntityState.Modified;
        if (SaveChanges() > 0)
            result = true;
        return result;
    }

    public bool Delete(TKey id)
    {
        var entity = Get(id);
        return Delete(entity);
    }

    public bool Delete(T entity)
    {
        _context.Entry(entity).State = EntityState.Deleted;
        return SaveChanges() > 0;
    }

    public int Delete(Expression<Func<T, bool>> where)
    {
        var entries = Query().Where(where);
        _context.Set<T>().RemoveRange(entries);
        return SaveChanges();
    }

    public int SaveChanges()
    {
        return _context.SaveChanges();
    }

    #endregion other interface implementations ...
}

And if I understood the standard proposal correctly, I would have to pass an instance reference from my Session User, so I could change the methods to something like:

public T Get(TKey id)
{
    var entry = _context.Set<T>().Find(id);
    if (entry.RevendaId == userSession.RevendaId && 
      entry.EmpresaId == userSession.EmpresaId && 
      entry.ClienteId == userSession.ClienteId)
        return entry;
    else
        return null;
}

And also:

public IQueryable<T> Query(params Expression<Func<T, object>>[] includes)
{
    IQueryable<T> set = _context.Set<T>();
    foreach (var include in includes)
        set = set.Include(include);

    set = set.Where(x => x.RevendaId == userSession.RevendaId &&
      x.EmpresaId == userSession.EmpresaId &&
      x.ClienteId == userSession.ClienteId);

    return set;
}

And apply this approach to all methods.

Well, I have two questions:

  1. This approach in trying to implement the standard would be a correct way?

    1.1. If not, how would a correct implementation of this standard in the presented scenario?

  2. If yes, how do I pass an instance of the Session User to the repository instance?

  • to make your situation worse, there is still the authorization, that each user will have certain authorizations per company kkk is quite complex this, I will follow the answers

  • 1

    You like this repository junk, right? Jeez, life.

  • 1

    @Tiagosilva I will answer your question, but first I will recommend you this reading here from the creator of the Entity Framework. Then I think it will become clearer why the EF repository is a gadget.

  • @Ciganomorrisonmendez: I think you made a little mess... the Entity Framework is from Microsoft. Ayende is the guy from Nhibernate.

  • @Miguelangelo I don’t. This guy right here, then.

  • Ah tá... the site refers to an Entity Framework Profiler, made by Ayende.

  • That Ayende is the Dick of The Galaxies.

Show 2 more comments

3 answers

3


What you’re calling a repository is not quite the repository standard, because you’re leaking Iqueryable... but I don’t want to go into too much detail about nomenclature, because that would be too purist. That said, I will continue to refer to the pattern described by you as repository.

What seems to me to be your goal is:

  • create an encapsulated component that can be reused

  • apply filters to all queries in order to make it impossible for users of one tenant A to perform operations on data of another tenant B

  • pass the logged-in user as parameter of this component, and use this information in the filter (i.e., the logged-in user is associated with a specific tenant)

My suggestions for achieving your goal:

  • use dependency injection to inject the interface IRepository<T> wherever you want (your dependents). Example:

    class EuDependoDeUmRepositorio
    {
        IRepository<MinhaEntidade> repositorio;
        public EuDependoDeUmRepositorio(IRepository<MinhaEntidade> repositorio)
        {
            this.repositorio = repositorio;
        }
    
        public void FazerAlgumaCoisa()
        {
            // operações sobre `this.repositorio`
        }
    }
    
  • make a generic implementation of IRepository<T> dependent on an interface representing the logged in user, which will be injected into it... example ISessaoUsuario

    class Repository<T> : IRepository<T>
    {
        ISessaoUsuario sessaoUsuario;
        public Repository(ISessaoUsuario sessaoUsuario)
        {
            this.sessaoUsuario = sessaoUsuario;
        }
    
        // mais detalhes sobre os outros métodos abaixo
    }
    
  • in the concrete implementation of IRepository<T>, in all data retrieval methods, use a Visitator of Expression<T> with a view to changing the IQueryable<T> and automatically apply filters to properties that identify the tenant.

    I mean, turn it:

    db.MinhaEntidade.Where(e => e.Nome == "xpto")
    

    Automatically in that:

    db.MinhaEntidade.Where(e => e.Nome == "xpto"
        && e.InquilinoId == this.SessaoUsuario.InquilinoId)
    

    Manipulate objects Expression<T> is the most laborious part, but think about it, it’s practically generic for any ORM that supports LINQ... it’s very reusable, it’s worth it.

  • still in this implementation, in the rescue method, get all the entities that will be saved and check if the entity is being saved in the correct tenant. To do this, I suggest that entities implement an interface of the type IInquilinoEspecifico, which allows to easily obtain which is the tenant of the object being saved

     foreach (var e in entidadesModificadas.OfType<IInquilinoEspecifico>())
     {
         if (e.InquilinoId != this.SessaoUsuario.InquilinoId)
             throw new Exception("Você não pode salvar dados de um inquilino diferente do que o usuário logado");
     }
    
  • 1

    In this case, you can implement a IDbSet<T> or IObjectSet<T>, who receives IUsuarioSessao and would be injected into dependents. This implementation would be responsible for changing the Expression<T> of the interface IQueryable<T>, whenever it is about to be materialized, and also by checking objects being saved. It gives in the same, it is as if the IDbSet<T> were the IRepository<T>.

2

I will try to give an answer that is not opinionated.

This approach in trying to implement the standard would be a correct way?

Apparently its architecture uses a database only, and each query executed always takes into account a composite key scheme. There are some problems:

  • It is unclear how the session will be passed into the repository;
  • It is not clear how data persistence will be done;
  • Some things are still prolific, for example:

    public bool Edit(T entity)
    {
        var result = false;
        _context.Entry<T>(entity).State = EntityState.Modified;
        if (SaveChanges() > 0)
            result = true;
        return result;
    }
    

    And that could be simplified to:

    public bool Edit(T entity)
    {
        _context.Entry<T>(entity).State = EntityState.Modified;
        return SaveChanges() > 0;
    }
    

From the point of view of the Entity Framework, you are subproviding the Framework in favor of an unswerving approach, and adding a composite key complicator, which can be configured, but it raises the complexity of your application unnecessarily.

Incidentally, I already answered that to @Rod here: for organization, isolation and simplicity, the ideal is to separate per database, adding an additional database to register businesses and users.

If yes, how do I pass an instance of the Session User to the repository instance?

It is a doubt that I have, including. In your place, I would do in the repository builder:

public CustomRepository(int RevendaId, int EmpresaId, int ClienteId)
{
    _context = new TContext();
    _responsibleContext = true;
    this.RevendaId = RevendaId;
    this.EmpresaId = EmpresaId;
    this.ClienteId = ClienteId;
}

0

You could study the possibility of separating the banks by tenants. For a possible Restore it would be much quieter.

I adopted the following strategy for an application I developed. One of the options at the time of knowing which tenancy I had to charge for the client’s domain. In the customer control panel it should provide the contract (id), login and password for me to load the data from it.

In the constructor of my Dbcontext I would give a replace in XXX.

public class KFMAutoXXXContext : DbContext
{
public MyXXXContext(string database) : base(ConfigurationManager.ConnectionStrings["MyXXXContext"].ToString().Replace("XXX", database))
{
}
}

My string connection contained this XXX in the DB name.

In my project I had an administrative DB where I could set up new contracts, disable tenants, issue invoices, etc.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.