I build a Repository just like Codefirst demo.
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
But this search has a problem.
Sometimes other person use 'get' function to search value and this value will be used as param inside other function.
Just like this:
UserInfoBll userInfoBll = new UserInfoBll();
UserInfo userInfo = userInfoBll.Get(p => p.UserCode == "8001", null, "CorpShop").First();
ConsumeProcess.process(userInfo);
If userInfo value change in function ConsumeProcess.process when I process savechange.
It will update something I don't want to update, so I want to find a way. The search value is just for search, when value change it, not to update the value.
For this way I wrote this:
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
List<TEntity> tEntityList = Get(filter, orderBy, includeProperties).ToList();
SetEntityStates(EntityState.Detached, tEntityList);
return tEntityList;
}
protected void SetEntityStates(EntityState entityState, IEnumerable<TEntity> tEntity_params)
{
foreach (TEntity tEntity in tEntity_params)
{
if (tEntity != null)
{
context.Entry(tEntity).State = entityState;
}
}
}
Now it won't update, if somebody changed search value. But there is another problem.
If I use code like this, the property which is included can't get
UserInfoBll userInfoBll = new UserInfoBll();
userInfo = userInfoBll.GetList(p => p.UserCode == "8001", null, "CorpShop").First(); // CorpShop is include property
corpShop = userInfo.CorpShop; //userInfo.CorpShop is null
If you have an IQueryable, the easiest way to get entities disconnected from db is to use the AsNoTracking method.
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
return = Get(filter, orderBy, includeProperties).AsNoTracking().ToList();
}
Related
This is my sample model,
Name
Age
I have a list of records for which I will use Entity Framework Core AddRange. This is my sample data, say list 1
John, 21
Mike, 18
Rick, 19
Alex, 20
I have another query which is a list of the existing records, say list 2.
John, 21
Alex, 20
I want to compare these 2 lists to get what exists in list 1 but not in list 2.
Mike, 18
Rick, 19
I tried using LINQ except but it can't be used for complex types. I also tried to use .Any like below but it returns nothing.
var result = list1.Where(x=> !list2.Any(y=>x.Name == y.Name));
I do not want to check and insert one record at one time. I want to insert by list of objects using AddRange.
Is there any Nuget package that can easily get this?
For not so big amount of records, you can use the following extension:
var nonExistent = await ctx.Some.GetNonExistentAsync(list2, x => x.Name);
Or with complex key:
var nonExistent = await ctx.Some.GetNonExistentAsync(list2,
x => new { x.Name, x.Other });
It will generate effective SQL to check which records do not exist.
And implementation:
public static class QueryableExtensions
{
public static async Task<List<T>> GetNonExistentAsync<T, TKey>(this IQueryable<T> query, IEnumerable<T> records,
Expression<Func<T, TKey>> keySelector, CancellationToken cancellationToken = default)
{
var recordsList = records.ToList();
var keySelectorCompiled = keySelector.Compile();
var predicate = BuildPredicate(recordsList, keySelector, keySelectorCompiled);
var existent = (await query
.Where(predicate)
.Select(keySelector)
.ToListAsync(cancellationToken))
.ToHashSet();
var result = recordsList.Where(r => !existent.Contains(keySelectorCompiled(r))).ToList();
return result;
}
private static Expression<Func<T, bool>> BuildPredicate<T, TKey>(IEnumerable<T> records, Expression<Func<T, TKey>> keySelector, Func<T, TKey> keySelectorCompiled)
{
var members = CollectMembers(keySelector.Body).ToList();
if (members.Count == 0)
throw new InvalidOperationException("No key found");
Expression? predicate = null;
if (members.Count == 1 && keySelector.Body.NodeType == ExpressionType.MemberAccess)
{
// we can use Contains
var recordsSequence = Expression.Constant(records.Select(keySelectorCompiled));
predicate = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { typeof(TKey) },
recordsSequence, keySelector.Body);
}
else
{
foreach (var record in records)
{
Expression? subPredicate = null;
var recordExpression = Expression.Constant(record);
foreach (var m in members)
{
var memberExpression =
ReplacingExpressionVisitor.Replace(keySelector.Parameters[0], recordExpression, m);
var equality = Expression.Equal(m, memberExpression);
subPredicate = subPredicate == null ? equality : Expression.AndAlso(subPredicate, equality);
}
if (subPredicate == null)
throw new InvalidOperationException(); // should never happen
predicate = predicate == null ? subPredicate : Expression.OrElse(predicate, subPredicate);
}
}
predicate ??= Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(predicate, keySelector.Parameters);
}
private static IEnumerable<Expression> CollectMembers(Expression expr)
{
switch (expr.NodeType)
{
case ExpressionType.New:
{
var ne = (NewExpression)expr;
foreach (var a in ne.Arguments)
foreach (var m in CollectMembers(a))
{
yield return m;
}
break;
}
case ExpressionType.MemberAccess:
yield return expr;
break;
default:
throw new InvalidOperationException();
}
}
}
I need to generate a query dynamically based on parameter passed and need to join three tables, getting below exception while building query with EntityManager CriteriaBuilder, same code structure is working if I convert it to Criteria but I want use CriteriaBuilder only.
#Override
#Transactional
public List<DatapointReply> getAllByFilter(Map<String, List<Object>> filter, int maxResults,
boolean matchAllFilters) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<DatapointReply> criteriaQuery = criteriaBuilder.createQuery(DatapointReply.class);
Root<PartDatapointReply> datapointReplyRoot = criteriaQuery.from(PartDatapointReply.class);
Join<DatapointReply, Datapoint> datapointJoin = null;
Join<PartDatapointReply, Part> partJoin = null;
criteriaQuery.select(datapointReplyRoot);
if (filter.containsKey("datapointkey")) {
datapointJoin = datapointReplyRoot.join("datapoint");
}
if (filter.containsKey("partstatus") || filter.containsKey("partmodelnumber")) {
partJoin = datapointReplyRoot.join("part");
}
List<Predicate> predicateList = new ArrayList<>();
for (String searchKey : filter.keySet()) {
List<Object> searchTerms = filter.get(searchKey);
Predicate predicate = buildSearchCriterion(new String[searchTerms.size()], true, JunctionType.OR, datapointReplyRoot, criteriaBuilder,
"value");
if (predicate != null) {
predicateList.add(predicate);
}
}
Predicate[] predicateArray = predicateList.toArray(new Predicate[predicateList.size()]);
if (!predicateList.isEmpty() && matchAllFilters) {
criteriaBuilder.and(predicateArray);
} else if (!predicateList.isEmpty() && !matchAllFilters) {
criteriaBuilder.or(predicateArray);
}
TypedQuery<DatapointReply> query = entityManager.createQuery(criteriaQuery);
// Define the Max Results
if (maxResults > SearchEngine.SEARCH_MAX_RESULTS_ALL) {
query.setMaxResults(maxResults);
}
return query.getResultList();
}
// Get Predicate based on parameter
private Predicate buildSearchCriterion(String[] values, boolean isWildcardSearch, JunctionType criteriaType,
Root<PartDatapointReply> datapointReplyRoot, CriteriaBuilder criteriaBuilder, String... attributeNames) {
// Build the Search Criteria as Single or Multiple Column Search
Predicate finalCriterion = null;
if (!isWildcardSearch) {
List<Predicate> criteria = new ArrayList<>();
for (String attributeName : attributeNames) {
Predicate attributeCriterion = criteriaBuilder.in(datapointReplyRoot.get(attributeName)).value(values);
criteria.add(attributeCriterion);
}
if (criteriaType == null || criteriaType == JunctionType.OR) {
finalCriterion = criteriaBuilder.or(criteria.toArray(new Predicate[criteria.size()]));
} else {
finalCriterion = criteriaBuilder.and(criteria.toArray(new Predicate[criteria.size()]));
}
} else if (isWildcardSearch) {
// Wildcard OR search on a single attribute
// Search through each Search Term, ignoring white space, and add
// each term to the Criteria Restrictions.
List<Predicate> criteria = new ArrayList<>();
for (String attributeName : attributeNames) {
List<Predicate> attributeCriteria = new ArrayList<>();
Predicate attributeCriterion;
for (String value : values) {
Predicate valueCriterion;
// Wrap the Value in Wild Cards if it is a Wild Card Search
if (isWildcardSearch) {
value = "%" + value + "%";
}
valueCriterion = criteriaBuilder.like(criteriaBuilder.lower(datapointReplyRoot.get(attributeName)), value.toLowerCase());
attributeCriteria.add(valueCriterion);
}
attributeCriterion = criteriaBuilder.or(attributeCriteria.toArray(new Predicate[attributeCriteria.size()]));
criteria.add(attributeCriterion);
}
if (criteriaType == null || criteriaType == JunctionType.OR) {
finalCriterion = criteriaBuilder.or(criteria.toArray(new Predicate[criteria.size()]));
} else {
finalCriterion = criteriaBuilder.and(criteria.toArray(new Predicate[criteria.size()]));
}
}
return finalCriterion;
}
Getting below error message:
java.lang.IllegalArgumentException: Encountered array-valued parameter binding, but was expecting [java.lang.String (n/a)]
at org.hibernate.query.spi.QueryParameterBindingValidator.validateArrayValuedParameterBinding(QueryParameterBindingValidator.java:142) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:49) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at ```
Changing type of values from String[] to Arrays.asList(values) in below lines will fix the problem.
Predicate attributeCriterion = criteriaBuilder.in(datapointReplyRoot.get(attributeName)).value(Arrays.asList(values));
I have the following method to update a document in MongoDB:
public async Task UpdateAsync(T entity)
{
await _collection.ReplaceOneAsync(filter => filter.Id == entity.Id, entity);
}
Which works fine - I was just wondering if anybody has an example of how the UpdateManyAsync function works:
public async Task UpdateManyAsync(IEnumerable<T> entities)
{
await _collection.UpdateManyAsync(); // What are the parameters here
}
Any advice is appreciated!
UpdateManyAsync works the same way as update with multi: true in Mongo shell. So you can specify filtering condition and update operation and it will affect multiple documents. For instance to increment all a fields where a is greater than 10 you can use this method:
var builder = Builders<SampleClass>.Update;
await myCollection.UpdateManyAsync(x => x.a > 10, builder.Inc(x => x.a, 1));
I guess you'd like to replace multiple documents. That can be achieved using bulkWrite method. If you need generic method in C# then you can introduce some kind of marker interface to build filter part of replace operation:
public interface IMongoIdentity
{
ObjectId Id { get; set; }
}
Then you can add generic constaint to your class and use BuikWrite in .NET like below:
class YourRepository<T> where T : IMongoIdentity
{
IMongoCollection<T> collection;
public async Task UpdateManyAsync(IEnumerable<T> entities)
{
var updates = new List<WriteModel<T>>();
var filterBuilder = Builders<T>.Filter;
foreach (var doc in entities)
{
var filter = filterBuilder.Where(x => x.Id == doc.Id);
updates.Add(new ReplaceOneModel<T>(filter, doc));
}
await collection.BulkWriteAsync(updates);
}
}
As #mickl answer, you can not use x=> x.Id because it is a Generic
Use as below:
public async Task<string> UpdateManyAsync(IEnumerable<T> entities)
{
var updates = new List<WriteModel<T>>();
var filterBuilder = Builders<T>.Filter;
foreach (var doc in entities)
{
foreach (PropertyInfo prop in typeof(T).GetProperties())
{
if (prop.Name == "Id")
{
var filter = filterBuilder.Eq(prop.Name, prop.GetValue(doc));
updates.Add(new ReplaceOneModel<T>(filter, doc));
break;
}
}
}
BulkWriteResult result = await _collection.BulkWriteAsync(updates);
return result.ModifiedCount.ToString();
}
Or you go by Bson attribute:
public async Task UpdateManyAsync(IEnumerable<TEntity> objs, CancellationToken cancellationToken = default)
{
var updates = new List<WriteModel<TEntity>>();
var filterBuilder = Builders<TEntity>.Filter;
foreach (var obj in objs)
{
foreach (var prop in typeof(TEntity).GetProperties())
{
object[] attrs = prop.GetCustomAttributes(true);
foreach (object attr in attrs)
{
var bsonId = attr as BsonIdAttribute;
if (bsonId != null)
{
var filter = filterBuilder.Eq(prop.Name, prop.GetValue(obj));
updates.Add(new ReplaceOneModel<TEntity>(filter, obj));
break;
}
}
}
}
await _dbCollection.BulkWriteAsync(updates, null, cancellationToken);
}
I at this moment I have repository filled with multiple gets methods.
E.q. Get1() => cxt.Entites.Include(e => e.obj1);
Get2() => cxt.Entities.Include(e => e.obj1).Include(e => e.obj2)
And so on.
Is there good method, pattern to have one GET method where I can send inclues via parameter?
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
See repository pattern in msdn
You can use
_sampleRepostiory.Get(h=>h.Id>1,null,"Employees.Departments");
Including same with lambda
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includes != null)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
Consume it like this
var query = context.Customers
.Get(x=>x.Id>1,null,
c => c.Address,
c => c.Orders.Select(o => o.OrderItems));
Similar SO question
I did the following in my projects:
public Entity[] GetAll(bool includeObj1, bool includeAllOthers) {
IQueryable<Entity> entity = ctx.Entities;
if (includeObj1)
entity = entity.Include(e => e.obj1);
if (includeAllOthers) {
entity = entity
.Include(e => e.obj2)
.Include(e => e.obj3)
.Include(e => e.obj4)
.Include(e => e.obj5);
}
return entity.ToArray();
}
Providing arguments like includeObj1 and includeObj2 separates a consumer of repository from implementation and encapsulates any data access logic.
Passing direct "include these properties" orders to a repository means that you know how repository works and assume that it is some sort ORM which blurs abstractions.
I have an extension method for ordering, the sortExpression can be something like "Description" or "Description DESC", it is working perfectly for columns at the same table:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
{
if (source == null)
throw new ArgumentNullException("source");
if (string.IsNullOrEmpty(sortExpression))
return source;
var parts = sortExpression.Split(' ');
var isDescending = false;
var propertyName = "";
var type = typeof(T);
if (parts.Length > 0 && parts[0] != "")
{
propertyName = parts[0];
if (parts.Length > 1)
isDescending = parts[1].ToLower().Contains("esc");
var prop = type.GetProperty(propertyName);
if (prop == null)
throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, type.Name));
var funcType = typeof(Func<,>)
.MakeGenericType(type, prop.PropertyType);
var lambdaBuilder = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
.MakeGenericMethod(funcType);
var parameter = Expression.Parameter(type);
var propExpress = Expression.Property(parameter, prop);
var sortLambda = lambdaBuilder
.Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });
var sorter = typeof(Queryable)
.GetMethods()
.FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { type, prop.PropertyType });
var result = (IQueryable<T>)sorter
.Invoke(null, new object[] { source, sortLambda });
return result;
}
return source;
}
Working Example:
var query = db.Audit.Include("AccessLevel").AsQueryable();
query = query.OrderBy("Description");
Please note that the "Description" column exists at the same table "Audit".
What I'm trying to do is to sort by a column in a relation table:
Like the following
var query = db.Audit.Include("AccessLevel").AsQueryable();
query = query.OrderBy("AccessLevel.Name");
Which is equivalent to:
query = query.OrderBy(o => o.AccessLevel.Name);
What is the required modification on my extension method ?
I solved it by using the following code:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, parts[0], "OrderBy");
}
public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
if (source == null)
throw new ArgumentNullException("source");
if (string.IsNullOrEmpty(property))
return source;
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IQueryable<T>)result;
}