Getting a single item from a table containing 6,000 records is taking about 30 seconds. Obviously, this is not acceptable and I can't figure out why. My stack is .NET 4.5, EF 6, and Web API 2. Is anything glaringly wrong with what I've done?
// DbSet
internal DbSet<TEntity> _dbSet;
// Ctor
public GenericRepository(TContext context)
{
_context = context;
_context.Configuration.ProxyCreationEnabled = false;
_dbSet = _context.Set<TEntity>();
}
// Really slow method
public TEntity GetByFilter(Func<TEntity,bool> filter, params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = _dbSet;
if (includes != null)
{
foreach (var include in includes)
query = query.Include(include);
}
var entity = query.Where(filter).FirstOrDefault();
return entity;
}
// Here's how it's called. It returns a single item
var x = _unitOfWork.Repository.GetByFilter(i => i.WinId == id, null);
The reason why this is slow is you are using linq-to-objects in your Where clause, meaning you're executing the predicate on the client (C#) instead of the server (SQL), the C# is receiving 6000 database records and then filtering it in memory.
You can see this because your filter parameter is of type Func, which implies you're using linq-to-objects by way of the IEnumerable.Where extension.
Instead, what you want to use is the IQueryable.Where extension that takes a parameter of type Expression. This makes use of the Entity Framework query provider, and thus uses linq-to-ef instead.
Update your method signature to be as follows:
public TEntity GetByFilter(
Expression<Func<TEntity,bool>> filter,
params Expression<Func<TEntity, object>>[] includes)
This is illustrated further in the following stackoverflow answer https://stackoverflow.com/a/793584/507793
Related
I migrate .NET Framework to ASP.NET Core Application. We need to move also test projects. But in previous tests, the Include() method was mocked and many tests are based on this. The Include() method in ASP.NET where just virtual method, which I can easy mock by this way:
var fakePersons= PersonsServiceTestsData.GetPersonsMockedSet();
applicationDbContext.Persons.Returns(fakePersons);
applicationDbContext.Persons.Include(Arg.Any<string>()).Returns(powerPlantsSet);
public static DbSet<TEntity> SetupData<TEntity>(
this DbSet<TEntity> dbset,
ICollection<TEntity> data = null,
Func<object[], TEntity> find = null) where TEntity : class
{
data = data ?? new List<TEntity>();
IQueryable<TEntity> GetQuery() => new InMemoryAsyncQueryable<TEntity>(data.AsQueryable());
((IQueryable<TEntity>)dbset).Provider.Returns(info => GetQuery().Provider);
((IQueryable<TEntity>)dbset).Expression.Returns(info => GetQuery().Expression);
((IQueryable<TEntity>)dbset).ElementType.Returns(info => GetQuery().ElementType);
((IQueryable<TEntity>)dbset).GetEnumerator().Returns(info => GetQuery().GetEnumerator());
dbset.Remove(Arg.Do<TEntity>(entity => data.Remove(entity)));
dbset.Add(Arg.Do<TEntity>(entity => data.Add(entity)));
//dbset.AsNoTracking().Returns(dbset); ***//causes issue***
return dbset;
}
In ASP.NET Core Include() from LINQ is static method which can't be mocked this way ( there is no compliation error , but in run time there is:
ActivatorChain: λ:APPLICATION.DataModel.IQueryContext
----> System.ArgumentNullException : Value cannot be null. (Parameter 'navigationPropertyPath')
Similar problem is with AsNoTracking() method...
Why we should not mock static classes?
What to do in that case? When we want to mock Include() or AsNoTracking() methods in asp.net core?
I am trying to build out a web application using a repository pattern. In doing so, I have noticed that my navigation properties are not loading. I was able to get my properties to load using EF Proxies, but when I went to use my DTOs, it really didn't like that. I have since removed all the Proxy bit of code and decided that I just need to provide a way to Eager Load the data I care about.
In my repository, I have an interface similar to this. Using DBSet, I am able to use the Find method to generically find the value and return it from the repo. Doing this, I am not able to pass in Includes to resolve the navigation properties.
public async Task<T> GetById(int Id)
{
var query = await this.Context.Set<T>().FindAsync(Id);
// Left split like this for clarity
return query;
}
The only way I got this working was by removing the generic piece and adding a new method called GetUserById into the UserRepository.
public class UserRepository : RepositoryBase<User>, IUserRepository
{
private readonly Context _context;
public UserRepository(Context context) : base(context) {
_context = context;
}
public User GetUserById(int Id)
{
var query = _context.Users.Include("Department").Where(x => x.Id == Id).FirstOrDefault();
return query;
}
}
Please tell me there is a better way.
Try this GetUserById implementation:
public User GetUserById(int Id, params Expression<Func<User, object>>[] includes)
{
var query = _context.Users.Where(x => x.Id == Id);
foreach (var include in includes)
{
query.Include(include);
}
return query.FirstOrDefault();
}
And this is how you would call it:
var repo = new UserRepository();
var user = repo.GetUserById(100, u => u.Department);
As the title says I want to return all entities which matches an Expression<Func<T, bool>> in EF Core. I've created the FirstOrDefaultAsync, but I cannot find any way to return all entities which matches the predicate.
public async Task<TEntity> ReadAsync(Expression<Func<TEntity, bool>> predicate)
{
return await _dbContext.Set<TEntity>().FirstOrDefaultAsync(predicate);
}
I've seen examples where the Where method is used, but this is not listed as an alternative method. How can I return all entities which matches the predicate in EF Core?
You can use Where. You should be aware that there is no async version of Where, since execution is deferred (i.e. Where will not cause the query to be executed on the DB, so there is no operation that can run asynchronously). If you want to keep your method async, you have to await a ToListAsync operation:
public async Task<List<TEntity>> ReadAsync(Expression<Func<TEntity, bool>> predicate)
{
return await _dbContext.Set<TEntity>().Where(predicate).ToListAsync();
}
Note that in cases when you immediately return from the async operation, you don't need to await and can save yourself the overhead of an async method:
public Task<List<TEntity>> ReadAsync(Expression<Func<TEntity, bool>> predicate)
{
return _dbContext.Set<TEntity>().Where(predicate).ToListAsync();
}
If your problem is you do not find the Where(predicate) extension method, please verify that if you're referencing System.Linq.Queryable
Using Moq. I have a repository with the following interface:
public virtual IEnumerable<TEntity> GetBySpec(ISpecification<TEntity> specification, params string[] includes)
{
IQueryable<TEntity> query = includes.Aggregate<string, IQueryable<TEntity>>(_dbSetQuery, (current, include) => current.Include(include));
return query.Where(specification.SatisfiedBy())
.AsEnumerable<TEntity>();
}
In this case, i'm using a DirectSpecification:
public sealed class DirectSpecification<TEntity> : Specification<TEntity>
{
Expression<Func<TEntity, bool>> _MatchingCriteria;
public DirectSpecification(Expression<Func<TEntity, bool>> matchingCriteria)
{
_MatchingCriteria = matchingCriteria;
}
public override Expression<Func<TEntity, bool>> SatisfiedBy()
{
return _MatchingCriteria;
}
}
In my actual code i'm calling
var recentlyChanged = _vwPersonRepository.GetBySpec(
new DirectSpecification<vwPerson>(person =>
person.ModifiedDate > modifiedFromDay &&
person.ModifiedDate < modifiedTo));
var recentlyCreated = _vwPersonRepository.GetBySpec(
new DirectSpecification<vwPerson>(person =>
person.CreatedDate > createdFromDay &&
person.CreatedDate < createdTo));
Edit: As suggested by duplicate, I've tried this:
Container.GetMock<IvwPersonRepository>()
.Setup(p => p.GetBySpec(It.IsAny<ISpecification<vwPerson>>()))
.Returns((Expression<Func<vwPerson, bool>> predicate) =>
items.Where(predicate));
I get a
Exception thrown: 'System.Reflection.TargetParameterCountException' in mscorlib.dll
Additional information: Parameter count mismatch.
My question is complicated by having the ISpecification parameter, how can I get the correct parameters so I can work with the predicate?
Edit 2: Thanks to Patrick, here is the solution:
Container.GetMock<IvwPersonRepository>()
.Setup(p => p.GetBySpec(It.IsAny<ISpecification<vwPerson>>(), It.IsAny<string[]>()))
.Returns((ISpecification<vwPerson> specification, string[] includes) =>
items.Where(predicate));
They key was to include the string[] includes, even though I don't pass it as a parameter the reflection finds it and expects it to be there.
Brilliant!
The Setup call in your edit is wrong, it should be:
Container.GetMock<IvwPersonRepository>()
.Setup(p => p.GetBySpec(It.IsAny<ISpecification<vwPerson>>()))
.Returns((ISpecification<vwPerson> specification) => /* TODO */);
(This is because the parameters passed to Returns are the parameters passed to the function being setup, which in this case is GetBySpec.)
I believe (based on what you posted) you could just do this:
Container.GetMock<IvwPersonRepository>()
.Setup(p => p.GetBySpec(It.IsAny<ISpecification<vwPerson>>()))
.Returns((ISpecification<vwPerson> specification) => items.Where(specification.SatisfiedBy()));
However, you might see some benefit by using a factory to create your specifications so that you can mock them to avoid relying on their implementation (in the call to SatisfiedBy above).
Can Please sugget Best /Efficient way to perform CRUD Opration in MVC application.
I am using Entity Framework in my project for Database access.
ex: EDMX: SupportContext ;
Here i performed the CRUD Operation using SupportContext [.edmx}. which processed more quickly than repository. and also i can retrive data's from more than one context object[table's] in a LinQ query.
ex:
using (SupportContext support =new SupportContext())
{
var=(from t1 in support.table1
from t2 in support.table 2
where t1.id==t2.id
select t1).ToList();
}
I have used Generic Repository Pattern for instead of direct access with EDMx. it act layer between my code logic and EDMX file. find the generic example.
code:
public interface IRepository<T> : IDisposable where T : class
{
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
void Add(T entity);
void SaveChanges();
}
public class GenericRepository<T> : IRepository<T> where T : class
{
private ObjectContext _context;
private IObjectSet<T> _objectSet;
public GenericRepository(ObjectContext context)
{
_context = context;
_objectSet = _context.CreateObjectSet<T>();
}
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
{
return _objectSet.Where<T>(predicate);
}
public void Add(T entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_objectSet.AddObject(entity);
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
Here i have used this repository for CRUD opration as:
var dat1=new GenericRepository<Table1>(new SupportContext());
var dat2=new GenericRepository<Table2>(new SupportContext());
Here i want to retrieve the records in both dat1,dat2 repository while it Id filed is equal. But I can't able to retrieve in combine way. but do it in single way.
var=dat1.Find(t=>t.id==3).toList();
Can you please suggest how to retrieve data 's from combined repository AND also suggest which method is best way to Access data ?