Generic Find Method on Repository using Mongo - c#

I am trying to make a generic repository with a find operation that takes an expression. I have the following right now (I am using FastMapper for projecting from my entity objects to an external contract object):
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Project().To<T>().Where(predicate).ToList();
}
The problem is I get the following exception: "Where with predicate after a projection is not supported."
I can do the following, but this would severely poor performance wise because it would involve retrieving every record from the database before performing the filtering:
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Project().To<T>().ToList().AsQueryable().Where(predicate).ToList();
}
I am wondering if there is a way to convert the expression from a T to a Y object so that I could do the following instead (which I think would be the most performant because it then passes the filtering down to the database and only performs Project on the resultset):
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Where(predicate).Project().To<T>().ToList();
}
any help would be greatly appreciated. Thanks.
Update
So, using the information from this question (Question) I was able to get closer to what I was looking for. I am now able to do the following:
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var newPredicate = TransformPredicateLambda<T, Y>(predicate);
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Where(newPredicate).Project().To<T>().ToList();
}
The only thing I have left to resolve is retrieving the fastmapper mappings at this point (if property is null part):
protected override Expression VisitMember(MemberExpression node)
{
var dataContractType = node.Member.ReflectedType;
var activeRecordType = _typeConverter(dataContractType);
var property = activeRecordType.GetProperty(node.Member.Name);
if (property == null)
{
}
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
property
);
return converted;
}
essentially, some of my objects might look like this:
//object used throughout my code
public class Store
{
public string StoreId {get; set;}
public Account Account {get; set;}
...
}
//object used in access layer only
public class Store
{
public string StoreId {get; set;}
public string AccountId {get; set;}
...
}
And in my initialization script, I define a type adapter like so:
TypeAdapterConfig<Store, Models.Store>.NewConfig()
.MapFrom(a => a.Id, s => s.StoreId != null ? new ObjectId(s.StoreId) : new ObjectId())
.MapFrom(d => d.AccountId, s => s.Account.AccountId)
.IgnoreNullValues(true);

The reason you can't do a projection from Y to T prior to handing the query to MongoDB is that MongoDB doesn't know anything about T. We only know about Y (because that is the type of the collection). For instance:
class Person
{
[BsonElement("fn")]
public string FirstName { get; set; }
[BsonElement("ln")]
public string LastName { get; set; }
}
class PersonView
{
public string FullName { get; set; }
}
And your projector does something like this:
person => new PersonView { FullName = person.FirstName + person.LastName }
We don't have access to the code in the projector and have no idea that FullName is the concatenation of FirstName and LastName and therefore can't tell MongoDB to do this.
The LINQ support in the 1.x version of the driver cannot target the Aggregation Framework, which is the only place this would be legal, and only assuming your projector produced an Expression<Func<Person, PersonView>> and not a compiled Func<Person, PersonView>. However, the 2.x version of the driver will have better support for this, although it really depends on what FastMapper does underneath.
======
So, your current options are this:
Change the method signature to be Expression<Func<Y, bool>>. This would not only satisfy your needs, but also limit the documents you are projecting to only those that pass the filter.
If T inherits from Y, you could start with an OfType and not even need the projector: collection.AsQueryable<Y>().OfType<T>.Where(predicate).ToList();

Related

Is there a way to map Func<T1, bool> to Func<T2, bool>?

So, I was wondering if it possible to do the next thing in c#:
I have a DB model - let's say it is Car:
public class Car {
public string Id {get;set;}
public string Name {get;set}
}
And a DbSet for this type in someDbContext:
public DbSet<Car> Cars {get;set;}
And also I have a CarDto
public class CarDto {
public string Id {get;set;}
public string Name {get;set}
}
And as result we get something like this:
var select = new Func<CarDto, bool>(car => car.Name == "BMW");
// And somehow use this expression for other type Car
someDbContext.Cars.Where(select);
Maybe there is an approach in which I could map these Funcs like this:
var newFunc = mapper.Map<Func<Car, bool>>(select);
Any thoughts?
If you just want to handle rewriting property accesses, you can use an ExpressionVisitor which looks a bit like this:
public class Program
{
public static void Main()
{
Expression<Func<Car, bool>> expr = x => x.Name == "BMW";
var replaced = ReplaceParameter<CarDto>(expr);
}
private static Expression<Func<T, bool>> ReplaceParameter<T>(LambdaExpression expr)
{
if (expr.Parameters.Count != 1)
throw new ArgumentException("Expected 1 parameter", nameof(expr));
var newParameter = Expression.Parameter(typeof(T), expr.Parameters[0].Name);
var visitor = new ParameterReplaceVisitor()
{
Target = expr.Parameters[0],
Replacement = newParameter,
};
var rewrittenBody = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(rewrittenBody, newParameter);
}
}
public class ParameterReplaceVisitor : ExpressionVisitor
{
public ParameterExpression Target { get; set; }
public ParameterExpression Replacement { get; set; }
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == this.Target)
{
// Try and find a property with the same name on the target type
var members = this.Replacement.Type.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Public | BindingFlags.Instance);
if (members.Length != 1)
{
throw new ArgumentException($"Unable to find a single member {node.Member.Name} of type {node.Member.MemberType} on {this.Target.Type}");
}
return Expression.MakeMemberAccess(this.Replacement, members[0]);
}
return base.VisitMember(node);
}
}
We need to deconstruct the LambdaExpression into its body and parameters. We need to create a new parameter which has the correct type, and replace all usages of the old parameter with the new one. This is where the visitor comes in: whenever it sees you access a member on the old parameter, it tries to find the corresponding member on the new parameter, and access that instead.
We then construct a new LambdaExpression, using the rewritten body and the new parameter.
You have a whole bunch of options:
Derive your Dto class from the context class. That way you can use polymorphism as normal.
Extract an interface and implement it in both your Dto and context classes. Same as above then, use polymorphism.
Use duck-typing. In C#, that's done with the dynamic keyword. You lose Intellisense and compile-time error checking, but your code will work.
Reflection. It's a lot of code, it's slow, it's practically a much worse version of #3, but you can cobble it together if you really try.
Something like Automapper will help you map your context to your Dto piece-wise, but it won't help you translate your lambda function filters.

A generic entity comparison expression builder

As stated here EntityFramework (at least in version 6) does not support user types comparison (and thus, entities comparison) which is not practical for an ORM.
I have a scenario where I have a lot of code that does entities comparison that should be translated to SQL.
All my entities have an Id field of type int.
class Entity
{
public int Id { get; set; }
}
For queries where I want to compare entities in a Where clause, I would like to be able the compiler to detect if trying to perform an invalid comparison.
Considere the following classes :
class SomeEntity : Entity
{
public RelationEntity_Id int{ get; set; }
public RelationEntity Relation { get; set; }
}
class RelationEntity : Entity
{
}
With the following syntax there is such a check on the types:
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().Where(e => s.Relation == relation);
}
With the following one, we are only comparing int and this could be error prone :
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().Where(e => s.Relation.Id == relation.Id);
}
Because EntityFramework needs to be told how the comparison on objects should be done, I'm looking for a way create a generic Expression returning an expression Comparing the Id of a SomeEntity with the Id of a RelationEntity.
Something like this :
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().Where(e => AreEquals(s.Relation, relation));
}
AreEquals would be adapting the Expression Tree for the SQL generated correctly comparing on entity Ids.
I have found this post which seems to be a good start, but I can't get it to append the '.Id' part of the Expression.
Any idea of how I could achieve this, or what could be the good way to go ?
Here you go.
The helper function (inside some static class):
public static IQueryable<T> WhereEquals<T>(this IQueryable<T> source, T target)
where T : Entity
{
var item = Expression.Parameter(typeof(T), "item");
var body = Expression.Equal(
Expression.Property(item, "Id"),
Expression.Property(Expression.Constant(target), "Id"));
var predicate = Expression.Lambda<Func<T, bool>>(body, item);
return source.Where(predicate);
}
and the usage:
public IQueryable<SomeEntity> SearchByRelation(RelationEntity relation)
{
return CreateQueryable().WhereEquals(relation);
}

Using Expressions to combine Funcs with different signature

I am using the following class to wrap some DocumentDB access which allows me to store multiple entities in the same collection:
public class TypedEntity<T> {
public string Type { get; set; }
public T Item { get; set; }
public TypedEntity(T item) {
Id = Guid.NewGuid().ToString();
Item = item;
Item.Id = Id;
Type = typeof (T).FullName;
}
}
The usage of this class is encapsulated inside a repository class. I'm trying to build the API of the repository class such that the consumer doesn't need to know about the usage of TypedEntity<T> and can instead treat it as a source for just <T>. For example, the repository has a method with this signature:
public async Task<IQueryable<T>> WhereAsync(Func<T, bool> predicate)
In order to actually retrieve this data, the predicate needs to be combined/converted with one that interacts with TypedEntity<T>. This is the pseudo code that I'm picturing in my head for what I'd ultimately like to achieve:
public async Task<IQueryable<T>> WhereAsync(Func<T, bool> predicate) {
// remembering that dataSource is talking to a backing store of TypedEntity<T>
var queryable = dataSource.Where(x => x.Type == typeof(T).FullName && predicate(x.Item));
// ... other business logic stuff
}
This actually builds but ultimately results in an Expression that uses .Invoke around the passed in predicate (which DocumentDb is unable to understand). Is there some way that I can combine the type part with the passed in Func to build up the Expression manually?
You'll want to take in an Expression<> rather than just a Func<>. Then, it should be fairly easy to apply it in a Where() clause after you've performed a Select() projection:
public async Task<IQueryable<T>> WhereAsync(Expression<Func<T, bool>> predicate) {
// remembering that dataSource is talking to a backing store of TypedEntity<T>
var typeName = typeof(T).FullName;
var queryable = dataSource.Where(x => x.Type == typeName)
.Select(x => x.Item)
.Where(predicate);
// ... other business logic stuff
}

How to convert between predicate types in EF

I want to convert Expression<Func<DTOItem, bool>> predicate to Expression<Func<Item, bool>> predicate with entity framework where DTOItem is mapped class of entity and Item(a table in database) is entityframe work class.
IDTOItemRepository itemRepo = new DTOItemRepository();
DTOItem itemObject = itemRepo.Single(q => q.ItemID == 1 && q.ItemType == 1);
where q => q.ItemID == 1 && q.ItemType == 1 is of type Expression<Func<DTOItem, bool>>.
Now i need to convert this expression to Expression<Func<Item, bool>>
You can't. You can do the opposite easily enough.
Let's take a look at another example.
I promise that if you give me an apple, I can tell you if it's still fresh. I know everything there is to know about every single kind of apple.
How can you use my knowledge to determine if any given piece of fruit is still fresh? You can't. I know about apples, but I don't know about other types of fruit. I can't make the guarantee that if you give me any kind of fruit I'll know if its fresh or not.
Now what if you were asked to determine if any type of Red Delicious apple is fresh? Can you use the information I can give you to provide an answer to that question in all cases? Sure you can. I can handle any kind of apple, limiting your questions to just one kind is no problem for me.
You have a function that knows how to take any DTOItem and return a boolean. You can't use that to take an Item at all and return a boolean, because what if that item isn't a DTOItem? Then what will you do? But if you have a method that can take any item at all and return a boolean then by all means you can "pretend" that it only accepts DTOItems. It can handle any item, limiting it's input to just DTOItems isn't a problem.
Use AutoMapper and it's Queryable Extensions portion, it will do exactly that for you (but not in a direct way). What it does is let you turn a IQueryable<Item> to a IQueyable<DtoItem> so you could use your Expression<Func<DTOItem, bool>> on it, then when you go to perform the query aginst the backing store AutoMapper will transform the Expression<Func<DTOItem, bool>> portion in to a Expression<Func<Item, bool>> portion to be passed on to the EF provider.
public static void Main(string[] args)
{
//This needs to be done once when your program starts up.
//You may need to do a more complicated "CreateMap" depending on how Item and DTOItem relate.
Mapper.CreateMap<Item, DTOItem>();
RunMyProgram();
}
public DTOItem GetItem()
{
using (var context = new MyDatabaseContext())
{
IQueryable<Item> itemQuery = context.Items;
return itemQuery.Project().To<DTOItem>()
.Single(q => q.ItemID == 1 && q.ItemType == 1);
}
//the above translates to the equivalent code
/*
return itemQuery.Where(q => q.itemid == 1 && q.itemtype == 1)
.Select(a => new ItemDTO {ItemID = a.itemid, ItemType = a.itemType, SomeType = a.sometype} )
.Single();
*/
}
public class DTOItem
{
public int ItemID { get; set; }
public int ItemType { get; set; }
public String SomeType {get; set;}
}
public class Item
{
public int itemid { get; set; }
public string itemtype { get; set; }
public String sometype {get; set;}
}
public class MyDatabaseContext: DbContext
{
public MyDatabaseContext(): base()
{
}
public DbSet<Item> Items{ get; set; }
}

LINQ Expression help with Func TEntity,TType

I have a repository method that accepts an order by parameter in the form:
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity,string>> orderBy)
Now that works fine when trying to sort by a property of type string,
var entities = rep.Get(x => x.Name);
but what if i want to sort by double or int or any other type.
Doing something like var entities = rep.Get(x => x.Price); obviously throws a compile error saying I can't convert double to string.
How can I make this more generic so I can sort by any property in my entity, or at least the properties where the type implements IComparable or something similar?
The repository class itself should have a type parameter. Then you don't need to specify the entity type when accessing the repository members. Like this:
public interface IRep<TEntity>
{
IEnumerable<TEntity> Get<TOrderBy>(
Expression<Func<TEntity, TOrderBy>> orderBy
);
}
An example entity class:
public class MyEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
}
An example repository implementation:
public class Rep<TEntity> : IRep<TEntity> { ... }
Now you can just consume it like this:
var a = new Rep<MyEntity>();
var b = a.Get(x => x.Name); // string
var c = a.Get(x => x.Price); // decimal
This is how Linq does it. :)
public IEnumerable<TEntity> Get<TEntity, TOrderBy>(Expression<Func<TEntity,TOrderBy>> orderBy)

Categories

Resources