using selector in entity framework repositories - c#

I am having trouble with the Select method in my repository method when I try to use certain return types.
The repository method where I am having the issue is:
public IEnumerable<T> List(Expression<Func<T, bool>> filter = null,
string include = "",
int Taked = 0, Expression<Func<T, T>> selector = null)
{
IQueryable<T> query = dbSet;
if (filter != null)
query = query.Where(filter);
#region Stringleri İnclude Eder
foreach (var includeProperty in
include.Split(new char[] {','},
StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
#endregion
if (selector != null)
query = query.Select(selector);
if (Taked != 0)
return query.Take(Taked).ToList();
return query.ToList();
}
internal DbContext context;
internal DbSet<T> dbSet;
I want to return my entity class using the method above, but I only want certain properties to be populated. I have tried the following approach:
AdminWork workunit = new AdminWork();
IEnumerable<AdminMenu> adminMenus = workunit.Menu.List(x => x.Online == true,
selector: z => new AdminMenu
{
MenuID = z.MenuID,
Name = z.Name,
Path = z.Path
});
Which throws the exception:
AdminMenu cannot be constructed in a LINQ to Entities query
I have also tried the following approach but it requires to return an IEnumerable<int>:
IEnumerable<AdminMenu> menus = workunit.Menu.List(x => x.Online == true,
selector: z => z.MenuID);
My question is how can I create new instances of my entity class in linq to entities, so not every property is populated.

You cannot pass a selector that creates instances of AdminMenu because that is a type mapped in your Entity Framework context. So Entity Framework wants to be the only one creating instances of that type so it can keep track of changes, return the same instance if already loaded etc... See this question for more info.
So, if you want to return a list of AdminMenu, you won't need to pass a selector (as the dbSet you start querying is already of that type). You would only need to pass a selector when the return type is a different one than the type in the dbSet.
You could have this method: (I have made selector a non-optional parameter as it will be used to define the TResult type. I am also assuming T is a generic parameter defined in the class like public class Repository<T>, so the only generic parameter to add in the method is TResult)
public IEnumerable<TResult> ListProjected<TResult>(Expression<Func<T, TResult>> selector,
Expression<Func<T, bool>> filter = null,
string include = "",
int Taked = 0)
{
IQueryable<T> query = dbSet;
if (filter != null)
query = query.Where(filter);
#region Stringleri İnclude Eder
foreach (var includeProperty in include.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
#endregion
if (Taked != 0)
query = query.Take(Taked);
return query.Select(selector).ToList();
}
And a different one when you don't want to project into a different type:
public IEnumerable<T> List(Expression<Func<T, bool>> filter = null,
string include = "",
int Taked = 0)
{
return ListProjected(x => x, filter, include, Taked);
}
You can then use those methods as in:
IEnumerable<AdminMenu> menus = workunit.Menu.List(x => x.Online == true);
IEnumerable<int> menuIds = workunit.Menu.ListProjected(x => x.MenuID,
x => x.Online == true);
Finally, if you only want some columns, you will need to either use an anonymous object or create another class (like a DTO) that only contains the columns you are interested in:
var menuSomeColumnsWithAnonymousObject = workunit.Menu.ListProjected(x => new
{
MenuID = x.MenuID,
Path = x.Path,
Name = x.Name
}, x => x.Id == 1);
var menuSomeColumnsWithDTO = workunit.Menu.ListProjected(x => new AdminMenuDTO
{
MenuID = x.MenuID,
Path = x.Path,
Name = x.Name
}, x => x.Id == 1);
A longer answer than I thought, but I hope it helps!

Related

Execution-Deferred IQueryable<T> from Dynamic Linq?

I am using Dynamic Linq to perform some queries (sorry but it's my only option). As a result, I am getting an IQueryable instead of an IQueryable<T>. In my case, I want an IQueryable<Thing> where Thing is a concrete type.
My query is as such:
public IQueryable<Thing> Foo(MyContext db)
{
var rootQuery = db.People.Where(x => x.City != null && x.State != null);
var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
var finalLogicalQuery = groupedQuery.Select("new ( Count() as TotalNumber, Key.City as City, Key.State as State )");
var executionDeferredResults = finalLogicalQuery.Take(10); // IQueryable
IQueryable<Thing> executionDeferredTypedThings = ??; // <--- Help here!!!!
return executionDeferredTypedThings;
}
My Thing.cs:
public class Thing
{
public int TotalNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Yes, I know the exact above thing can be done without Dynamic Linq but I have some variableness going on that I've simplified out of here. I can get it to work with my variableness if my return type is simply IQueryable but I can't figure out how to convert to IQueryable<Thing> while keeping it execution-deferred and while also keeping Entity Framework happy. I do have the dynamic Select always returning something (with the correct data) that looks like a Thing. But I simply can't figure how to return the IQueryable<Thing> and could use some help there. Thanks!!
Failed Attempt 1
Based on Rex M's suggestion, I am now trying to use AutoMapper to solve this problem (although I am not committed to this approach and am willing to try other approaches). For the AutoMapper approach, I am doing it as such:
IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!
But this results in an InvalidOperationException:
Missing map from DynamicClass2 to Thing. Create using Mapper.CreateMap.
The thing is, while I have defined Thing, I have not defined DynamicClass2 and as such, I cannot map it.
Failed Attempt 2
IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);
This gives an InvalidCastException and seems to be the same underlying problem that the above AutoMapper fail hits:
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'.
You can use AutoMapper's Queryable Extensions to produce an IQueryable which wraps the underlying IQueryable, thus preserving the original IQueryable's IQueryProvider and the deferred execution, but adds in a mapping/translating component to the pipeline to convert from one type to another.
There's also AutoMapper's UseAsDataSource which makes some common query extension scenarios easier.
If I understand correctly, the following extension method should do the job for you
public static class DynamicQueryableEx
{
public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
var dynamicLambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
var memberInit = dynamicLambda.Body as MemberInitExpression;
if (memberInit == null) throw new NotSupportedException();
var resultType = typeof(TResult);
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var lambda = Expression.Lambda(body, dynamicLambda.Parameters);
return source.Provider.CreateQuery<TResult>(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
}
(Side note: Frankly I have no idea what values argument is for, but added it to match the corresponding DynamicQueryable.Select method signature.)
So your example will become something like this
public IQueryable<Thing> Foo(MyContext db)
{
var rootQuery = db.People.Where(x => x.City != null && x.State != null);
var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
var finalLogicalQuery = groupedQuery.Select<Thing>("new ( Count() as TotalNumber, Key.City as City, Key.State as State )"); // IQueryable<Thing>
var executionDeferredTypedThings = finalLogicalQuery.Take(10);
return executionDeferredTypedThings;
}
How it works
The idea is quite simple.
The Select method implementation inside the DynamicQueryable looks something like this
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
What it does is to dynamically create a selector expression and bind it to the source Select method. We take exactly the same approach, but after modifying the selector expression created by the DynamicExpression.ParseLambda call.
The only requirement is that the projection is using "new (...)" syntax and the names and types of the projected properties match, which I think fits in your use case.
The returned expression is something like this
(source) => new TargetClass
{
TargetProperty1 = Expression1(source),
TargetProperty2 = Expression2(source),
...
}
where TargetClass is a dynamically generated class.
All we want is to keep the source part and just replace that target class/properties with the desired class/properties.
As for the implementation, first the property assignments are converted with
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
and then the new DynamicClassXXX { ... } is replaced with with
var body = Expression.MemberInit(Expression.New(resultType), bindings);
Would something like this be of benefit to you?
public static IQueryable<TEntity> GetQuery<TEntity>(this DbContext db, bool includeReferences = false) where TEntity : class
{
try
{
if (db == null)
{
return null;
}
var key = typeof(TEntity).Name;
var metaWorkspace = db.ToObjectContext().MetadataWorkspace;
var workspaceItems = metaWorkspace.GetItems<EntityType>(DataSpace.OSpace);
var workspaceItem = workspaceItems.First(f => f.FullName.Contains(key));
var navProperties = workspaceItem.NavigationProperties;
return !includeReferences
? db.Set<TEntity>()
: navProperties.Aggregate((IQueryable<TEntity>)db.Set<TEntity>(), (current, navProperty) => current.Include(navProperty.Name));
}
catch (Exception ex)
{
throw new ArgumentException("Invalid Entity Type supplied for Lookup", ex);
}
}
You may want to take a look into the Generic Search project on Github located here:
https://github.com/danielpalme/GenericSearch
There is no need for Dynamic Linq on this one.
var groupedQuery = from p in db.People
where p.City != null && p.State != null
group p by new {p.City, p.State}
into gp
select new Thing {
TotalNumber = gp.Count(),
City = gp.Key.City,
State = gp.Key.State
};
IQueryable<Thing> retQuery = groupedQuery.AsQueryable();
retQuery= retQuery.Take(10);
return retQuery;

Resolving generic lambda expression type

I have a method that calls Entity Framework functions. It is responsible for sending the "select clause", "order by clause" and possible includes (considering that I'm using lazy loading). The method looks like this:
public IEnumerable<TReturn> GetAll<TReturn, TOrderKey>(
Expression<Func<TEntity, TReturn>> selectExp,
Expression<Func<TEntity, TOrderKey>> orderbyExp,
Boolean descending,
params Expression<Func<TEntity, Object>>[] includeExps)
{
var query = DbSet.AsQueryable();
query = !descending ? query.OrderBy(orderByExp) : query.OrderByDescending(orderByExp);
if (includeExps != null)
query = includeExps.Aggregate(query, (current, exp) => current.Include(exp));
return query.Select(selectExp).ToList();
}
When I call:
_service.GetAll(i => new { i.Name}, i => i.Name, false, null);
It works fine! That is the generated SQL is the exactly as I wanted.
However, considering a real scenario (in my case I'm using asp.net mvc), I have an Action method that gets the order parameter from the client.
That is the method:
public JsonResult GetAllUsers(string sortColumn, bool sortDescending)
{
//sortColumn string must be translated in a Expression
var users = _service.GetAll(i => new { i.Name, i.Email }, i => i.Name, sortDescending, null);
//
//
}
My first attempt was to create an expression for every column, like this:
public JsonResult GetAllUsers(string sortColumn, bool sortDescending)
{
//I don't what is the Type that I should put here
//It can be anything, like: Expression<Func<User, String>>,
//Expression<Func<User, Guid>>, Expression<Func<User, Int>>
?Type? orderExp;
switch(sortColumn)
{
case "UserId":
//Expression<Func<User, Guid>>
orderExp = i => i.UserId;
break;
case "Name":
//Expression<Func<User, String>>
orderExp = i => i.Email;
break;
}
//sortColumn string must be translated in a Expression
var users = _service.GetAll(i => new { i.Name, i.Email }, orderExp, sortDescending, null);
//
//
}
I want to create an expression based on sortProperty, there's a lot of information about it over the stackoverflow, however (see the action method) the variable must be typed before the process. The GetAll method can't be called inside every "case" because it returns an anonymous type.
I can't convert all columns to Expression<Func<User, Object>> because entity framework does not support it.
Linq.Dynamic should help, but I don't want to use string parameters.
You could overload your GetAll method as follows:
public IEnumerable<TReturn> GetAll<TReturn>(
Expression<Func<TEntity, TReturn>> selectExp,
string orderColumnName,
Boolean descending,
params Expression<Func<TEntity, Object>>[] includeExps)
{
var entityType = typeof(TEntity);
var prop = entityType.GetProperty(orderColumnName);
var param = Expression.Parameter(entityType, "i");
var orderExp = Expression.Lambda(
Expression.MakeMemberAccess(param, prop), param);
// get the original GetAll method overload
var method = this.GetType().GetMethods().Where(m => m.Name == "GetAll" && m.GetGenericArguments().Length == 2);
var actualMethod = method.First().MakeGenericMethod(typeof(TReturn), prop.PropertyType);
return (IEnumerable<TReturn>)actualMethod.Invoke(this, new object[] { selectExp, orderExp, descending, includeExps });
}
Just add a few more checks for null values, otherwise invalid sortColumn names will come back haunting you.
Use it similar to your current approach:
_service.GetAll(i => new { i.Name, i.Email }, sortColumn, sortDescending, null);

DbSet doesn't have a Find method in EF7

I am trying to create a generic repository to access my database. In EF6 I was able to do that in order to get a specific entity:
protected IDbSet<T> dbset;
public T Get(object id)
{
return this.dbset.Find(id);
}
DbSet in EF7 is missing a Find method. Is there a way to implement the above piece of code?
Here's a very crude, incomplete, and untested implementation of .Find() as an extension method. If nothing else, it should get you pointed in the right direction.
The real implementation is tracked by #797.
static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues)
where TEntity : class
{
var context = ((IAccessor<IServiceProvider>)set).Service.GetService<DbContext>();
var entityType = context.Model.GetEntityType(typeof(TEntity));
var key = entityType.GetPrimaryKey();
var entries = context.ChangeTracker.Entries<TEntity>();
var i = 0;
foreach (var property in key.Properties)
{
var keyValue = keyValues[i];
entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValue);
i++;
}
var entry = entries.FirstOrDefault();
if (entry != null)
{
// Return the local object if it exists.
return entry.Entity;
}
// TODO: Build the real LINQ Expression
// set.Where(x => x.Id == keyValues[0]);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var query = set.Where((Expression<Func<TEntity, bool>>)
Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, "Id"),
Expression.Constant(keyValues[0])),
parameter));
// Look in the database
return query.FirstOrDefault();
}
In case you are using EF 7.0.0-rc1-final, below you find a small update for the code presented by #bricelam in the previous answer. By the way, thank you very much #bricelam - your code was extremely useful for me.
Here are my dependencies under "project.config":
"dependencies": {
"EntityFramework.Commands": "7.0.0-rc1-final",
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
"Microsoft.Framework.Configuration.Json": "1.0.0-beta8",
"Microsoft.Framework.ConfigurationModel": "1.0.0-beta4",
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4",
"Microsoft.Framework.DependencyInjection": "1.0.0-beta8"
}
And below is the extension method for DbSet.Find(TEntity):
using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Microsoft.Data.Entity.Extensions
{
public static class Extensions
{
public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
{
var context = ((IInfrastructure<IServiceProvider>)set).GetService<DbContext>();
var entityType = context.Model.FindEntityType(typeof(TEntity));
var key = entityType.FindPrimaryKey();
var entries = context.ChangeTracker.Entries<TEntity>();
var i = 0;
foreach (var property in key.Properties)
{
entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
i++;
}
var entry = entries.FirstOrDefault();
if (entry != null)
{
// Return the local object if it exists.
return entry.Entity;
}
// TODO: Build the real LINQ Expression
// set.Where(x => x.Id == keyValues[0]);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var query = set.Where((Expression<Func<TEntity, bool>>)
Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, "Id"),
Expression.Constant(keyValues[0])),
parameter));
// Look in the database
return query.FirstOrDefault();
}
}
}
Can't comment because of reputation, but if you use RC2 (or later?) you should use
var context = set.GetService<ICurrentDbContext>().Context;
instead of
var context = set.GetService<DbContext>();
I've taken some of the previously provided answers and tweaked them to fix a couple of problems:
Implicitly captured closure
Key shouldn't be hard coded to "Id"
public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
{
var context = set.GetService<DbContext>();
var entityType = context.Model.FindEntityType(typeof(TEntity));
var key = entityType.FindPrimaryKey();
var entries = context.ChangeTracker.Entries<TEntity>();
var i = 0;
foreach (var property in key.Properties)
{
var i1 = i;
entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i1]);
i++;
}
var entry = entries.FirstOrDefault();
if (entry != null)
{
// Return the local object if it exists.
return entry.Entity;
}
var parameter = Expression.Parameter(typeof(TEntity), "x");
var query = set.AsQueryable();
i = 0;
foreach (var property in key.Properties)
{
var i1 = i;
query = query.Where((Expression<Func<TEntity, bool>>)
Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, property.Name),
Expression.Constant(keyValues[i1])),
parameter));
i++;
}
// Look in the database
return query.FirstOrDefault();
}
Find finally arrives into Entity Framework core.
So...the above find methods worked great, but if you don't have a column named "Id" in your model, the whole thing is going to fail on the following line. I'm not sure why the OP would have put a hardcoded value into this spot
Expression.Property(parameter, "Id"),
Here's a revision that will fix it for those that name our Id columns appropriately. :)
var keyCompare = key.Properties[0].Name;
// TODO: Build the real LINQ Expression
// set.Where(x => x.Id == keyValues[0]);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var query = set.Where((Expression<Func<TEntity, bool>>)
Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, keyCompare),
//Expression.Property(parameter, "Id"),
Expression.Constant(keyValues[0])),
parameter));
// Look in the database
return query.FirstOrDefault();
}
This STILL very well could fail if you have more than one Key setup on the entity object and the key you're looking up by isn't the first, but it should be quite a bit btter this way.
Not enough reputation to comment, but there is a bug in #Roger-Santana answer when using it in a console app/seperate assembly:
var i = 0;
foreach (var property in key.Properties)
{
entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
i++;
}
var entry = entries.FirstOrDefault();
The value of 'i' is captured in the foreach so that when entries.FirstOrDefault() is called, keyValues[i] has the value of (at least) keyValues[i++], which in my case crashed with an out of index error.
A fix would be to copy the value of 'i' through the loop:
var i = 0;
foreach (var property in key.Properties)
{
var idx =i;
entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[idx]);
i++;
}
var entry = entries.FirstOrDefault();
I use linq; instead of Find method you can use:
var record = dbSet.SingleOrDefault(m => m.Id == id)
Let me contribute a revision that includes building the expression.
I'll confess I didn't actually test this ;-)
public static TEntity Find<TEntity>(this DbSet<TEntity> dbSet, params object[] keyValues) where TEntity : class
{
// Find DbContext, entity type, and primary key.
var context = ((IInfrastructure<IServiceProvider>)dbSet).GetService<DbContext>();
var entityType = context.Model.FindEntityType(typeof(TEntity));
var key = entityType.FindPrimaryKey();
// Build the lambda expression for the query: (TEntity entity) => AND( entity.keyProperty[i] == keyValues[i])
var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
Expression whereClause = Expression.Constant(true, typeof(bool));
uint i = 0;
foreach (var keyProperty in key.Properties) {
var keyMatch = Expression.Equal(
Expression.Property(entityParameter, keyProperty.Name),
Expression.Constant(keyValues[i++])
);
whereClause = Expression.And(whereClause, keyMatch);
}
var lambdaExpression = (Expression<Func<TEntity,bool>>)Expression.Lambda(whereClause, entityParameter);
// Execute against the in-memory entities, which we get from ChangeTracker (but not filtering the state of the entities).
var entries = context.ChangeTracker.Entries<TEntity>().Select((EntityEntry e) => (TEntity)e.Entity);
TEntity entity = entries.AsQueryable().Where(lambdaExpression).First(); // First is what triggers the query execution.
// If found in memory then we're done.
if (entity != null) { return entity; }
// Otherwise execute the query against the database.
return dbSet.Where(lambdaExpression).First();
}
here is what I use.
Not a find method, but works like a charm
var professionalf = from m in _context.Professionals select m;
professionalf = professionalf.Where(s => s.ProfessionalId == id);
Professional professional = professionalf.First();
An edit was proposed to change ".First()" to ".FirstOrDefault()" in the very last line of my earlier post. The edit was voted down, but I agree with it. I would expect the function to return null if the key was not found. I would not want it to throw an exception. In most cases I would want to know if the key existed in the set, and handling an exception is a very slow way of figuring that out.

method to map entity to a poco getting invoke error

I am getting the "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." error with this bit of code.
Function Call
IEnumerable<OrderListItem> orders;
orders = _service.GetAllForUser<OrderListItem>(userName, mapOrderToListItem);
var foo = orders.ToArray();
Functions
public IEnumerable<T> GetAllForUser<T>(string userName, Func<Order, T> mapper)
{
string displayName = _adService.GetDisplayName(userName);
return _repo.Query().Where(x => x.OrderedBy == userName)
.OrderBy(x => x.OrderDate)
.Select(x => mapper(x))
.AsEnumerable();
}
private OrderListItem mapOrderToListItem(Order order)
{
OrderListItem result = new OrderListItem
{
DeliveryLoc = order.OrderGroup.DeliveryLocation.Name,
Department = order.OrderGroup.Department.Name,
Id = order.Id,
OrderDate = order.OrderDate,
OrderedBy = order.OrderedBy,
Status = !order.ApprovedDate.HasValue ? "SUBMITTED" : !order.ReceivedDate.HasValue ? order.ApprovalStatus == ApprovalStatus.Approved ? "APPROVED" : "DENIED" : !order.FilledDate.HasValue ? "RECEIVED" : "FILLED"
};
return result;
}
however i do not receive the invoke error with this bit of code
Function Call
IEnumerable<ProductListItem> products;
products = _service.SearchAll<ProductListItem>(sSearch, 0, all, orderBy, sortDir, productListItemMapper);
var foo = products.ToArray();
Function
public IEnumerable<T> SearchAll<T>(string sSearch, int skip, int take, Func<Product, IComparable> sortCol, DAL.SortDir sortDir, Func<Product, T> mapper)
{
switch (sortDir)
{
case DAL.SortDir.asc:
return _repo.Query().Where(r => (r.Name.Contains(sSearch) || r.CatalogNumber.Contains(sSearch)))
.OrderBy(sortCol).Skip(skip).Take(take).Select(x => mapper(x)).AsEnumerable();
case DAL.SortDir.dsc:
return _repo.Query().Where(r => (r.Name.Contains(sSearch) || r.CatalogNumber.Contains(sSearch)))
.OrderByDescending(sortCol).Skip(skip).Take(take).Select(x => mapper(x)).AsEnumerable();
default:
throw new ArgumentException("sortDir");
}
}
private ProductListItem productListItemMapper(Product product)
{
ProductListItem output = new ProductListItem
{
Id = product.Id,
Location = product.WarehouseLocation.Name,
Name = product.Name,
Active = product.Active,
Number = product.CatalogNumber,
Units = product.UnitOfMeasure.Name,
UnitsId = product.UnitOfMeasureId,
LocationId = product.WarehouseLocationId
};
return output;
}
i feel like i'm just blind and missing something simple. Why does it work for products but not for orders?
Just for the error message I can think it's because you're trying to use the invoke on the sql server side, try to convert the result first to a collection (list/array) then use your function, so instead "select(e => mapper(e))" do first a ToList() then a select. This is because and IQueryable executes your commands on sql and sql doesn't know about your mapper function.
Edit: keep in mind ToList will retrieve all the records from the db.

Generalizing CRUD tests in Linq to Entities

We have a lot of tests such as this:
using (new TransactionScope())
{
var repository = _unitOfWork.DataFieldSetRepository;
var entity = new DataFieldSet {Label = "test", Title = "test"};
repository.Add(entity);
_unitOfWork.Commit();
entity = repository.GetAll().Single(x => x.Id == entity.Id);
entity.IsClosed = true;
repository.Update(entity);
_unitOfWork.Commit();
repository.Delete(entity);
_unitOfWork.Commit();
}
There are no Asserts because the test is passed unless an exception is thrown.
Instead of copying this code and altering a few details, I would like to generalize it.
The only bits that vary are
1) The entity type and the corresponding repository (here DataFieldSet and DataFieldSetRepository)
2) The contents of the newly created entity (typically the minimal contents to make the test pass, here Label and Title cannot be null)
3) The update operation (typically just one random property has its value changed)
So far I have this:
public void AssertCrudOperationsWork<T>(T entity, Func<T, T, bool> keyComparer, Action<T> updateAction) where T : class
{
using (new TransactionScope())
{
var repository = (IRepository<T>)_unitOfWork.GetType().GetProperty(typeof(T).Name + "Repository").GetValue(_unitOfWork);
repository.Add(entity);
_unitOfWork.Commit();
//var keyProperties = typeof(T).GetProperties().Where(prop => prop.IsDefined(typeof(KeyAttribute), false));
Expression<Func<T, bool>> keyEqualsKey = x => keyComparer(x, entity);
entity = repository.GetAll().Single(keyEqualsKey);
updateAction(entity);
repository.Update(entity);
_unitOfWork.Commit();
repository.Delete(entity);
_unitOfWork.Commit();
}
}
[TestMethod]
public void CRUDTest_DataFieldGroup()
{
AssertCrudOperationsWork(new DataFieldSet {Label = "test", Title = "test"}, (a, b) => a.Id == b.Id, x => x.IsClosed = true);
}
The problem is, it fails in the call to Single() with: The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
I guess my keyEqualsKey predicate is not exactly the same as the original x => x.Id == entity.Id.
Is there a way to fix this?
The commented line //var keyProperties ... gets all the key properties of the entity. Is there a dynamic way to build the predicate comparing the entity keys so that the keyComparer and the keyEqualsKey can be removed altogether?

Categories