How do I pass an EF expression as a method parameter?
To illustrate my question I have created a pseudo code example:
The first example is my method today. The example utilizes EF and a Fancy Retry Logic.
What I need to do is to encapsulate the Fancy Retry Logic so that it becomes more generic and does not duplicate.
In the second example is how I want it to be, with a helper method that accepts the EF expression as a parameter.
This would be a trivial thing to do with SQL, but I want to do it with EF so that I can benefit from the strongly typed objects.
First Example:
public static User GetUser(String userEmail)
{
using (MyEntities dataModel = new MyEntities ())
{
var query = FancyRetryLogic(() =>
{
(dataModel.Users.FirstOrDefault<User>(x => x.UserEmail == userEmail)));
});
return query;
}
}
Second Example:
T RetryHelper<T>(Expression<Func<T, TValue>> expression)
{
using (MyEntities dataModel = new (MyEntities ())
{
var query = FancyRetryLogic(() =>
{
return dataModel.expression
});
}
}
public User GetUser(String userEmail)
{
return RetryHelper<User>(<User>.FirstOrDefault<User>(x => x.UserEmail == userEmail))
}
Maybe something like this?
public TValue RetryHelper<T, TValue>(Func<ObjectSet<T>, TValue> func)
where T : class
{
using (MyEntities dataModel = new MyEntities())
{
var entitySet = dataModel.CreateObjectSet<T>();
return FancyRetryLogic(() =>
{
return func(entitySet);
});
}
}
public User GetUser(String userEmail)
{
return RetryHelper<User, User>(u => u.FirstOrDefault(x => x.UserEmail == userEmail));
}
Just to post what we discussed already...
Here is a link that might help you, I think it has something similar to what you need.
Using AsQueryable With Linq To Objects And Linq To SQL
How do I cache an IQueryable object?
I've seen better examples but I don't have them handy, basically as I mentioned you can use that to keep your query in a form so that you can further filter, change until the very last moment when you know all is done and can actually realize and enumerate the query.
hope it helps
Related
I'm currently migrating existing repositories to a generic one.
The existing method to retrieve a list of all MyEntity from the database looks like:
public IList<MyEntity> GetAll()
{
var list = _context.MyEntity.Include(_ => _.OtherEntity).ToList();
return list;
}
This method would return a list of all MyEntity, plus load their related OtherEntites.
I've simplified the repository in to a generic one, whose method looks like this:
public IList<TEntity> GetAll(params Expression<Func<TEntity, object>>[] expressions)
{
var entity = _context.Set<TEntity>();
foreach (var expression in expressions)
{
entity.Include(expression);
}
var list = entity.ToList();
return list;
}
And I would call the method like this:
var myEntityList = _repository.GetAll(_ => _.OtherEntity);
However this does not seem to load the related OtherEntites. I've had to include the expression parameter for scenarios where loading related entities may or may not be needed.
Could someone explain where I've gone wrong and why my related entities won't load in the new setup? Thanks
It looks like the first method returns the result of .Include() while the second method just calls the method, but ignores the result. LINQ methods usually return a new collection rather than modify the existing collection.
Try this:
public IList<TEntity> GetAll(params Expression<Func<TEntity, object>>[] expressions)
{
IQueryable<TEntity> entity = _context.Set<TEntity>();
foreach (var expression in expressions)
{
entity = entity.Include(expression);
}
var list = entity.ToList();
return list;
}
So I have a property that I will parse into the Select method. This will work with one parameter but can I make it work with two, and if I can't what would be the approach? I am using EF Core 3.1.8. with the SqlServer 3.1.8 package.
private static Expression<Func<ClassOne, bool, ClassTwo> Summary
{
get
{
return (p, myBool) => new ClassTwo()
{
ListOfItems = p.ListWithMyItems.Where(i => i.Field == myBool)
}
}
}
This is my Expression. I query with this method.
public async Task<ClassTwo> GetSummaryAsync(bool isAdmin = false)
{
return await _context
.DatabaseTable // type of DbSet<ClassOne>
.Select(Summary) // how do I parse isAdmin to Summary?
.ToListAsync();
}
So I hope you can see my problem. I want to avoid the where clause in the method because I have at least 10 other methods that use this Expression in different ways and also in my case it would become an nested Where which is not possible which straight querying. I don't want C# to do the work for me, let SQL Server handle that.
Thanks in advance!
EDIT:
I tried this in the GetSummaryAsync but it is not possible:
.Select(i => (i, isAdmin))
Define extension method
public static class Extensions {
public static IQueryable<ClassTwo> Summary(this IQueryable<ClassOne> one, bool myBool)
{
return one.Select(p => new ClassTwo
{
ListOfItems = p.ListWithMyItems.Where(i => i.Field == myBool)
});
}
}
And use it like this.
public async Task<ClassTwo> GetSummaryAsync(bool isAdmin = false)
{
return await _context
.DatabaseTable
.Summary(isAdmin)
.ToListAsync();
}
I am using MongoDB.Driver 2.10.4
I want to get all documents that have an id in a list of ids that I get from my controller
I am using this code :
var pending_processes = mongo_context.collectionName
.AsQueryable()
.Where(x => request.ids.Contains(x.Id))
.Select(x => new ViewModel()
{
process_id = x.Id,
// date = not important just accessing the x object
state = new States()
{
timeline = x.states.timeline,
part = x.states.part,
}
})
.ToList();
It works fine but if I make my function async and do an await and replace ToList() with ToListAsync() I get the following error:
The source IQueryable doesn't implement IAsyncEnumerable<Application.Process.Query.GetPendingProcesses.ViewModel>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
Clearly there is something I am not getting here my concern is I don't want my code to run synchronously this would be really bad. usually when dealing with postgresql context I always use the ToListAsync() but here in order to use linq with mongo I had to use AsQueryable() and AsQueryable() as I understand it does not get the data it's a normal query that I need to execute afterwards but when I use with it ToList() everything works but when I use ToListAsync() I get the error.
I just want to know what is behind all of this and is the code above synchronous or asynchronous?
I just changed my query using Find and ForEachAsync() and now all works well. I just did not use AsQueryable() because the mongodb driver as I understand it use these other functions but provides a way to use linq so I used the default methods without linq-
var result = new GetPendingProcessesViewModel();
var filter = Builders<Domain.MongoDocuments.Process>
.Filter.Where(x => own_processes.Contains(x.Id));
await _elba_mongo_context.process
.Find(filter)
.ForEachAsync(x =>
result.pending_processes_own.Add(
new GetPendingProcessesViewModelItem()
{
process_id = x.Id,
state = new States()
{
timeline = x.states.timeline,
part = x.states.part,
supplier = x.states.supplier
}
}
)
);
You can get the documentation and references for your MongoDB driver version from GitHub.
Sure you can keep it asynchronous, but first you have switch out AsQueryable to some other method which returns back and IQueryable.
In a nutshell ToListAsync() works on a IQueryable<T> only, when you turned it in to a IEnumerable via AsEnumerable() you lost the ability to call it. Its explained well here
You have a couple of choices, either implement IDbAsyncEnumerable see here or change the result list you have into an async list with Task.FromResult()
Option 1:
// try this in your controller
public async Task<List<PeopleStatesType>> GetAsyncStatesList()
{
//for e.g.
List<PeopleType> peopleList = new List<PeopleType>()
{
new PeopleType(){ Name = "Frank", Gender = "M" },
new PeopleType(){ Name = "Rose", Gender = "F" } //..
};
var result = from e in peopleList
where e.Gender == "M"
select e;
return await Task.FromResult(result.ToList());
}
Option 2:
Use this class
public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T> {
public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable) {
}
public AsyncEnumerableQuery(Expression expression) : base(expression) {
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator() {
return new InMemoryDbAsyncEnumerator<T>(((IEnumerable<T>) this).GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() {
return GetAsyncEnumerator();
}
private class InMemoryDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {
private readonly IEnumerator<T> _enumerator;
public InMemoryDbAsyncEnumerator(IEnumerator<T> enumerator) {
_enumerator = enumerator;
}
public void Dispose() {
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken) {
return Task.FromResult(_enumerator.MoveNext());
}
public T Current => _enumerator.Current;
object IDbAsyncEnumerator.Current => Current;
}
}
// FindAll: with a condition like .Find(x => x.user == "Jone Doe")
// see [here][3]
var task = collection.Find(p => true).ToListAsync();
Update Option 3: Simple Async Get
public async Task<IEnumerable<MyMongoEntity>> Where(Expression<Func<MyMongoEntity, bool>> expression = null)
{
return await context.GetCollection<MyMongoEntity>(typeof(MyMongoEntity).Name, expression).Result.ToListAsync();
}
Based on your comment, for a simple get documents collection, this helper should work.
From your error, it seems like the mongo_context.collectionName is returning something from Entity Framework?
Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
Make sure you are calling the AsQueryable extension method directly on the Mongo collection. (Your code just shows mongo_context.collectionName.AsQueryable() so I'm not sure you're doing that)
Hooking into the LINQ provider requires getting access to an IQueryable instance. The driver provides an AsQueryable extension method on IMongoCollection.
var collection = db.GetCollection<Person>("people");
var queryable = collection.AsQueryable();
Reference: https://mongodb.github.io/mongo-csharp-driver/2.10/reference/driver/crud/linq/#queryable
The AsQueryable extension above actually returns an IQueryable instance that implements IMongoQueryable and has all the same async extensions that other ORMs (Entity Framework, NHibernate, etc.) have - including ToListAsync.
Here is what I want to do:
var user = db.User.First(conditions);
user.Book.First();
Here is currently how I have to do that.
var user = db.User.Include("Book").First(conditionsForUser);
user.Book.First();
The reason why I want to simplify this, is because I don't want to have to specify what is included every time I want to access a relationship. Seems very cumbersome.
e.g.: I would like to just be able to do the following, given I have previously retrieved a user:
user.Book.First()
user.Blog.First()
user.SomeOtherHasManyRelationship.Where(conditions)
Here is what I have so far:
public object RelationshipFor(string relationship)
{
using (var db = User.DbContext())
{
var relationshipType = TypeRepresentedBy(relationship); // unused for now, not sure if I need the type of the relationship
var myTable = ((ICollection)db.Send(RelationshipName)); // RelationshipName is "User" in this instance.
var meWithRelationship = myTable.Where(i => i.Send(IdColumn) == Id).Include(relationship); // currently, myTable doesn't know about 'Where' for some reason.
return meWithRelationship.Send(relationship);
}
}
And then how that would be used would be the following:
user.RelationshipFor("Book") // returns a list of books
I have some other logic in my code which abstracts that further which would allow me to do user.Book.First().
Hopefully I can get permission to open source a lot of this, as I'm modelling a lot of the api after ActiveRecord-style crud.
Note, that I'm using I set of extensions I made to help dealing with dynamicness less painful: https://github.com/NullVoxPopuli/csharp-extensions
UPDATE 1:
public object RelationshipFor(string relationship)
{
using (var db = User.DbContext())
{
var myTable = (DbSet<DatabaseModels.User>)db.Send(RelationshipName);
var myInclude = myTable.Include(i => i.Send(relationship));
var meWithRelationship = myInclude.First(i => (long)i.Send(IdColumn) == Id);
return meWithRelationship.Send(relationship);
}
}
For now, I've hard coded the cast of the user in an attempt to just get something working.
My error now is:
Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.
This is not a trivial problem, and there's no "one size fits all" approach. What you actually seem to be after is lazy loading, which was not included in EF7 for many reasons.
I don't know what the code you show is supposed to do, but one option would be to introduce a repository pattern, where you specify the "entities to include" at the collection level:
public class UserRepository
{
private readonly IQueryable<User> _dataSet;
public UserRepository(IQueryable<User> userDataSet)
{
_dataSet = userDataSet;
}
public IQueryable<User> Include()
{
return _dataSet.Include(u => u.Book)
.Include(u => u.Blog);
}
}
And you can move lots of the logic to a generic base class, leaving you with just the Include() method. You can for example work with strings as you show (or enums, or ...), to only select related entities to include:
public class GenericRepository
{
// ...
public IQueryable<User> Include(string includeGroup = null)
{
return IncludeGroup(includeGroup);
}
protected virtual IncludeGroup(string includeGroup)
{
return _dataSet;
}
}
And then in UserRepository:
protected override IQueryable<User> IncludeGroup(string includeGroup)
{
switch (includeGroup.ToUpperInvariant())
{
case "BOOK":
return _dataSet.Include(u => u.Book)
.Include(u => u.Book.Author);
case "BLOG":
return _dataSet.Include(u => u.Blog);
default:
return base.Include(includeGroup);
}
}
And then use it like this:
var userRepo = new UserRepository(db.User);
var userWithBooks = userRepo.Include("Book");
var firstUser = userWithBooks.FirstOrDefault(u => u.Name == "Foo");
var firstUserFirstBook = firstUser.Book.FirstOrDefault();
One alternative would be to always include all navigation properties (recursively), but that would be a horrible approach in terms of query efficiency, as every query will be one massive join to all related tables, whether that is necessary or not.
I'm looking for an equivalent of the DataContext.GetTable<TEntity> in Entity Framework.
I've found the ObjectContext.CreateQuery<T> method but it is different from DataContext.GetTable<TEntity> since it needs a querystring to work.
Is there a way to get an IQueryable object for a table using the entity type without specifying the querystring?
*EDIT: Added code snippet*
This is a snippet of a Repository class I've implemented that works with linq2sql. I can't use ObjectContext.[TableName] because it wouldn't be generic anymore.
public class BaseRepository<TClass> : IDisposable
where TClass : class
{
protected BaseRepository(DataContext database)
{
_database = database;
}
...
public IQueryable<TClass> GetAllEntities()
{
IQueryable<TClass> entities = _database.GetTable<TClass>();
return entities;
}
public IQueryable<TClass> GetEntities(Expression<Func<TClass, bool>> condition)
{
IQueryable<TClass> table = _database.GetTable<TClass>();
return table.Where(condition);
}
*EDIT: Added my solution (so far..)*
This is what I'm using:
public IQueryable<TClass> GetEntities(Expression<Func<TClass, bool>> condition)
{
IQueryable<TClass> table = _database.CreateQuery<TClass>(typeof(TClass).Name);
return table.Where(condition);
}
This works as long as the class name is the same of the table name. This will became a problem for me when I'll start using different objects for the same table.
I hope I've been clear, thanks in advance,
Marco :)
Actually, the EF designer itself uses CreateQuery with hard-coded strings for the static references. If you dig into the designer file you'll see something like this:
public global::System.Data.Objects.ObjectQuery<Customers> Customers
{
get
{
if ((this._Customers == null))
{
this._Customers = base.CreateQuery<Customers>("[Customers]");
}
return this._Customers;
}
}
private global::System.Data.Objects.ObjectQuery<Customers> _Customers;
Technically there's no perfect solution because you can use the same entity type for different entity sets. But you can give it the old college try:
public IQueryable<TEntity> GetEntities<TEntity>()
{
Type t = typeof(TEntity);
var edmAttr = (EdmEntityTypeAttribute)Attribute.GetCustomAttribute(t,
typeof(EdmEntityTypeAttribute), false);
if (edmAttr == null) // Fall back to the naive way
{
return context.CreateQuery<TEntity>(t.Name);
}
var ec = context.MetadataWorkspace.GetEntityContainer(
context.DefaultContainerName, DataSpace.CSpace);
var entityType = context.MetadataWorkspace.GetType(edmAttr.Name,
edmAttr.NamespaceName, DataSpace.CSpace);
var es = ec.BaseEntitySets.First(es => es.ElementType == entityType);
return context.CreateQuery<TEntity>(es.Name);
}
public IQueryable GetTable<T>(T entity) where T : class
{
return context.CreateObjectSet<T>();
}
I hope I'm not missing the point, but wouldn't it be:
ObjectContext.TableName
Where TableName is the EntitySet of the type you want to work with.