I have this method in my Generic EF Repository class
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
public async Task<IEnumerable<T>> GetAllByExpression<T>(Expression<Func<T, bool>> expression,
Expression<Func<T, object>>[] includes) where T : class
{
if (expression != null)
{
IQueryable<T> query = context.Set<T>();
foreach(var include in includes)
{
query = query.Include(include);
}
var result = await query.Where(expression)
.ToListAsync();
return result;
}
else
{
throw new ArgumentNullException("Invalid expression.");
}
}
}
I do not know how to use the "Include" to it. Basically what I want to get is like this =>
return await context.PropertyChargesRates.Where(x => x.BuildingId == buildingId)
.Include(x => x.PropertyChargeType)
.ToListAsync();
This is how I try to utilize this function:
public async Task<IEnumerable<PropertyChargesRate>> GetPropertyChargeRates(int buildingId)
{
try
{
var g = new GenericRepository<PropertyChargesRate>(context);
var result = g.GetAllByExpression<PropertyChargesRate>(x => x.BuildingId == buildingId, ** CANT FIGURE OUT HOW TO CALL THIS PART YET **);
return result;
}
catch (Exception ex)
{
return null;
}
}
Implementation that you are looking for could look like this:
public IEnumerable<T> GetAllByExpression<T>(
YourDBContext context,
Expression<Func<T, bool>> expression,
Func<IQueryable<T>, IQueryable<T>> includes) where T : class
{
if (expression != null)
{
IQueryable<T> query = context.Set<T>();
query = includes(query);
var result = query.Where(expression)
.ToList();
return result;
}
throw new ArgumentNullException(nameof(expression), "Invalid expression.");
}
And usage would look like this:
repository.GetAllByExpression<YourEntity>
(YourDbContext,
YourWhereExpression,
(q)=> q.Include(i=> i.YourInclude1)
.Include(i2=> i2.YourInclude2));
Related
I have created an API that is using EF Core with a repository pattern and I have few questions:
Post method receives an email address and verify whether user exists on not.
If an email address does not exist in the User table, get the guest access details from the AccessManagement table and save in Entitlement table and return the details
If the entry exists, get the user access details and return them
IGeneralRepository:
public interface IGenrealRepository<TEntity> where TEntity : class , new()
{
IQueryable<TEntity> GetAll();
Task<TEntity> AddAsync(TEntity entity);
Task<TEntity[]> AddRangeAsync(TEntity[] entity);
TEntity Update(TEntity entity);
Task<int> CompleteAsync();
}
General repository:
public class GeneralRepository<TEntity> : IGenrealRepository<TEntity> where TEntity : class, new()
{
private MyDbContext _myDbContext;
public GeneralRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _myDbContext.AddAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<TEntity[]> AddRangeAsync(TEntity[] entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddRangeAsync)} entity must not be null");
}
try
{
await _myDbContext.AddRangeAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<int> CompleteAsync()
{
return await _myDbContext.SaveChangesAsync();
}
public IQueryable<TEntity> GetAll()
{
try
{
return _myDbContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception($"Couldn't retrieve entities: {ex.Message}");
}
}
public TEntity Update(TEntity entity)
{
try
{
_myDbContext.Update<TEntity>(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be updated: {ex.Message}");
}
}
}
IUserService:
public interface IUserService
{
Task<User> CreateUser(string emailId);
Task<int> Complete();
}
UserService implementation:
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IAccessManagementRepository _accessManagementRepository;
public UserService(IUserRepository userRepository, IAccessManagementRepository accessManagementRepository)
{
_userRepository = userRepository;
_accessManagementRepository = accessManagementRepository;
}
public async Task<int> Complete()
{
return await _userRepository.CompleteAsync();
}
public async Task<User> CreateUser(string emailId)
{
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
if (user == null)
{
var entitlements = await _userAccessRepository.GetAll()
.Where( x => x.Default == true)
.Select( x => new UserEntitlement() {
Id = x.Id,
AccessName = x.AccessName
}).ToListAsync();
//saving User and Entitlement
user = new User()
{
EmailId = emailId,
UserEntitlements = entitlements
};
user = await _userRepository.AddAsync(user);
}
else
{
// Getting current User Entitlement
var entitlements = await _userRepository.GetAllUserEntitilements();
var entitlement = entitlements.Find(x => x.UserId == user.UserId);
user.UserEntitlements = entitlements;
}
return user;
}
}
API call:
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var result = await _userService.CreateUser(user.EmailId);
await _userService.Complete();
return CreatedAtAction(nameof(GetUser), new { emailId = result.EmailId }, result);
}
Questions:
Is my method UserService.CreateUser() implementation correct? Any better approach?
Is the below code is the best approach to filter?
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
How to get data from User and Entitlement table at one stretch? Something like below Include but can not use include because of an error
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.Include<UserEntitlement>()
.FirstOrDefault();
How to do insert to one table and update to another table in a single transaction?
Leo,
I prefer doing the validation of the email outside the CreateUser function
This comes with another function where you could add to IUserService where you can get the user by email GetUserByEmail.
Doing that you can possibly return a proper error or validation message before invoking the CreateUser at the API Call
For example
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var user = await _userService.GetUserByEmail(user.EmailId);
// or var userRegistered = await _userService.UserExistsByEmail(user.EmailId) returning a bool
// user registered?
if (user)
{
// The user already exists, return an error or
// You could update the UserEntitlements here or you could
// make an HttpPut where the user is updated do nothing here
}
....
}
An example
var user = _userRepository.GetAll()
.Include(x => x.UserEntitlements)
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
You can do it using UnitOfWork
Repository Pattern and Unit of Work
How can I access to AddOrUpdate() method when I using ASP.NET Core 2?
There is AddOrUpdate() in EntityFramework 6 in System.Data.Entity.Migrations namespace. But when I want to use this method in ASP.NET Core 2, I cannot find it.
This may be what you want
public static class DbSetExtension
{
/// <exception cref="ArgumentNullException"></exception>
public static TEntity FindEntity<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, bool noTracking = false) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
var dbContext = dbSet.GetService<ICurrentDbContext>().Context;
var entityEntry = dbContext.Entry(entity);
var entityType = entityEntry.Metadata;
var primaryKey = entityType.FindPrimaryKey();
if (primaryKey == null)
{
return (noTracking ? dbSet.AsNoTracking() : dbSet).FirstOrDefault(item => item.Equals(entity));
}
var ids = primaryKey.Properties.Select(item => item.PropertyInfo.GetValue(entity)).ToArray();
var result = dbSet.Find(ids);
if (noTracking && result != null)
{
dbContext.Entry(result).State = EntityState.Detached;
}
return result;
}
/// <exception cref="ArgumentNullException"></exception>
public static async ValueTask<TEntity> FindEntityAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, bool noTracking = false)
where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
var dbContext = dbSet.GetService<ICurrentDbContext>().Context;
var entityEntry = dbContext.Entry(entity);
var entityType = entityEntry.Metadata;
var primaryKey = entityType.FindPrimaryKey();
if (primaryKey == null)
{
return await (noTracking ? dbSet.AsNoTracking() : dbSet).FirstOrDefaultAsync(item => item.Equals(entity));
}
var ids = primaryKey.Properties.Select(item => item.PropertyInfo.GetValue(entity)).ToArray();
var result = await dbSet.FindAsync(ids);
if (noTracking && result != null)
{
dbContext.Entry(result).State = EntityState.Detached;
}
return result;
}
/// <exception cref="ArgumentNullException"></exception>
public static EntityEntry<TEntity> Update<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, bool? includeOrExclude = null,
params string[] propertyNames) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
var entityEntry = dbSet.Update(entity);
if (includeOrExclude != null)
{
foreach (var property in entityEntry.Properties)
{
if (includeOrExclude.Value ^ propertyNames?.Contains(property.Metadata.PropertyInfo.Name) == true)
{
property.IsModified = false;
}
}
}
return entityEntry;
}
/// <exception cref="ArgumentNullException"></exception>
public static EntityEntry<TEntity> AddOrUpdate<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, bool? includeOrExclude = null,
params string[] propertyNames) where TEntity : class
{
if (dbSet.FindEntity(entity, true) == null)
{
return dbSet.Add(entity);
}
return dbSet.Update(entity, includeOrExclude, propertyNames);
}
/// <exception cref="ArgumentNullException"></exception>
public static async ValueTask<EntityEntry<TEntity>> AddOrUpdateAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, bool? includeOrExclude = null,
params string[] propertyNames) where TEntity : class
{
if (await dbSet.FindEntityAsync(entity, true) == null)
{
return await dbSet.AddAsync(entity);
}
return dbSet.Update(entity, includeOrExclude, propertyNames);
}
}
I've omitted the comments for brevity.
Use like this
var people = new People {
Id=1,
Name="Tom",
ChangeTime=DateTimeOffset.Now
AddTime=DateTimeOffset.Now,
};
await dbContext.AddOrUpdateAsync(people, false, nameof(people.AddTime));
await dbContext.SaveChangesAsync();
or
await dbContext.AddOrUpdateAsync(people, true, nameof(people.Name), nameof(people.ChangeTime));
await dbContext.SaveChangesAsync();
You can also use its synchronization method.
I was in the "Microsoft. EntityFrameworkCore 2.1.1" and "Microsoft. EntityFrameworkCore 3.1.7" test normal.
You may need more: Saving an explicit value during add.
If the entity uses auto-generated key values. see also: Saving single entities.
If there is any omission, please let me know. Thank you.
IRepository.cs
public interface ICommonRepository<T>
{
Task<int> CountAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>,IOrderedQueryable<T>> orderBy = null,List<Expression<Func<T, object>>> includes = null);
}
Repository.cs:
public class Repository<T> : IRepository<T> where T : class, new()
{
protected readonly MyDbContext _context;
protected readonly ILogger<Repository<T>> _logger;
protected readonly DbSet<T> _dbSet;
public CommomRepository(MyDbContext context, ILogger<Repository<T>> logger)
{
_context = context;
_logger = logger;
if (_context != null)
{
_dbSet = _context.Set<T>();
}
else
{
}
}
internal IQueryable<T> _Select(Expression<Func<T, bool>> filter = null
, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null
, List<Expression<Func<T, object>>> includes = null
, int? pageIndex = null
, int? pageSize = null)
{
IQueryable<T> query = _dbSet;
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
if (orderBy != null)
{
query = orderBy(query);
}
if (filter != null)
{
query = query.Where(filter);
}
if (pageIndex != null && pageSize != null)
{
query = query.Skip((pageIndex.Value - 1) * pageSize.Value).Take(pageSize.Value);
}
return query;
}
public async Task<int> CountAsync(Expression<Func<T, bool>> filter = null
, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null
, List<Expression<Func<T, object>>> includes = null)
{
var query = _Select(filter, orderBy, includes);
return await query.CountAsync();
}
}
Usage (controller):
var singleCheckTask = _Repo.CountAsync(x=> x.id== item.id);
var nameCheckTask = _Repo.CountAsync(x=> x.name== item.name);
var ipCheckTask = _Repo.CountAsync(x=> x.ip == item.ip);
await Task.WhenAll(singleCheckTask, nameCheckTask, ipCheckTask);
And exception thowed:
Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryCompilationContextFactory|ERROR|An exception occurred in the database while iterating the results of a query.
System.InvalidOperationException: Can not start another operation while there is an asynchronous operation pending.
I'vs tested that if I do not use Task.whenAll, var testSingleCheck = _Repo.CountAsync(x=> x.id== item.id).Result; This would be all right.
It's simple, you can't run queries in parallel with EF (neither EF6 nor EF Core).
One reasons for is, that EF isn't thread-safe.
EF 6 on Task-based pattern
Thread Safety
While thread safety would make async more useful it is an orthogonal feature. It is unclear that we could ever implement support for it in the most general case, given that EF interacts with a graph composed of user code to maintain state and there aren't easy ways to ensure that this code is also thread safe.
For the moment, EF will detect if the developer attempts to execute two async operations at one time and throw.
I have the below code snippet for dynamic sorting using JPA Criteria API
Root<Employee> root = criteriaQuery.from(Employee);
Join<Employee, Project> joinProject =
root.join(Employee_.projectList, JoinType.LEFT);
if (sortDirection.equals("asc")) {
criteriaQuery.orderBy(cb.asc(root.get(sortField)));
If I am passing an attribute of Employee entity to order by statement, it works without any hitch, however if an attribute of Project entity is passed to order by statement, exception is thrown stating that
The attribute [projectName] is not present in the managed type
because projectName is an attribute of Projectentity which is joined with Employee using joinProject. In order by statement I am using root.get(sortField). if it is joinProject.get(sortField), it would work fine when attributes of Project are being passed to order by statement.
My questions are
How could I modify my Order By statement in order to cater all the attributes which being passed?
Do I need to conditionally check which attribute and accordingly use if conditions or are there better ways of doing this?
Appreciate insight into this.
A specific approach for a simple scenario (predetermined one-level only joins) may be something like this:
Root<Employee> root = criteriaQuery.from(Employee.class);
Join<Employee, Project> joinProject = root.join(Employee_.projectList, JoinType.LEFT);
Class<?> baseClass = fieldTypeMap.get(sortField);
From<?, ?> from;
if(baseClass == Employee.class)
{
from = root;
}
else if(baseClass == Project.class)
{
from = joinTeam;
}
else ...
Expression<?> expr = from.get(sortField);
if(sortDirection.equals("asc"))
{
criteriaQuery.orderBy(cb.asc(expr));
}
...
where fieldTypeMap is something like:
private final static Map<String, Class<?>> fieldTypeMap = new HashMap<>();
static {
fieldTypeMap.put("employeeName", Employee.class);
fieldTypeMap.put("projectName", Project.class);
...
}
However, this is quick and dirty, ugly and unmaintainable.
If you want a generic approach, things may get a bit complex.
Personally, I'm using my own classes built on top of EntityManager, CriteriaBuilder and Metamodel, which provides dynamic filtering and multi-sorting features.
But something like this should be meaningful enough:
protected static List<Order> buildOrderBy(CriteriaBuilder builder, Root<?> root, List<SortMeta> sortList)
{
List<Order> orderList = new LinkedList<>();
for(SortMeta sortMeta : sortList)
{
String sortField = sortMeta.getSortField();
SortOrder sortOrder = sortMeta.getSortOrder();
if(sortField == null || sortField.isEmpty() || sortOrder == null)
{
continue;
}
Expression<?> expr = getExpression(root, sortField);
if(sortOrder == SortOrder.ASCENDING)
{
orderList.add(builder.asc(expr));
}
else if(sortOrder == SortOrder.DESCENDING)
{
orderList.add(builder.desc(expr));
}
}
return orderList;
}
protected static Expression<?> getExpression(Root<?> root, String sortField)
{
ManagedType<?> managedType = root.getModel();
From<?, Object> from = (From<?, Object>) root;
String[] elements = sortField.split("\\.");
for(String element : elements)
{
Attribute<?, ?> attribute = managedType.getAttribute(element);
if(attribute.getPersistentAttributeType() == PersistentAttributeType.BASIC)
{
return from.get(element);
}
from = from.join(element, JoinType.LEFT);
managedType = EntityUtils.getManagedType(from.getJavaType());
}
return from;
}
This way the join is based on sort field, which is now a dotted-path-expr like "projectList.name" or "office.responsible.age"
public static <X> ManagedType<X> getManagedType(Class<X> clazz)
{
try
{
return getMetamodel().managedType(clazz);
}
catch(IllegalArgumentException e)
{
return null;
}
}
public static Metamodel getMetamodel()
{
return getEntityManagerFactory().getMetamodel();
}
public static EntityManagerFactory getEntityManagerFactory()
{
try
{
return InitialContext.doLookup("java:module/persistence/EntityManagerFactory");
}
catch(NamingException e)
{
throw new RuntimeException(e.getMessage(), e);
}
}
to make it work on a webapp, you have to declare contextual references on web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>my_app_name</display-name>
...
<persistence-context-ref>
<persistence-context-ref-name>java:module/persistence/EntityManager</persistence-context-ref-name>
<persistence-unit-name>my_pu_name</persistence-unit-name>
</persistence-context-ref>
<persistence-unit-ref>
<persistence-unit-ref-name>java:module/persistence/EntityManagerFactory</persistence-unit-ref-name>
<persistence-unit-name>my_pu_name</persistence-unit-name>
</persistence-unit-ref>
</web-app>
update
I don't know about how EclipseLink handles grouping, but also Hibernate does not perform joins (actually, conditions on joins) correctly in some case.
In example, when all these conditions are met:
querying an entity (as Root/From) that is part of a class hierarchy (and not a leaf) based on SINGLE_TABLE
joining/getting a property of a subclass
joining/getting by property name (String) instead of Attribute
To workaround the issue I always resolve property name to an Attribute and reuse joins already "walked" (did I say things may get complicated?):
public class MetaDescriptor extends BusinessObject implements Serializable, MemberEx, ColumnDescriptor
{
private static final long serialVersionUID = 1L;
#BusinessKey
protected final Attribute<?, ?> attribute;
#BusinessKey
protected final MetaDescriptor parent;
protected List<MetaDescriptor> childList;
protected final Type<?> elementType;
...
protected MetaDescriptor(Attribute<?, ?> attribute, MetaDescriptor parent)
{
this.attribute = attribute;
this.parent = parent;
if(attribute instanceof SingularAttribute)
{
SingularAttribute<?, ?> singularAttribute = (SingularAttribute<?, ?>) attribute;
elementType = singularAttribute.getType();
}
else if(attribute instanceof PluralAttribute)
{
PluralAttribute<?, ?, ?> pluralAttribute = (PluralAttribute<?, ?, ?>) attribute;
elementType = pluralAttribute.getElementType();
}
else
{
elementType = null;
}
}
public static MetaDescriptor getDescriptor(ManagedType<?> managedType, String path)
{
return getDescriptor(managedType, path, null);
}
public static MetaDescriptor getDescriptor(From<?, ?> from, String path)
{
if(from instanceof Root)
{
return getDescriptor(((Root<?>) from).getModel(), path);
}
return getDescriptor(from.getJavaType(), path);
}
public static MetaDescriptor getDescriptor(Class<?> clazz, String path)
{
ManagedType<?> managedType = EntityUtils.getManagedType(clazz);
if(managedType == null)
{
return null;
}
return getDescriptor(managedType, path);
}
private static MetaDescriptor getDescriptor(ManagedType<?> managedType, String path, MetaDescriptor parent)
{
if(path == null)
{
return null;
}
Entry<String, String> slice = StringUtilsEx.sliceBefore(path, '.');
String attributeName = slice.getKey();
Attribute<?, ?> attribute;
if("class".equals(attributeName))
{
attribute = new ClassAttribute<>(managedType);
}
else
{
try
{
attribute = managedType.getAttribute(attributeName);
}
catch(IllegalArgumentException e)
{
Class<?> managedClass = managedType.getJavaType();
// take only if it is unique
attribute = StreamEx.of(EntityUtils.getMetamodel().getManagedTypes())
.filter(x -> managedClass.isAssignableFrom(x.getJavaType()))
.flatCollection(ManagedType::getDeclaredAttributes)
.filterBy(Attribute::getName, attributeName)
.limit(2)
.collect(Collectors.reducing((a, b) -> null))
.orElse(null);
if(attribute == null)
{
return null;
}
}
}
MetaDescriptor descriptor = new MetaDescriptor(attribute, parent);
String remainingPath = slice.getValue();
if(remainingPath.isEmpty())
{
return descriptor;
}
Type<?> elementType = descriptor.getElementType();
if(elementType instanceof ManagedType)
{
return getDescriptor((ManagedType<?>) elementType, remainingPath, descriptor);
}
throw new IllegalArgumentException();
}
#Override
public <T> Expression<T> getExpression(CriteriaBuilder builder, From<?, ?> from)
{
From<?, Object> parentFrom = getParentFrom(from);
if(attribute instanceof ClassAttribute)
{
return (Expression<T>) parentFrom.type();
}
if(isSingular())
{
return parentFrom.get((SingularAttribute<Object, T>) attribute);
}
return getJoin(parentFrom, JoinType.LEFT);
}
private <X, T> From<X, T> getParentFrom(From<?, ?> from)
{
return OptionalEx.of(parent)
.map(x -> x.getJoin(from, JoinType.LEFT))
.select(From.class)
.orElse(from);
}
public <X, T> Join<X, T> getJoin(From<?, ?> from, JoinType joinType)
{
From<?, X> parentFrom = getParentFrom(from);
Join<X, T> join = (Join<X, T>) StreamEx.of(parentFrom.getJoins())
.findAny(x -> Objects.equals(x.getAttribute(), attribute))
.orElseGet(() -> buildJoin(parentFrom, joinType));
return join;
}
private <X, T> Join<X, T> buildJoin(From<?, X> from, JoinType joinType)
{
if(isSingular())
{
return from.join((SingularAttribute<X, T>) attribute, joinType);
}
if(isMap())
{
return from.join((MapAttribute<X, ?, T>) attribute, joinType);
}
if(isSet())
{
return from.join((SetAttribute<X, T>) attribute, joinType);
}
if(isList())
{
return from.join((ListAttribute<X, T>) attribute, joinType);
}
if(isCollection())
{
return from.join((CollectionAttribute<X, T>) attribute, joinType);
}
throw new ImpossibleException();
}
public Order buildOrder(CriteriaBuilderEx builder, From<?, ?> from, SortOrder direction)
{
if(direction == null)
{
return null;
}
Expression<?> expr = getExpression(builder, from);
return direction == SortOrder.ASCENDING ? builder.asc(expr) : builder.desc(expr);
}
}
with this construct, I can now safely:
public static List<Order> buildOrderList(CriteriaBuilderEx builder, From<?, ? extends Object> from, List<SortMeta> list)
{
return StreamEx.of(list)
.nonNull()
.map(x -> buildOrder(builder, from, x.getSortField(), x.getSortOrder()))
.nonNull()
.toList();
}
public static Order buildOrder(CriteriaBuilderEx builder, From<?, ? extends Object> from, String path, SortOrder direction)
{
if(path == null || path.isEmpty() || direction == null)
{
return null;
}
MetaDescriptor descriptor = MetaDescriptor.getDescriptor(from, path);
if(descriptor == null)
{
return null;
}
return descriptor.buildOrder(builder, from, direction);
}
If sortField exists only in one entity:
try{
path = root.get(sortField);
}catch (IllegalArgumentException e){
path = joinProject.get(sortField);
}
criteriaQuery.orderBy(cb.asc(path));
I've recently discovered AutoMapper for bridging ViewModels and my actual DB objects. I use it in the way decribed here: http://automapper.codeplex.com/wikipage?title=Projection&referringTitle=Home
I've discovered Emit Mapper to :), but I can't find anytning similar to (where I can specify custom projecting rules):
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
Thanks in advance!
For the Record this is the best solution that I came across on how to do it:
http://emitmapper.codeplex.com/discussions/259655
Check the solution on the last post. It works really well.
Update: The code for future reference:
public class ExtDefaultMapConfig<TSrc, TDst> : DefaultMapConfig
{
private readonly Dictionary<string, Func<TSrc, object>> _properties = new Dictionary<string, Func<TSrc, object>>();
public ExtDefaultMapConfig<TSrc, TDst> ForMember(string property, Func<TSrc, object> func)
{
if (!_properties.ContainsKey(property))
_properties.Add(property, func);
return this;
}
public ExtDefaultMapConfig<TSrc, TDst> ForMember(Expression<Func<TDst, object>> dstMember, Func<TSrc, object> func)
{
var prop = ReflectionHelper.FindProperty(dstMember);
return ForMember(prop.Name, func);
}
public ExtDefaultMapConfig<TSrc, TDst> Ignore(Expression<Func<TDst, object>> dstMember)
{
var prop = ReflectionHelper.FindProperty(dstMember);
IgnoreMembers<TSrc, TDst>(new[] { prop.Name });
return this;
}
public override IMappingOperation[] GetMappingOperations(Type from, Type to)
{
var list = new List<IMappingOperation>();
list.AddRange(base.GetMappingOperations(from, to));
list.AddRange(
FilterOperations(
from,
to,
ReflectionUtils.GetPublicFieldsAndProperties(to)
.Where(f => _properties.ContainsKey(f.Name))
.Select(
m =>
(IMappingOperation)new DestWriteOperation
{
Destination = new MemberDescriptor(m),
Getter =
(ValueGetter<object>)
(
(value, state) =>
{
Debug.WriteLine(string.Format("Mapper: getting value of field or property {0}", m.Name));
return ValueToWrite<object>.ReturnValue(_properties[m.Name]((TSrc) value));
}
)
}
)
)
);
return list.ToArray();
}
}
class ReflectionHelper
{
public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
{
Expression expression = lambdaExpression;
bool flag = false;
while (!flag)
{
switch (expression.NodeType)
{
case ExpressionType.Convert:
expression = ((UnaryExpression)expression).Operand;
break;
case ExpressionType.Lambda:
expression = ((LambdaExpression)expression).Body;
break;
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)expression;
if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert)
throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), "lambdaExpression");
return memberExpression.Member;
default:
flag = true;
break;
}
}
return null;
}
public static object GetValue(string property, object obj)
{
PropertyInfo pi = obj.GetType().GetProperty(property);
return pi.GetValue(obj, null);
}
}