patterncsharpMinor
Expose an IDbSet<> object to overcome a limitation for LINQ's Select()
Viewed 0 times
exposelinqobjectidbsetforlimitationselectovercome
Problem
Before posting this, I have spent the past 4 days tirelessly investigating the Internet and the different Stack Exchange websites on how to correctly implement the Unit of Work pattern and Unit Test it using MVC 5 + EF 6.1. So far, the implementation I manage to make it work was this one.
Here's the actual problem:
I am lifting all the lookups of the Database to the Unit of Work (as I've understood). I have a repository called "TournamentRepository" which derives from a
What I want to emphasize is the
It is actually from the following:
```
public class GenericRepository: IGenericRepository where TEntity:class
{
private readonly IDbSet _iDbSet;
private readonly DbSet _dbSet;
//protected IDbSet Entities { get; set; }
public GenericRepository(IDbRepositories dbRepositories)
{
_iDbSet = dbRepositories.Set();
_dbSet = dbRepositories.SetDb();
}
public IDbSet Get()
{
return _iDbSet;
}
public async Task FindAsync(int id)
{
return await _iDbSet.FindAsync(id);
}
public async Task> GetAllAsync()
{
return await _iDbSet.ToListAsync();
}
public TEntity SingleOrDefault(Expression> predicate)
{
return _iDbSet.SingleOrDefault(predicate);
}
public void Add(TEntity entity)
{
_iDbSet.Add(entity);
}
public void AddRange(IEnumerable entit
Here's the actual problem:
I am lifting all the lookups of the Database to the Unit of Work (as I've understood). I have a repository called "TournamentRepository" which derives from a
GenericRepository with the generic methods. In the example above, it uses a neat trick on implementing statics as extensions so we don't need to create new Interfaces for each new Repository.public static async Task IsUserRegisteredInTournament(this IGenericRepository tournamentRepository, string tournamentUrl, string userId)
{
return await tournamentRepository.Get().Select(x => x.Participants.Where(y => y.UserId == userId))
.AnyAsync();
}What I want to emphasize is the
Get() method. I want to know if it is a leaky abstraction.It is actually from the following:
```
public class GenericRepository: IGenericRepository where TEntity:class
{
private readonly IDbSet _iDbSet;
private readonly DbSet _dbSet;
//protected IDbSet Entities { get; set; }
public GenericRepository(IDbRepositories dbRepositories)
{
_iDbSet = dbRepositories.Set();
_dbSet = dbRepositories.SetDb();
}
public IDbSet Get()
{
return _iDbSet;
}
public async Task FindAsync(int id)
{
return await _iDbSet.FindAsync(id);
}
public async Task> GetAllAsync()
{
return await _iDbSet.ToListAsync();
}
public TEntity SingleOrDefault(Expression> predicate)
{
return _iDbSet.SingleOrDefault(predicate);
}
public void Add(TEntity entity)
{
_iDbSet.Add(entity);
}
public void AddRange(IEnumerable entit
Solution
Leaky as an old roof :)
An abstraction is leaky if you need to know details about the concrete implementation in order to work with it properly. Leakiness of an interface may reveal itself in various ways, for example: -
-
if it's very hard (or impossible) to implement the interface with another technology stack than the one for which it was designed. Or
-
if it perfectly exposes major features of one implementation, but would make powerful features of other implementations inaccessible. Or
-
if it enforces a work flow that perfectly fits one implementation but forces other implementations to bend their own preferred work flow. Or
-
If it doesn't tell the whole story, if an implementation has added core features.
Let's look at what you encounter if you want to implement
-
You may succeed in implementing the interface methods in a way that they display the same behavior as
-
If forces you to use LINQ-to-NHibernate. This in itself inevitably introduces differences. Both types of LINQ have their own set of supported methods. Both have their own bugs, or run-time issues (like generated queries that perform poorly).
NHibernate has other powerful query APIs that you can't benefit from, at least not fully.
-
NHibernate's workflow resembles that of EF. But there are important differences. For example NHibernate's auto flush (auto commit) feature.
-
The standard implementation if
Extension methods
So far, the challenges only just applied to implementing the interface specification itself. The real bummer are extension methods.
If you've got an
But let's look at the source code of just an arbitrary method,
As you see, it expects a
Now what?
Does this mean you should revert to the original plan and jump through hoops to return
An abstraction is leaky if you need to know details about the concrete implementation in order to work with it properly. Leakiness of an interface may reveal itself in various ways, for example: -
-
if it's very hard (or impossible) to implement the interface with another technology stack than the one for which it was designed. Or
-
if it perfectly exposes major features of one implementation, but would make powerful features of other implementations inaccessible. Or
-
if it enforces a work flow that perfectly fits one implementation but forces other implementations to bend their own preferred work flow. Or
-
If it doesn't tell the whole story, if an implementation has added core features.
IDbSet is so leaky, I wouldn't even call it an abstraction.Let's look at what you encounter if you want to implement
IDbSet for, say, NHibernate.-
You may succeed in implementing the interface methods in a way that they display the same behavior as
DbSet. This is hard enough. Find is far from trivial. Add affects an entire object graph. In fact, all methods affecting an entity's tracking state have nitty-gritty details when it comes to the adhered object graph. If you don't respect these details, saving will behave differently.-
If forces you to use LINQ-to-NHibernate. This in itself inevitably introduces differences. Both types of LINQ have their own set of supported methods. Both have their own bugs, or run-time issues (like generated queries that perform poorly).
NHibernate has other powerful query APIs that you can't benefit from, at least not fully.
-
NHibernate's workflow resembles that of EF. But there are important differences. For example NHibernate's auto flush (auto commit) feature.
-
The standard implementation if
IDbSet, DbSet, has other important methods that are not part of the interface. For example, AsNoTracking. An NHibernate implementation should also implement these to be even remotely interchangeable (which it never will be).Extension methods
So far, the challenges only just applied to implementing the interface specification itself. The real bummer are extension methods.
If you've got an
IDbSet, it seems reasonable to expect that you can apply extension methods from DbSetMigrationsExtensions or the vast QueryableExtensions class. After all, DbSetMigrationsExtensions extend IDbSet and QueryableExtensions is in the same namespace.But let's look at the source code of just an arbitrary method,
QueryableExtensions.AnyAsyncpublic static Task AnyAsync(this IQueryable source, CancellationToken cancellationToken)
{
Check.NotNull(source, "source");
var provider = source.Provider as IDbAsyncQueryProvider;
if (provider != null)
{
return provider.ExecuteAsync(
Expression.Call(
null,
_any.MakeGenericMethod(typeof(TSource)),
new[] { source.Expression }
),
cancellationToken);
}
else
{
throw Error.IQueryable_Provider_Not_Async();
}
}As you see, it expects a
Provider that implements IDbAsyncQueryProvider;. This is an interface in the System.Data.Entity.Infrastructure namespace. So you run from one implementation challenge into the other. I don't think there will be any provider implementing that interface, other than those created for Entity Framework. And it can be mocked for the purpose of unit tests in an EF environment, as in the link you shared.Now what?
Does this mean you should revert to the original plan and jump through hoops to return
IQueryable correctly? I don't think so. For one, IQueryable is leaky as well. But I may have answered your question in a stricter sense than you expected. Maybe you never intended to craft a completely different implementation. Maybe you only contemplated the implications of exposing IDbSet in a unit test setting. If so, IDbSet, or even DbSet is good enough.Code Snippets
public static Task<bool> AnyAsync<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken)
{
Check.NotNull(source, "source");
var provider = source.Provider as IDbAsyncQueryProvider;
if (provider != null)
{
return provider.ExecuteAsync<bool>(
Expression.Call(
null,
_any.MakeGenericMethod(typeof(TSource)),
new[] { source.Expression }
),
cancellationToken);
}
else
{
throw Error.IQueryable_Provider_Not_Async();
}
}Context
StackExchange Code Review Q#126190, answer score: 5
Revisions (0)
No revisions yet.