Linq Expressions: Create a max query using a generic DbSet - c#

In order to simplify creating fake data for unit testing, I want to have a function that can create a generic Entity (which I use as an underlying object for a lot of other classes). The entity has an index, and, for generating a new index, I want to simply find the currently highest number and add one.
I am using Index for a lot of other classes, and would like to make this function as generic as possible. My problem is that I don't know how to specify what DbSet to use in my generic function GetMaxID.
This is what I got so far:
private Entity CreateGenericEntity()
{
return new Entity()
{
Guid = Guid.NewGuid(),
Index = GetMaxID<Entity>(x => x.Index) + 1,
};
}
private int GetMaxID<TEntity>(Expression<Func<TEntity, int>> expression)
{
return _repository.Set<TEntity>().Max(expression);
}
_repository has a bunch of different IDbSets properties, such as
public IDbSet<Customers> Customers{ get; set; }
public IDbSet<Orders> Orders{ get; set; }
etc.

I found that I was missing the declaration of TEntity, which was fixed by appending where TEntity : class to my function. I also had to change the expression to accept int? in order to handle the case where the query returns a null value. The complete function looks like this (if anyone is interested)
private int GetMaxID<TEntity>(Expression<Func<TEntity, int?>> expression) where TEntity : class
{
return _repository.Set<TEntity>().Max(expression) ?? 0;
}

Related

How to use Anonymous Type query in c# and ef core [duplicate]

I have the following generic method that I need to be able to perform a LINQ Where query in:
public static List<T> GetItems<T>(Guid parentId = new Guid()) where T : new()
{
var db = new SQLiteConnection(_dbPath);
List<T> result;
if (parentId != Guid.Empty)
{
result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
}
else
{
result = db.Table<T>().ToList();
}
db.Close();
return result;
}
The compiler doesn't like the following line
result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
error: cannot resolve 'ParentId'
Is it possible to use generics in this way in a LINQ query? Note that object of type T will always have a ParentId property.
You should concretize T parameter with some interface which will include required values. Also, you should add this interface to all types that contains this field or base type for its classes.
public interface IHierarchy
{
public Guid ParentId { get; }
}
public static List<T> GetItems<T>(Guid parentId = new Guid())
where T : IHierarchy, new()
{
var db = new SQLiteConnection(_dbPath);
List<T> result;
if (parentId != Guid.Empty)
{
result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
}
else
{
result = db.Table<T>().ToList();
}
db.Close();
return result;
}
If you have 2 types of entities and the first contains required values and the second does not, then you can have two overloads for this scenario.
You used a generic type and the compiler don't know which entity are you going to use.
Just use reflection feature of .NET language.
result = db.Table<T>().Where(i => i.GetType().GetProperty("ParentId").GetValue(src, null).Equals(parentId)).ToList();
The problem is that in your code you assume that every T has a GUID property ParentId, while in fact you only required that every T has a default constructor. You need to require that every T has a ParentId.
You could do this by requiring that every T implements some interface. Like other answers suggest, however this is quite a nuisance, because for every class that you want to use this function for you'll need to implement this interface.
The function Enumerable.Where seems to be able to do the same job, without requiring any interface from the input items. So let's use the same method:
As input we tell which property to use (in your case ParentId) and with which value to compare (in your case parentId).
The only requirement we have is that we must be able to compare ParentId with parentId: it should be IEquatable
public List<T> GetItems<T, TKey>(Func<T, TKey> keySelector, Tkey value)
TKey : IEquatable<TKey>,
{
...
result = db.Table<T>()
.Where(t => keySelector(t).Equals(value))
.ToList();
}
Usage:
Guid value = ...
var items = GetItems<MyClass, Guid>(item => item.ParentId, value);
This function will also work with other classes and other properties:
int studentId = ...
var students = GetItems<Student, int>(student => student.Id, studentId);
var otherStudents = GetItems<Student, string>(student => student.Name, "John");
Two side remarks:
- you use new Guid() to define some default Guid. It is faster to use Guid.Empty
- You will not create new items of type T. They are already in your dbContext.Table. Therefore you don't need new().
- However, if your Table requires that T is a class then you should require that. See your Table definition:
where T : class

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 the logic from one lambda within a second lambda

I've looked at the other questions around this and I just can't work out how to apply the answers to my particular situation. Say you have a couple of models that look like this:
public class Person
{
public int PersonId { get; set; }
}
public class Business
{
public int BusinessId { get; set; }
}
I want to be able to write a couple of different generic methods: one that gets the models using a provided Lambda that might look something like this:
GetWhere(p => p.PersonId == 1)
And one to get the models using a unique key - to make this flexible, I'd like to be able to specify the unique key using a Lambda:
GetByUniqueKey(p => p.PersonId, 1)
Or
GetByUniqueKey(b => b.BusinessId, 1)
Ideally GetByUniqueKey would just be a shorthand method to build up an expression to send to GetWhere, and then return the FirstOrDefault() result. But the logic to do this is completely escaping me. What I want to do:
public IEnumerable<TModel> GetWhere(Expression<Func<TModel, bool>> whereExpression)
{
// Get from DB using expression provided
}
public TModel GetByUniqueKey<TUniqueKey>(
Expression<Func<TModel, TUniqueKey>> uniqueKeyProperty,
TUniqueKey value)
{
return GetWhere(m => uniqueKeyProperty(m) == value).FirstOrDefault();
}
So I want to take the uniqueKeyProperty expression, invoke it on the supplied parameter somehow to get the property, and then use that property in the whereExpression expression.
A note on duplicate questions:
I know this looks like a duplicate of other similar questions, but please note I have read those and I just can't figure out how to apply those answers to my specific use case.
Some clarification in response to comments:
Put simply, I want to do the following:
I want to take the Expression p => p.PersonId and the value 1, and generate a whereExpression that looks like this p => p.PersonId == 1. (Thanks #Rob)
You can build a new expression from the key selector and value provided like so:
public TModel GetByUniqueKey<TUniqueKey>(
Expression<Func<TModel, TUniqueKey>> uniqueKeySelector,
TUniqueKey value)
{
return GetWhere(Expression.Lambda<Func<TModel,bool>>(
Expression.MakeBinary(
ExpressionType.Equal,
uniqueKeySelector.Body,
Expression.Constant(value, typeof(TUniqueKey))),
uniqueKeySelector.Parameters));
}
For querying by ID I wouldn't bother with this approach. Check out the other static methods on the Expression class.
As apparently you would like to implement some kind of polymorphism, one possibility would be to have Person and Business inherit from the same base class or inherit from the same interface and share an Id property. You could define
public class Identifiable
{
public int Id { get; set; }
}
and make Person and Business inherit from in. Then the callback
Func<Identifiable,bool> = iIdentifiable => iIdentifiable.Id == 1
could be called for objects of both classes. However, the original classes would have to be changed for that approach to work.

Generic Find Method on Repository using Mongo

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();

Is it possible to modify an IQueryable expression manually

I'm pretty certain I know the answer is no but as a last ditch attempt I thought I'd ask the question here.
I'm using EF code first to query a table in the usual fashion
_context.Set<Foo>().Where(f => f.Bar == 999);
which creates the following expression (I've just written this so it might be wrong).
{SELECT
[Extent1].[Test] AS [Test],
[Extent1].[Test2] AS [Test2],
FROM [dbo].[Foo] AS [Extent1]
WHERE 19 = [Extent1].[Bar]}
Now, is it possible to manually modify this query to change the table name to, say, Foo10? (probably not)
Failing that, does anybody know of a way I can "late bind" the table name in code first?
You're probably wondering "Why the dirty hack?" As usual, this is a legacy issue with a database that's got some design issues and can't be changed.
Thanks in advance.
Ps. I'm aware that I could use Database.SqlQuery but would rather not.
Why don't you use TPT inheritance on your model?
Similar to #Krizz's answer, but you avoid using dynamic LINQ.
Using your comment:
if a particular parameter has a value of 1 look in Foo1 if its 2 look in Foo2 and so on
So, you could do this:
var query = ctx
.Foos
.OfMyType(value)
.Where(f => f.Bar == 999) // f.Bar is on the base/abstract entity.
.ToList();
Where OfMyType is a custom extension method on IQueryable<T>:
public static IQueryable<T> OfMyType<T>(this IQueryable<T> source, string value)
{
switch (value)
{
case "1":
return source.OfType<Foo1>();
case "2":
return source.OfType<Foo2>();
// etc, etc
}
}
Most (if not all) of the properties will be on the abstract "Foo" entity, and you create derived entities for each of the tables, which each have their own backing table.
That way, "consuming" code (e.g the ones making the queries), need not care about the different tables/Foo's, they simply pass the "magic value" to your repository (hopefully your using one), then you can silently switch to the table you want.
Would that work?
Assuming you have reasonable number of tables, I would add them all into model and create a common interface all classes will implement and then select the adequate model and use Dynamic Linq for querying.
I am not sure if this works, haven't checked it and haven't worked with "EF code-first", but this is something I would try:
Let's say your table(s) Foo have fields - Bar, Pub, X and let X be the one which the respective table depends on?
Then, I would define interface:
interface IFoo
{
int Bar { get; set; }
string Pub { get; set; }
int X { get; set; }
}
Then each table will have its class in model:
[Table("Foo1")]
class Foo1 : IFoo
{
public int Bar { get; set; }
public string Pub { get; set; }
public int X { get; set; }
}
[Table("Foo2")]
class Foo2 : IFoo
{
public int Bar { get; set; }
public string Pub { get; set; }
public int X { get; set; }
}
Then you could filter them like following:
IQueryable GetAdequateFoo(int X)
{
switch (X) // you could use reflection here to dynamically call the given Set<Foo#>()
{
case 1:
return _context.Set<Foo1>();
case 2:
return _context.Set<Foo2>();
default:
return null;
}
}
IFoo GetFooByBarAndX(int bar, int X)
{
IQueryable context = GetAdequateFoo(X);
return context.Where("it.Bar == #0", bar).Cast<IFoo>();
}
Here is how you create a new IQueryable with a new/modified expression (EF core 5.0 at the time of this writing).
var expression = query.Expression;
//modify your expression usually by building a new one or rebuilding using an ExpressionVisitor
var newQuery = query.Provider.CreateQuery(expression);
Note: I was searching for editing an Expression on an IQueryable and this is the question that came first, but the details then focus on a very specific use case and the more general question hasn't been answered...

Categories

Resources