How to implement an Objectset wrapper that works with Linqtoentities?

Asked

Viewed 298 times

7

I need to create a wrapper for ObjectSet to create a centralized access control.

The goal is to implement CA without making changes to existing queries in the system, which in this case are spread throughout the code (there is no centralized layer for data access).

The connection to the bank uses a ObjectContext.

The wrapper of ObjectSet created, is this:

public class ObjectSetWrapper<TEntity> : IQueryable<TEntity> where TEntity : EntityObject
{
    private IQueryable<TEntity> QueryableModel;
    private ObjectSet<TEntity> ObjectSet;

    public ObjectSetWrapper(ObjectSet<TEntity> objectSetModels)
    {
        this.QueryableModel = objectSetModels;
        this.ObjectSet = objectSetModels;
    }

    public ObjectQuery<TEntity> Include(string path)
    {
        return this.ObjectSet.Include(path);
    }

    public void DeleteObject(TEntity @object)
    {
        this.ObjectSet.DeleteObject(@object);
    }

    public void AddObject(TEntity @object)
    {
        this.ObjectSet.AddObject(@object);
    }

    public IEnumerator<TEntity> GetEnumerator()
    {
        return QueryableModel.GetEnumerator();
    }

    public Type ElementType
    {
        get { return typeof(TEntity); }
    }

    public System.Linq.Expressions.Expression Expression
    {
        get { return this.QueryableModel.Expression; }
    }

    public IQueryProvider Provider
    {
        get { return this.QueryableModel.Provider; }
    }

    public void Attach(TEntity entity)
    {
        this.ObjectSet.Attach(entity);
    }

    public void Detach(TEntity entity)
    {
        this.ObjectSet.Detach(entity);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.QueryableModel.GetEnumerator();
    }
}

He’s not the type ObjectSet, but this is not important, it only needs to be used in queries LinqToEntities. And it even works for simple queries, like this one:

//db.Produto é do tipo ObjectSetWrapper<Produto >
var query = (from item in db.Produto where item.Quantidade > 0 select new { item.Id, item.Nome, item.Valor });
var itensList = query.Take(10).ToList();

But when there is a * subquery*, like this:

//db.Produto é do tipo ObjectSetWrapper<Produto>
var query = (from item in db.Produto
             select new
             {
                 Id = item.Id,
                 Nome = item.Nome,
                 QuantidadeVendas = (from venda in db.Venda where venda.IdProduto == item.Id select venda.Id).Count()
             }).OrderByDescending(x => x.QuantidadeVendas);

var productsList = query.Take(10).ToList();

I get a NotSupportedException, stating that it is not possible to create a constant of the type of my subquery (in the case, Venda):

Unable to create a Constant value of type 'Mynamespace.Model.Venda'. Only Primitive types or enumeration types are supported in this context.

How I make this query work? I don’t need you to wrapper be the type ObjectSet, only that it can be used in queries, keeping the queries of the system working.


Updated

Update the Objectsetwrapper class to implement IObjectSet<TEntity> as indicated by Miguel Angelo, but the errors continue. Now the class presents this signature:

public class ObjectSetWrapper<TEntity> : IObjectSet<TEntity>, IQueryable<TEntity> where TEntity : EntityObject

To reinforce, the idea of wrapper is to be able to perform access control checks on queries of query, therefore it is important to keep queries with existing entities in the system working.

  • Extension Methods were designed exactly for this type of problem, where it is not possible or desirable to change the base classes. I believe this would be a better solution to your problem rather than creating a wrapper class.

  • Apparently you have a Iqueryable in return sales, you wouldn’t even need to materialize the query in objects before. A kick, try to remove the .OrderByDescending(x => x.QuantidadeVendas) and use only after materializing the list, who knows your problem is only there.

  • I just tried it here, it didn’t work either, it’s still the same mistake.

  • You managed to solve the problem?

  • Yes, they answered me in the "original" OS, but later I put the answer here.

4 answers

3

Possibly you will need to implement the interface IObjectSet<TEntity> so that the Entity-framework can know how to work with this object, otherwise Entity will interpret this object as being anything that has nothing to do with it.

EDIT

I’ll point out a path that will surely work:

The method Expression IQueryable.Expression returns the expression that will be transformed into SQL by the Entity. Possession of that object, which will be obtained from ObjectSet original, you must reconstruct it so that references to the ObjectSetWrapper are replaced by references from ObjectSet internal, in addition to adding a call to the method Where to filter according to access control.

Rebuild the Expression is something that will take a lot of work, because it is an immutable AST (abstract syntactic tree), that is, you will have to rebuild the whole tree. To do this, you can implement a ExpressionVisitor which will convert the original expression:

public class ControleAcessoVisitor : ExpressionVisitor
{
    protected override Expression VisitConstant(ConstantExpression node)
    {
        Expression result = node;

        if (node.Value is ObjectSetWrapper)
        {
            result = Expression.Constant((node.Value as ObjectSetWrapper).inner);

            var whereMethod = typeof(Queryable).GetMethods().Single(m =>
                m.Name == "Where"
                && m.GetParameters().Length == 2
                && m.GetParameters()[1].ParameterType
                    .GetGenericArguments()[1]
                    .GetGenericArguments().Length == 2);

            Expression filtroControleAcesso = PegarExpressionFiltroCA();

            result = Expression.Call(whereMethod, result, filtroControleAcesso);
        }

        return base.Visit(result);
    }
}

I will leave the implementation of the method PegarExpressionFiltroCA for you, this will depend on your specific rules.

And in the method Expression IQueryable.Expression of your Wraper:

    Expression IQueryable.Expression
    {
        get { return new ControleAcessoVisitor().Visit(this.query.Expression); }
    }

I hope it’s worth implementing this... is a way to ensure that access control will work, but it’s very complicated.

  • I made my Objectsetwrapper also implement Iobjectset<Tentity>, but still the same error still occurs in queries with subquery. I need to change something in existing methods?

  • 1

    I’m going to do a test project to replicate the problem, and see if I can do something.

  • 1

    @Miguelangelo, it doesn’t work dude. It makes no difference whether to implement Iobjectset<T> or not.

  • @Andrépena I have no way to test now... that was a kick that made sense.

  • I’m sure in a way that works... but it’s going to take an absurd job to do: rebuild the whole Expression, before it is evaluated by Entity and put the object that Entity waits for inside Expression Tree, and at the same time add an Expression Where in the subquery to do the access control.

1

Extension Methods were designed exactly for this type of problem, where it is not possible or desirable to change the base classes. I believe this would be a better solution to your problem rather than creating a wrapper class. Follow an example:

public static class ObjectSetExtensions
{
    public static ObjectQuery<TEntity> Include(this IQueryable<TEntity> set, string path) where TEntity : EntityObject
    {
        return set.Include(path);
    }

    public void DeleteObject(this IQueryable<TEntity> set, TEntity @object) where TEntity : EntityObject
    {
        set.DeleteObject(@object);
    }

    // etc

}

Thus any object of the type IQueryable<TEntity> "wins" new ways. Ex: meuSet.Include("path") and meuSet.DeleteObject(objeto). And the original classes continue to be properly recognized by the RU.

  • In my case, the main goal is to carry out queries with Linqtoentities on my wrapper. I believe that this is not possible with Extension Methods, right?

  • Simply continue using the base classes for the queries. There is in this case the need for the wrapper soh to include some new methods. Extension Methods give you the ability to extend the class without the need to modify it or create a wrapper.

  • By the way, Linq methods as OrderByDescending are implemented as Extension Methods.

  • 1

    Still I don’t see how Extension Methods can help me. My scenario is a system with more than 1500 queries with LinqToEntities. To avoid the refactor of all these queries there was the idea of creating the wrapper. At the moment all queries go through my wrapper class, so I can have the access control implementation in one place. Since an Extension Method cannot function as an override for a method, I don’t see how they can help me without having to change all these existing queries.

0


  • This solution is very similar to the one I suggested, with the difference of the Queryprovider.

0

I get the impression that your subquery is not being deferred, and therefore it tries to run along with the main query, which could be causing the error.

Try to change your code this way:

//db.Produto é do tipo ObjectSetWrapper<Produto>
var query = (from item in db.Produto
             select new
             {
                 Id = item.Id,
                 Nome = item.Nome,
                 QuantidadeVendas = db.Venda.Where(m => m.IdProduto == item.Id).ToList().Count() //ToList() para forçar a execução da subquery
             }).OrderByDescending(x => x.QuantidadeVendas);

var productsList = query.Take(10).ToList();
  • Thanks for the answer. But I’m looking for a solution that doesn’t require changing the queries already made in the code. Do you know any other way?

  • When everything is materialized, both db. Product, when db.Sale, then everything works. But materializing all table records is not a viable option, especially in the case of a subquery, for each db record.Product, a query will be performed in the database. That is, in this way it does not generate Exception, but to materialize everything is not an option. (from item in db.Produto.Tolist() select new { Quantityevendas = db.Venda.Where(m => m.Idproduct == item.Id). Tolist(). Count() }). Orderbydescending(x => x.Quantityevendas);

Browser other questions tagged

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