Interpret lambda expression for custom selection - c#

I'll need to expose a bit of background before going further: I have a project that uses nHibernate and some generic repository to forward predicates and return entities as follows:
public abstract class GenericRepository< T >
{
...
public virtual T Single( Expression< Func< T, bool > > predicates )
{
// Get object from nHibernate session object
var retObj = Session
.Query< T >()
.Where( predicates ).SingleOrDefault();
return retObj;
}
...
}
I can then for example get an entity this way:
var entity = Context.MyGenericEntityRepository.Single( e => e.Id == id );
// or
var entity = Context.MyGenericEntityRepository.Single( e => e.Name == name );
But, I also have some entities that because of the nature of the project aren't saved to the database but to the file system (bunch of files). So I use a derived repository that uses some sort of DataAccess class to get the entities from the file system as follows:
public class NotGenericRepository
{
...
// for example
public IList<Entity> All()
{
return _entityDataAccess.All();
}
...
}
As said, the 2nd type of entity isn't stored in the database, however, to facilitate my journey I created a sort of in-memory database system using DataSets and DataTables.
So when the solution is first started, I have a singleton called CustomDatabase that gets initialised, creates the DataTables in memory, adds relations between the DataTalbes, and adds them to a general DataSet before scanning the file system to populate the tables.
Using this, I can now query my DataTables instead of scanning through the file system tree every time. I then set up some events in my CustomDatabase so whenever a row is added/deleted/updated, the changes are reflected onto the file system.
So... This is it for the background, sorry for the length...
My question is now fairly simple, I'm looking for a way to somehow translate the lambda expression forwarded by the repository to my DataAccess class, so I can then analyse it and select from my DataTables accordingly...
For example, a call to the repository such as:
var entity = Context.MyNotGenericEntityRepository.Single( e => e.Id == id );
// or
var entity = Context.MyNotGenericEntityRepository.Single( e => e.Name == name );
Should be translated within the DataAccess as:
DataRow entityRow = CustomDatabase.Tables[ "EntityName" ].AsEnumerable().Where( e => e.Field< Guid >( "Id" ) == id);
// or
DataRow entityRow = CustomDatabase.Tables[ "EntityName" ].AsEnumerable().Where( e => e.Field< string >( "Name" ) == name);
Or:
var entity = from myRow in CustomDatabase.Tables[ "EntityName" ].AsEnumerable()
where myRow.Field<Guid>( "Id" ) == id
select myRow;
// or
var entity = from myRow in CustomDatabase.Tables[ "EntityName" ].AsEnumerable()
where myRow.Field<string>( "Name" ) == name
select myRow;
I have absolutely no idea how to do this, I've been looking all over the net, but the problem is that I don't really know how to name this problem so I haven't found much so far... :(
Any help is appreciated since I'm expecting there'll be several ways to tackle this problem :)
Thanks!!

It's difficult to tell what's been skipped in the derived repository class example, but it could be as simple as overriding (or overloading) Single to return All().Single(..).

Assuming your predicate expressions are of the form in your question, you can convert the expression to one for a DataRow, and then use that to filter the table corresponding to the entity:
public static DataRow GetSingleRow<T>(Expression<Func<T, bool>> expr)
{
var bodyExpr = ((LambdaExpression)expr).Body;
var binExpr = (BinaryExpression)bodyExpr;
var propExpr = (MemberExpression)binExpr.Left;
PropertyInfo targetProperty = (PropertyInfo)propExpr.Member;
Type targetType = targetProperty.DeclaringType;
var valueExpr = binExpr.Right;
string entityName = targetType.Name;
var fieldMethod = typeof(DataRowExtensions).GetMethod("Field", new[] { typeof(DataRow), typeof(string) });
var methodInfo = fieldMethod.MakeGenericMethod(targetProperty.PropertyType);
var propNameExpr = Expression.Constant(targetProperty.Name);
var rowParamExpr = Expression.Parameter(typeof(DataRow), "dr");
var fieldInvocationExpr = Expression.Call(methodInfo, rowParamExpr, propNameExpr);
var eqExpr = Expression.Equal(fieldInvocationExpr, valueExpr);
Func<DataRow, bool> predicate = Expression.Lambda<Func<DataRow, bool>>(eqExpr, rowParamExpr).Compile();
return CustomDatabase.Tables[entityName].AsEnumerable().Single(predicate);
}

Related

Entity Framework Core Dynamic Where Clause

I have been searching and I have not seen an example like this.
I want to query a Model table to retrieve all Models from German manufacturers.
My data structure is Model Table has a Navigation Property called "Manufacturer" and an FK called "IdManufacturer"
A Manufacturer Has a Navigation Property called "Origin" and an FK called IdOrigin.
I would like to have a query that includes a where clause something like this:
(m => m.Manufacturer.Origin.Name == "German");
I would like this to be dynamically created at run time. Not only the lookup value might change, but the next time even the fields may change to, for example:
(m.Type.Name == "SUV");
Finally, there will not necessarily be a UI associated with this request, it may be generated in code.
Please don't get too hung up on the business of it, I am hoping this made up example will be simple to understand.
Any suggestions on how to handle this would be greatly appreciated. Any general thoughts on performance would be created.
My solution:
I have a model RouteList, in this model I created Expression:
public static Expression<Func<RouteList, bool>> FilterByDateOrShiftCode(RoutesFilterViewModel filter)
{
Expression<Func<RouteList, bool>> result = rl => true;
if (string.IsNullOrEmpty(filter.ShiftCode))
{
result = rl => rl.RouteListDate.Date >= filter.From.Date && rl.RouteListDate.Date <= filter.To.Date && rl.User.Id == filter.User.Id;
}
else
{
result = rl => rl.ShiftCode == filter.ShiftCode && rl.User.Id == filter.User.Id;
}
Place where I use it:
model.RouteLists = await _context.RouteLists
.Include(rl => rl.Status)
.Include(rl => rl.User)
.Include(rl => rl.RouteListRows).ThenInclude(rlr => rlr.Address)
.Where(RouteList.FilterByDateOrShiftCode(filter))
.ToListAsync();
Speed of executing grow in several times.
Try the following simple extension method. Note that this realization will work only with sting properties.
Usage:
query = query.Where("Manufacturer.Origin.Name", "German");
Realization:
public static class QueryableExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> query, string propPath, string value)
{
var param = Expression.Parameter(typeof(T), "e");
// e.Prop1.Prop2 == value
var body = Expression.Equal(MakePropPath(param, propPath), Expression.Constant(value));
// e => e.Prop1.Prop2 == value
var predicateLambda = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(predicateLambda);
}
static Expression MakePropPath(Expression objExpression, string path)
{
return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
}
}

I am getting "The LINQ expression could not be translated" exception [duplicate]

given a list of ids, I can query all relevant rows by:
context.Table.Where(q => listOfIds.Contains(q.Id));
But how do you achieve the same functionality when the Table has a composite key?
This is a nasty problem for which I don't know any elegant solution.
Suppose you have these key combinations, and you only want to select the marked ones (*).
Id1 Id2
--- ---
1 2 *
1 3
1 6
2 2 *
2 3 *
... (many more)
How to do this is a way that Entity Framework is happy? Let's look at some possible solutions and see if they're any good.
Solution 1: Join (or Contains) with pairs
The best solution would be to create a list of the pairs you want, for instance Tuples, (List<Tuple<int,int>>) and join the database data with this list:
from entity in db.Table // db is a DbContext
join pair in Tuples on new { entity.Id1, entity.Id2 }
equals new { Id1 = pair.Item1, Id2 = pair.Item2 }
select entity
In LINQ to objects this would be perfect, but, too bad, EF will throw an exception like
Unable to create a constant value of type 'System.Tuple`2 (...) Only primitive types or enumeration types are supported in this context.
which is a rather clumsy way to tell you that it can't translate this statement into SQL, because Tuples is not a list of primitive values (like int or string). For the same reason a similar statement using Contains (or any other LINQ statement) would fail.
Solution 2: In-memory
Of course we could turn the problem into simple LINQ to objects like so:
from entity in db.Table.AsEnumerable() // fetch db.Table into memory first
join pair Tuples on new { entity.Id1, entity.Id2 }
equals new { Id1 = pair.Item1, Id2 = pair.Item2 }
select entity
Needless to say that this is not a good solution. db.Table could contain millions of records.
Solution 3: Two Contains statements (incorrect)
So let's offer EF two lists of primitive values, [1,2] for Id1 and [2,3] for Id2. We don't want to use join, so let's use Contains:
from entity in db.Table
where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
select entity
But now the results also contains entity {1,3}! Well, of course, this entity perfectly matches the two predicates. But let's keep in mind that we're getting closer. In stead of pulling millions of entities into memory, we now only get four of them.
Solution 4: One Contains with computed values
Solution 3 failed because the two separate Contains statements don't only filter the combinations of their values. What if we create a list of combinations first and try to match these combinations? We know from solution 1 that this list should contain primitive values. For instance:
var computed = ids1.Zip(ids2, (i1,i2) => i1 * i2); // [2,6]
and the LINQ statement:
from entity in db.Table
where computed.Contains(entity.Id1 * entity.Id2)
select entity
There are some problems with this approach. First, you'll see that this also returns entity {1,6}. The combination function (a*b) does not produce values that uniquely identify a pair in the database. Now we could create a list of strings like ["Id1=1,Id2=2","Id1=2,Id2=3]" and do
from entity in db.Table
where computed.Contains("Id1=" + entity.Id1 + "," + "Id2=" + entity.Id2)
select entity
(This would work in EF6, not in earlier versions).
This is getting pretty messy. But a more important problem is that this solution is not sargable, which means: it bypasses any database indexes on Id1 and Id2 that could have been used otherwise. This will perform very very poorly.
Solution 5: Best of 2 and 3
So the most viable solution I can think of is a combination of Contains and a join in memory: First do the contains statement as in solution 3. Remember, it got us very close to what we wanted. Then refine the query result by joining the result as an in-memory list:
var rawSelection = from entity in db.Table
where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
select entity;
var refined = from entity in rawSelection.AsEnumerable()
join pair in Tuples on new { entity.Id1, entity.Id2 }
equals new { Id1 = pair.Item1, Id2 = pair.Item2 }
select entity;
It's not elegant, messy all the same maybe, but so far it's the only scalable1 solution to this problem I found, and applied in my own code.
Solution 6: Build a query with OR clauses
Using a Predicate builder like Linqkit or alternatives, you can build a query that contains an OR clause for each element in the list of combinations. This could be a viable option for really short lists. With a couple of hundreds of elements, the query will start performing very poorly. So I don't consider this a good solution unless you can be 100% sure that there will always be a small number of elements. One elaboration of this option can be found here.
Solution 7: Unions
There's also a solution using UNIONs that I posted later here.
1As far as the Contains statement is scalable: Scalable Contains method for LINQ against a SQL backend
Solution for Entity Framework Core with SQL Server
🎉 NEW! QueryableValues EF6 Edition has arrived!
The following solution makes use of QueryableValues. This is a library that I wrote to primarily solve the problem of query plan cache pollution in SQL Server caused by queries that compose local values using the Contains LINQ method. It also allows you to compose values of complex types in your queries in a performant way, which will achieve what's being asked in this question.
First you will need to install and set up the library, after doing that you can use any of the following patterns that will allow you to query your entities using a composite key:
// Required to make the AsQueryableValues method available on the DbContext.
using BlazarTech.QueryableValues;
// Local data that will be used to query by the composite key
// of the fictitious OrderProduct table.
var values = new[]
{
new { OrderId = 1, ProductId = 10 },
new { OrderId = 2, ProductId = 20 },
new { OrderId = 3, ProductId = 30 }
};
// Optional helper variable (needed by the second example due to CS0854)
var queryableValues = dbContext.AsQueryableValues(values);
// Example 1 - Using a Join (preferred).
var example1Results = dbContext
.OrderProduct
.Join(
queryableValues,
e => new { e.OrderId, e.ProductId },
v => new { v.OrderId, v.ProductId },
(e, v) => e
)
.ToList();
// Example 2 - Using Any (similar behavior as Contains).
var example2Results = dbContext
.OrderProduct
.Where(e => queryableValues
.Where(v =>
v.OrderId == e.OrderId &&
v.ProductId == e.ProductId
)
.Any()
)
.ToList();
Useful Links
Nuget Package
GitHub Repository
Benchmarks
QueryableValues is distributed under the MIT license.
You can use Union for each composite primary key:
var compositeKeys = new List<CK>
{
new CK { id1 = 1, id2 = 2 },
new CK { id1 = 1, id2 = 3 },
new CK { id1 = 2, id2 = 4 }
};
IQuerable<CK> query = null;
foreach(var ck in compositeKeys)
{
var temp = context.Table.Where(x => x.id1 == ck.id1 && x.id2 == ck.id2);
query = query == null ? temp : query.Union(temp);
}
var result = query.ToList();
You can create a collection of strings with both keys like this (I am assuming that your keys are int type):
var id1id2Strings = listOfIds.Select(p => p.Id1+ "-" + p.Id2);
Then you can just use "Contains" on your db:
using (dbEntities context = new dbEntities())
{
var rec = await context.Table1.Where(entity => id1id2Strings .Contains(entity.Id1+ "-" + entity.Id2));
return rec.ToList();
}
You need a set of objects representing the keys you want to query.
class Key
{
int Id1 {get;set;}
int Id2 {get;set;}
If you have two lists and you simply check that each value appears in their respective list then you are getting the cartesian product of the lists - which is likely not what you want. Instead you need to query the specific combinations required
List<Key> keys = // get keys;
context.Table.Where(q => keys.Any(k => k.Id1 == q.Id1 && k.Id2 == q.Id2));
I'm not completely sure that this is valid use of Entity Framework; you may have issues with sending the Key type to the database. If that happens then you can be creative:
var composites = keys.Select(k => p1 * k.Id1 + p2 * k.Id2).ToList();
context.Table.Where(q => composites.Contains(p1 * q.Id1 + p2 * q.Id2));
You can create an isomorphic function (prime numbers are good for this), something like a hashcode, which you can use to compare the pair of values. As long as the multiplicative factors are co-prime this pattern will be isomorphic (one-to-one) - i.e. the result of p1*Id1 + p2*Id2 will uniquely identify the values of Id1 and Id2 as long as the prime numbers are correctly chosen.
But then you end up in a situation where you're implementing complex concepts and someone is going to have to support this. Probably better to write a stored procedure which takes the valid key objects.
Ran into this issue as well and needed a solution that both did not perform a table scan and also provided exact matches.
This can be achieved by combining Solution 3 and Solution 4 from Gert Arnold's Answer
var firstIds = results.Select(r => r.FirstId);
var secondIds = results.Select(r => r.SecondId);
var compositeIds = results.Select(r => $"{r.FirstId}:{r.SecondId}");
var query = from e in dbContext.Table
//first check the indexes to avoid a table scan
where firstIds.Contains(e.FirstId) && secondIds.Contains(e.SecondId))
//then compare the compositeId for an exact match
//ToString() must be called unless using EF Core 5+
where compositeIds.Contains(e.FirstId.ToString() + ":" + e.SecondId.ToString()))
select e;
var entities = await query.ToListAsync();
For EF Core I use a slightly modified version of the bucketized IN method by EricEJ to map composite keys as tuples. It performs pretty well for small sets of data.
Sample usage
List<(int Id, int Id2)> listOfIds = ...
context.Table.In(listOfIds, q => q.Id, q => q.Id2);
Implementation
public static IQueryable<TQuery> In<TKey1, TKey2, TQuery>(
this IQueryable<TQuery> queryable,
IEnumerable<(TKey1, TKey2)> values,
Expression<Func<TQuery, TKey1>> key1Selector,
Expression<Func<TQuery, TKey2>> key2Selector)
{
if (values is null)
{
throw new ArgumentNullException(nameof(values));
}
if (key1Selector is null)
{
throw new ArgumentNullException(nameof(key1Selector));
}
if (key2Selector is null)
{
throw new ArgumentNullException(nameof(key2Selector));
}
if (!values.Any())
{
return queryable.Take(0);
}
var distinctValues = Bucketize(values);
if (distinctValues.Length > 1024)
{
throw new ArgumentException("Too many parameters for SQL Server, reduce the number of parameters", nameof(values));
}
var predicates = distinctValues
.Select(v =>
{
// Create an expression that captures the variable so EF can turn this into a parameterized SQL query
Expression<Func<TKey1>> value1AsExpression = () => v.Item1;
Expression<Func<TKey2>> value2AsExpression = () => v.Item2;
var firstEqual = Expression.Equal(key1Selector.Body, value1AsExpression.Body);
var visitor = new ReplaceParameterVisitor(key2Selector.Parameters[0], key1Selector.Parameters[0]);
var secondEqual = Expression.Equal(visitor.Visit(key2Selector.Body), value2AsExpression.Body);
return Expression.AndAlso(firstEqual, secondEqual);
})
.ToList();
while (predicates.Count > 1)
{
predicates = PairWise(predicates).Select(p => Expression.OrElse(p.Item1, p.Item2)).ToList();
}
var body = predicates.Single();
var clause = Expression.Lambda<Func<TQuery, bool>>(body, key1Selector.Parameters[0]);
return queryable.Where(clause);
}
class ReplaceParameterVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
/// <summary>
/// Break a list of items tuples of pairs.
/// </summary>
private static IEnumerable<(T, T)> PairWise<T>(this IEnumerable<T> source)
{
var sourceEnumerator = source.GetEnumerator();
while (sourceEnumerator.MoveNext())
{
var a = sourceEnumerator.Current;
sourceEnumerator.MoveNext();
var b = sourceEnumerator.Current;
yield return (a, b);
}
}
private static TKey[] Bucketize<TKey>(IEnumerable<TKey> values)
{
var distinctValueList = values.Distinct().ToList();
// Calculate bucket size as 1,2,4,8,16,32,64,...
var bucket = 1;
while (distinctValueList.Count > bucket)
{
bucket *= 2;
}
// Fill all slots.
var lastValue = distinctValueList.Last();
for (var index = distinctValueList.Count; index < bucket; index++)
{
distinctValueList.Add(lastValue);
}
var distinctValues = distinctValueList.ToArray();
return distinctValues;
}
In the absence of a general solution, I think there are two things to consider:
Avoid multi-column primary keys (will make unit testing easier too).
But if you have to, chances are that one of them will reduce the
query result size to O(n) where n is the size of the ideal query
result. From here, its Solution 5 from Gerd Arnold above.
For example, the problem leading me to this question was querying order lines, where the key is order id + order line number + order type, and the source had the order type being implicit. That is, the order type was a constant, order ID would reduce the query set to order lines of relevant orders, and there would usually be 5 or less of these per order.
To rephrase: If you have a composite key, changes are that one of them have very few duplicates. Apply Solution 5 from above with that.
I tried this solution and it worked with me and the output query was perfect without any parameters
using LinqKit; // nuget
var customField_Ids = customFields?.Select(t => new CustomFieldKey { Id = t.Id, TicketId = t.TicketId }).ToList();
var uniqueIds1 = customField_Ids.Select(cf => cf.Id).Distinct().ToList();
var uniqueIds2 = customField_Ids.Select(cf => cf.TicketId).Distinct().ToList();
var predicate = PredicateBuilder.New<CustomFieldKey>(false); //LinqKit
var lambdas = new List<Expression<Func<CustomFieldKey, bool>>>();
foreach (var cfKey in customField_Ids)
{
var id = uniqueIds1.Where(uid => uid == cfKey.Id).Take(1).ToList();
var ticketId = uniqueIds2.Where(uid => uid == cfKey.TicketId).Take(1).ToList();
lambdas.Add(t => id.Contains(t.Id) && ticketId.Contains(t.TicketId));
}
predicate = AggregateExtensions.AggregateBalanced(lambdas.ToArray(), (expr1, expr2) =>
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<CustomFieldKey, bool>>
(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
});
var modifiedCustomField_Ids = repository.GetTable<CustomFieldLocal>()
.Select(cf => new CustomFieldKey() { Id = cf.Id, TicketId = cf.TicketId }).Where(predicate).ToArray();
I ended up writing a helper for this problem that relies on System.Linq.Dynamic.Core;
Its a lot of code and don't have time to refactor at the moment but input / suggestions appreciated.
public static IQueryable<TEntity> WhereIsOneOf<TEntity, TSource>(this IQueryable<TEntity> dbSet,
IEnumerable<TSource> source,
Expression<Func<TEntity, TSource,bool>> predicate) where TEntity : class
{
var (where, pDict) = GetEntityPredicate(predicate, source);
return dbSet.Where(where, pDict);
(string WhereStr, IDictionary<string, object> paramDict) GetEntityPredicate(Expression<Func<TEntity, TSource, bool>> func, IEnumerable<TSource> source)
{
var firstP = func.Parameters[0];
var binaryExpressions = RecurseBinaryExpressions((BinaryExpression)func.Body);
var i = 0;
var paramDict = new Dictionary<string, object>();
var res = new List<string>();
foreach (var sourceItem in source)
{
var innerRes = new List<string>();
foreach (var bExp in binaryExpressions)
{
var emp = ToEMemberPredicate(firstP, bExp);
var val = emp.GetKeyValue(sourceItem);
var pName = $"#{i++}";
paramDict.Add(pName, val);
var str = $"{emp.EntityMemberName} {emp.SQLOperator} {pName}";
innerRes.Add(str);
}
res.Add( "(" + string.Join(" and ", innerRes) + ")");
}
var sRes = string.Join(" || ", res);
return (sRes, paramDict);
}
EMemberPredicate ToEMemberPredicate(ParameterExpression firstP, BinaryExpression bExp)
{
var lMember = (MemberExpression)bExp.Left;
var rMember = (MemberExpression)bExp.Right;
var entityMember = lMember.Expression == firstP ? lMember : rMember;
var keyMember = entityMember == lMember ? rMember : lMember;
return new EMemberPredicate(entityMember, keyMember, bExp.NodeType);
}
List<BinaryExpression> RecurseBinaryExpressions(BinaryExpression e, List<BinaryExpression> runningList = null)
{
if (runningList == null) runningList = new List<BinaryExpression>();
if (e.Left is BinaryExpression lbe)
{
var additions = RecurseBinaryExpressions(lbe);
runningList.AddRange(additions);
}
if (e.Right is BinaryExpression rbe)
{
var additions = RecurseBinaryExpressions(rbe);
runningList.AddRange(additions);
}
if (e.Left is MemberExpression && e.Right is MemberExpression)
{
runningList.Add(e);
}
return runningList;
}
}
Helper class:
public class EMemberPredicate
{
public readonly MemberExpression EntityMember;
public readonly MemberExpression KeyMember;
public readonly PropertyInfo KeyMemberPropInfo;
public readonly string EntityMemberName;
public readonly string SQLOperator;
public EMemberPredicate(MemberExpression entityMember, MemberExpression keyMember, ExpressionType eType)
{
EntityMember = entityMember;
KeyMember = keyMember;
KeyMemberPropInfo = (PropertyInfo)keyMember.Member;
EntityMemberName = entityMember.Member.Name;
SQLOperator = BinaryExpressionToMSSQLOperator(eType);
}
public object GetKeyValue(object o)
{
return KeyMemberPropInfo.GetValue(o, null);
}
private string BinaryExpressionToMSSQLOperator(ExpressionType eType)
{
switch (eType)
{
case ExpressionType.Equal:
return "==";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.NotEqual:
return "<>";
default:
throw new ArgumentException($"{eType} is not a handled Expression Type.");
}
}
}
Use Like so:
// This can be a Tuple or whatever.. If Tuple, then y below would be .Item1, etc.
// This data structure is up to you but is what I use.
[FromBody] List<CustomerAddressPk> cKeys
var res = await dbCtx.CustomerAddress
.WhereIsOneOf(cKeys, (x, y) => y.CustomerId == x.CustomerId
&& x.AddressId == y.AddressId)
.ToListAsync();
Hope this helps others.
in Case of composite key you can use another idlist and add a condition for that in your code
context.Table.Where(q => listOfIds.Contains(q.Id) && listOfIds2.Contains(q.Id2));
or you can use one another trick create a list of your keys by adding them
listofid.add(id+id1+......)
context.Table.Where(q => listOfIds.Contains(q.Id+q.id1+.......));
I tried this on EF Core 5.0.3 with the Postgres provider.
context.Table
.Select(entity => new
{
Entity = entity,
CompositeKey = entity.Id1 + entity.Id2,
})
.Where(x => compositeKeys.Contains(x.CompositeKey))
.Select(x => x.Entity);
This produced SQL like:
SELECT *
FROM table AS t
WHERE t.Id1 + t.Id2 IN (#__compositeKeys_0)),
Caveats
this should only be used where the combination of Id1 and Id2 will always produce a unique result (e.g., they're both UUIDs)
this cannot use indexes, though you could save the composite key to the db with an index

dynamic query on entity field name in a generic

I'm using generics because I need a lot of reusability with different types of data. my main problem is querying data. I'm looking for a way to query something like this:
public void test<T>(int id, T type) where T : class
{
using (var ctx = myDbContext())
{
var myTbl = ctx.Set<T>();
//this line gets the primary key of the table
string key = myTbl.GetPrimaryKey(ctx);
//this is the query I want:
var myResult = myTbl.FirstOrDefault(x => x.key == id);
//let's say if key = "UserId", then (x => x.UserId == id) or something that translates to this.
}
}
also I have implemented following method:
public object GetPropertyValue(object src, string propertyName)
that I can use to get value of a specific property.
but my problem is that I can't use it inside the .FirstOrDefault() call because of the LINQ to query issues with methods.
I currently use this code instead:
var myResult = myTbl.ToList().FirstOrDefault(x => (int)GetPropertyValue(x, key) == id);
which is fine with a few number of rows in database, but when data grows in future it will have a lot of performance impact.
P.S: I'm using EF power tools for reverse engineering code first
Umm, your code sample is completely unclear. Please, atleast provide correct variable names, because now I don't even know where you use 'key' variable.
Also, if you want to store and query objects and it's properties of various inheritance and nestings, consider to use NoSQL databases instead of relation based SQL engines.
First -> Dont use myTbl.ToList().FirstOrDefault(x => (int)GetPropertyValue(x, key) == id) because it generates a select that brings all rows from that table, then filter by id in memory. You should translate your filter to an Expression> that will generare a select filtered by the Id column.
Build your linq expression like this:
var x = Expression.Parameter(typeof(T), "x");
string keyPropName = type.GetPrimaryKey(ctx);
var equalExp = Expression.Equal(
Expression.Property(x, keyPropName),
Expression.Constant(id)
);
var lambda = Expression.Lambda<Func<T, bool>>(equalExp, x); //x => x.Id == idValue
var myResult = myTbl.FirstOrDefault(lambda);
I used your GetPropertyValue and GetPrimaryKey methods.

How to parametrize a query in Entity Framework?

I am new to EF. I have a table with a list of projects. I have found a query in my software that finds all projects .
public Project[] FindAll()
{
var projects = new List<Project>();
using (var db = new ProjetDbConext())
{
var qProjects = from project in db.ProjectSet
where project.CreateDateTime != null
select project;
projects = qProjects.ToList();
}
return projects.ToArray();
}
This seems to be fine , but I am not sure how to parametrize it. I need this because I am implementing a search feature trying to re use some query logic from EF.
This takes a List of tuples . Each tuple basically has an attribute and a list of search terms.
eg. Tuple(FirstName , { Prasaanth ,Bill } ; Tuple( LastName , { Neelakandan , Gates } ;
This means I need to write a select query where I search projects where FirstName is Prasaanth or Bill . If the list has only one term.
eg. Tuple( Company , { Microsoft} ; then i need to search only one where condition in my query.
public Project[] LoadSearchProjects(List<System.Tuple<string, List<string>>> searchTerms)
{
var projects = new List<Project>();
using (var db = new ProjetDbConext())
{
foreach (System.Tuple<string, List<string>> pair in searchTerms)
{
string attribute = pair.Item1;
List<string> terms = pair.Item2;
/// logic here
}
}
return projects.ToArray();
}
I can always write an if condition where I do :
if(attribute.equals("FirstName"){
// query project.FirstName in the where conditon
}
But I have too many attributes to search on.
I know the ADO.NET way of doing this :
mycommands = new SqlCommand(" select projects from from Persons where '"+attibute+"' = some search terms ...
I don't know how to do something like this in my EF query.
1 ) Is there a way EF allows me to do the search on dynamic attributes ? or parametrize using '"+attribute+"' ??
2) Is there a better data structure I could use to simplify my structure instead of using List<Tuple<string, List<string>> ?
3) I was recommend to use 3rd party LINQKit or dynamic linq but am not sure how to integrate that to EF querying.
My apologies if much of this sounds like collegeboy code. Please let me know if any additional details needed.
Regards,
Prasaanth
UPDATE :
Working method as per Andriy's answer. My question here is this doesnt work if any particular entry in my database say Name is Null.
private static Expression<Func<TEntity, bool>> BuildStringFilter<TEntity, TProp>(
Tuple<string, List<string>> filter)
{
// entity is the Project table
var entity = Expression.Parameter(typeof (TEntity));
var prop = Expression.Property(entity, filter.Item1);
//check if contains returns true
var body = filter.Item2
.Select(v => Expression.Equal(Expression.Call(prop,
typeof (String).GetMethod("Contains"),
new Expression[] { Expression.Constant(v) }), Expression.Constant(true)))
.Aggregate(Expression.Or);
var result = (Expression<Func<TEntity, bool>>) Expression.Lambda(body, entity);
return result;
}
Any way I can modify the expression so that the Contains method :
prop,
typeof (String).GetMethod("Contains"),
new Expression[] { Expression.Constant(v)
works if the value of the attribute (prop) is null ?
You can build filter expression using snippet:
public static Expression<Func<TEntity, bool>> BuildFilter<TEntity, TProp>(
KeyValuePair<string, IEnumerable<TProp>> filter)
{
var entity = Expression.Parameter(typeof(TEntity));
var prop = Expression.Property(entity, filter.Key);
var body = filter.Value
.Select(v => Expression.Equal(prop, Expression.Constant(v)))
.Aggregate((curr, next) => Expression.Or(curr, next));
var result = (Expression<Func<TEntity, bool>>)Expression.Lambda(body, entity);
return result;
}
And call it like:
var filter = new KeyValuePair<string, IEnumerable<string>> (
"FirstName",
new [] {"Alice", "Bob"}
);
var predicate = BuildFilter<Item, string>(filter);
var result = ctx.Items.Where(predicate);
Also, see How to: Use Expression Trees to Build Dynamic Queries.
I'm spitballing here, but I think something like this would be simpler:
public Project[] Find(Expression<Func<Project, bool> filter = null)
{
using (var db = new ProjetDbConext())
{
var query = db.ProjectSet.Where(p => p.CreateDateTime != null);
if(filter != null)
query = query.Where(filter);
return query.ToArray();
}
}
Use it like:
var projects = repo.Find(p => p.id > 100);

Linq to Entities Join -- I don't want anonymous types

I have two tables that are related via a mapping table:
keywords
titles
I am trying to return a list of Map_Keywords_Title (the mapping table's object) based on the results of a join.
Until I added the last part with typedQuery it was just returning objects of anonymous type. I want it to return items of type Map_Keywords_Title so I tried adding the Select at the end (also tried Cast but that failed). The problem now is that the resulting objects are not managed by entity framework so I can't save changes to them.
Is there no straightforward way to do this in Linq to Entities? I'm using VS2008 so I don't yet have the new EF.
public IList<Map_Keywords_Title> MatchingTitleKeywordMappings(string containedInText)
{
var keywords = QueryKeywords();
if (!string.IsNullOrEmpty(containedInText))
{
Expression<Func<Keyword, bool>> exprContainsKeyword = k => containedInText.Contains(k.WordText);
keywords = keywords.Where(exprContainsKeyword);
}
var maps = QueryMap_Keywords_Title();
var anonQuery = keywords.Join(
maps,
key => (int)key.Id,
map => (int)map.Keyword.Id,
(key, map) => new
{
map.Id,
map.Keyword,
map.Title
});
var typedQuery = anonQuery.ToList().Select(anon => new Map_Keywords_Title()
{
Id = anon.Id,
Keyword=anon.Keyword,
Title=anon.Title
});
return typedQuery.ToList();
}
Ok, I got tired of comments...
using( someContext sc = new someContext())
{
sc.Map_Keywords_Title.Include("Title")
.Include("Keyword")
.Where( mkt => containedInText.Contains(mkt.Keyword.WordText));
}
Is that anywhere close to what you want?

Categories

Resources