0

I'll need to expose a bit of background before going further: I have a project that uses nHibernate and some generic repository to forward predicates and return entities as follows:

public abstract class GenericRepository< T >
{
...
    public virtual T Single( Expression< Func< T, bool > > predicates )
    {
        // Get object from nHibernate session object
        var retObj = Session
            .Query< T >()
            .Where( predicates ).SingleOrDefault();

        return retObj;
    }
...
}

I can then for example get an entity this way:

var entity = Context.MyGenericEntityRepository.Single( e => e.Id == id );
// or
var entity = Context.MyGenericEntityRepository.Single( e => e.Name == name );

But, I also have some entities that because of the nature of the project aren't saved to the database but to the file system (bunch of files). So I use a derived repository that uses some sort of DataAccess class to get the entities from the file system as follows:

public class NotGenericRepository
{
...
    // for example
    public IList<Entity> All()
    {
        return _entityDataAccess.All();
    }
...
}

As said, the 2nd type of entity isn't stored in the database, however, to facilitate my journey I created a sort of in-memory database system using DataSets and DataTables. So when the solution is first started, I have a singleton called CustomDatabase that gets initialised, creates the DataTables in memory, adds relations between the DataTalbes, and adds them to a general DataSet before scanning the file system to populate the tables.

Using this, I can now query my DataTables instead of scanning through the file system tree every time. I then set up some events in my CustomDatabase so whenever a row is added/deleted/updated, the changes are reflected onto the file system.

So... This is it for the background, sorry for the length...

My question is now fairly simple, I'm looking for a way to somehow translate the lambda expression forwarded by the repository to my DataAccess class, so I can then analyse it and select from my DataTables accordingly...

For example, a call to the repository such as:

var entity = Context.MyNotGenericEntityRepository.Single( e => e.Id == id );
// or
var entity = Context.MyNotGenericEntityRepository.Single( e => e.Name == name );

Should be translated within the DataAccess as:

DataRow entityRow = CustomDatabase.Tables[ "EntityName" ].AsEnumerable().Where( e => e.Field< Guid >( "Id" ) == id);
// or
DataRow entityRow = CustomDatabase.Tables[ "EntityName" ].AsEnumerable().Where( e => e.Field< string >( "Name" ) == name);

Or:

var entity = from myRow in CustomDatabase.Tables[ "EntityName" ].AsEnumerable()
             where myRow.Field<Guid>( "Id" ) == id
             select myRow;
// or
var entity = from myRow in CustomDatabase.Tables[ "EntityName" ].AsEnumerable()
             where myRow.Field<string>( "Name" ) == name
             select myRow;

I have absolutely no idea how to do this, I've been looking all over the net, but the problem is that I don't really know how to name this problem so I haven't found much so far... :(

Any help is appreciated since I'm expecting there'll be several ways to tackle this problem :)

Thanks!!

Seb
  • 778
  • 1
  • 9
  • 27
  • Just for my curiosity: why the in-memory repository is based on DataSets instead of on in-memory collections? If I understand correctly, the core of your issue is the incompatibility between signatures returned by your repositories. And a repository which uses collections (vs DataSets) could implement the same exact interface as the NH repository. – Wiktor Zychla Jun 24 '12 at 13:10
  • How about using reflection to get the class name and then use a white list to route where requests should go? The better way would be to apply attributes to your entities and then use reflection in your generic method above `T Single` to figure out which data access should be used. – Candide Jun 24 '12 at 13:18
  • If your `Single` method takes an `Expression>` as a parameter, then that's what's called an [expression tree](http://msdn.microsoft.com/en-us/library/bb397951.aspx). It should be possible to _parse_ the expression tree to get the information you want. – Jeppe Stig Nielsen Jun 24 '12 at 13:49
  • Wiktor you're right, but I just started that way originally because I wanted to use all the DataTables events, so that whenever a row is created, modified, deleted, some action will be undertaken on the filesystem to reflect the changes. Also, since I have 3 DataTables in the DataSet, I was able to establish relations, so that when a parent row is deleted, the deletion is cascaded to the child rows in the other 2 tables – Seb Jul 04 '12 at 12:54
  • Jeppe, that's exactly what I want to do, parse the expression tree, I just don't know how :( But thanks, I didn't know it was called that, I'll do some research on those exact terms – Seb Jul 04 '12 at 12:56

2 Answers2

0

It's difficult to tell what's been skipped in the derived repository class example, but it could be as simple as overriding (or overloading) Single to return All().Single(..).

kram
  • 31
  • 3
0

Assuming your predicate expressions are of the form in your question, you can convert the expression to one for a DataRow, and then use that to filter the table corresponding to the entity:

public static DataRow GetSingleRow<T>(Expression<Func<T, bool>> expr)
{
    var bodyExpr = ((LambdaExpression)expr).Body;
    var binExpr = (BinaryExpression)bodyExpr;
    var propExpr = (MemberExpression)binExpr.Left;
    PropertyInfo targetProperty = (PropertyInfo)propExpr.Member;
    Type targetType = targetProperty.DeclaringType;
    var valueExpr = binExpr.Right;
    string entityName = targetType.Name;

    var fieldMethod = typeof(DataRowExtensions).GetMethod("Field", new[] { typeof(DataRow), typeof(string) });
    var methodInfo = fieldMethod.MakeGenericMethod(targetProperty.PropertyType);
    var propNameExpr = Expression.Constant(targetProperty.Name);
    var rowParamExpr = Expression.Parameter(typeof(DataRow), "dr");
    var fieldInvocationExpr = Expression.Call(methodInfo, rowParamExpr, propNameExpr);

    var eqExpr = Expression.Equal(fieldInvocationExpr, valueExpr);
    Func<DataRow, bool> predicate = Expression.Lambda<Func<DataRow, bool>>(eqExpr, rowParamExpr).Compile();

    return CustomDatabase.Tables[entityName].AsEnumerable().Single(predicate);
}
Lee
  • 142,018
  • 20
  • 234
  • 287