Custom EF Core AddOrUpdate with composite keys - entity-framework

I've built an extension for Microsoft.EntityFrameworkCore that implements the AddOrUpdateMethod. It's working fine, but with entities with a composite primary key the AnyAsync method return always false, even if there are objects with the same key.
This is the method:
public static async Task AddOrUpdateAsync<TEntity>(this DbSet<TEntity> table, Expression<Func<TEntity, object>> key, Expression<Func<TEntity, bool>> deleteExpression, params TEntity[] entities) where TEntity : class
{
var getKeyFunction = key.Compile();
var getShouldDeleteFunction = deleteExpression.Compile();
var context = GetDbContext(table);
foreach (var entity in entities)
{
var primaryKey = getKeyFunction(entity);
var body = Expression.Equal(Expression.Convert(key.Body, primaryKey.GetType()), Expression.Constant(primaryKey));
Expression<Func<TEntity, bool>> query = Expression.Lambda<Func<TEntity, bool>>(body, key.Parameters);
var exist = await table.AnyAsync(query);
context.Entry(entity).State = exist
? getShouldDeleteFunction(entity) ? EntityState.Deleted : EntityState.Modified
: getShouldDeleteFunction(entity) ? EntityState.Detached : EntityState.Added;
}
}
private static DbContext GetDbContext<T>(this DbSet<T> table) where T : class
{
var infrastructure = table as IInfrastructure<IServiceProvider>;
var serviceProvider = infrastructure.Instance;
var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext;
return currentDbContext.Context;
}
and I'm using it like this:
await db.Reports.AddOrUpdateAsync(r => new { r.Number, r.Year }, r => r.Active == false, response.Reports.ToArray());
I think that's happening because I'm using an anonymous type as the key, but I've no idea how to fix this.

The problem seems to be the usage of the anonymous type constant expression, which currently is causing client evaluation, and C# operator == compares anonymous types by reference, hence always returns false.
The trick to get the desired server translation is to "invoke" the key expression with entity by replacing the parameter with Expression.Constant(entity) (Expression.Invoke doesn't work in this case)
So remove the line var getKeyFunction = key.Compile(); as no longer needed, and use the following:
foreach (var entity in entities)
{
var parameter = key.Parameters[0];
var body = Expression.Equal(
key.Body,
key.Body.ReplaceParameter(parameter, Expression.Constant(entity))
);
var query = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
var exist = await table.AnyAsync(query);
// ...
}
where ReplaceParameter is the usual expression helper method:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { Source = source, Target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : node;
}
}

Related

Get dependent ids when querying principal

I'm trying to get just the ids for dependents if a principal is queried, every time the principal is queried.
My initial thought is to add it somehow in the OnModelCreating definitions, however that appears to be limited to filtering down larger sets of data, unless I'm missing something.
Something like this:
builder.Entity<ListingModel>()
.AlsoDoThis(
x => x.MenuIds.AddRange(
Menus.Where(y => y.ListingId == x.Id).Select(y => y.Id).ToList()
)
);
There is a need to not do this in code for each individual place I have a Select, since that functionality is normalized in some base classes. The base classes have a <TModel> passed in and don't inherently know what properties need to be handled this way.
I do have a workaround where I'm grabbing everything with an AutoInclude(), then filtering it out in the model definition with customer getter/setter to return a list of ids. But rather than being more performant (grabbing related FK ids at the DB level) it's transferring all of that data to the server and then programmatically selecting a list of ids, as far as I understand it.
private List<int> _topicsIds = new();
[NotMapped]
public List<int> TopicsIds
{
get { return Topics.Count > 0 ? Topics.Select(x => x.Id).ToList() : _topicsIds; }
set { _topicsIds = value; }
}
public List<TopicModel> Topics { get; set; } = new();
"Extra SQL that gets called with every select in a context" is (to my limited knowledge) almost what HasQueryFilter does, with a just slightly broader operation. I think this is the approach I'm looking for, just selecting more stuff instead of filtering stuff out.
You can project everything via Select
var result = ctx.ListingModels
.Select(lm => new // or to DTO
{
Id = lm.Id,
OtherProperty = lm.OtherProperty,
Ids = x.MenuIds.Select(m => m.Id).ToList()
})
.ToList();
To make more general solution we can use annotations and define how to project such entities.
During Model defining:
builder.Entity<TopicModel>()
.WithProjection(
x => x.MenuIds,
x => x.Menus.Where(y => y.ListingId == x.Id).Select(y => y.Id).ToList()
);
Then usage in common code:
public virtual List<TModel> GetList(List<int> ids)
{
var list = _context.Set<TModel>().Where(x => ids.Any(id => id == x.Id))
.ApplyCustomProjection(_context)
.ToList();
return list;
}
ApplyCustomProjection(_context) will find previously defined annotation and will apply custom projection.
And extensions implementation:
public static class ProjectionExtensions
{
public const string CustomProjectionAnnotation = "custom:member_projection";
public class ProjectionInfo
{
public ProjectionInfo(MemberInfo member, LambdaExpression expression)
{
Member = member;
Expression = expression;
}
public MemberInfo Member { get; }
public LambdaExpression Expression { get; }
}
public static bool IsUnderDotnetTool { get; }
= Process.GetCurrentProcess().ProcessName == "dotnet";
public static EntityTypeBuilder<TEntity> WithProjection<TEntity, TValue>(
this EntityTypeBuilder<TEntity> entity,
Expression<Func<TEntity, TValue>> propExpression,
Expression<Func<TEntity, TValue>> assignmentExpression)
where TEntity : class
{
// avoid registering non serializable annotations during migrations update
if (IsUnderDotnetTool)
return entity;
var annotation = entity.Metadata.FindAnnotation(CustomProjectionAnnotation);
var projections = annotation?.Value as List<ProjectionInfo> ?? new List<ProjectionInfo>();
if (propExpression.Body is not MemberExpression memberExpression)
throw new InvalidOperationException($"'{propExpression.Body}' is not member expression");
if (memberExpression.Expression is not ParameterExpression)
throw new InvalidOperationException($"'{memberExpression.Expression}' is not parameter expression. Only single nesting is allowed");
// removing duplicate
projections.RemoveAll(p => p.Member == memberExpression.Member);
projections.Add(new ProjectionInfo(memberExpression.Member, assignmentExpression));
return entity.HasAnnotation(CustomProjectionAnnotation, projections);
}
public static IQueryable<TEntity> ApplyCustomProjection<TEntity>(this IQueryable<TEntity> query, DbContext context)
where TEntity : class
{
var et = context.Model.FindEntityType(typeof(TEntity));
var projections = et?.FindAnnotation(CustomProjectionAnnotation)?.Value as List<ProjectionInfo>;
// nothing to do
if (projections == null || et == null)
return query;
var propertiesForProjection = et.GetProperties().Where(p =>
p.PropertyInfo != null && projections.All(pr => pr.Member != p.PropertyInfo))
.ToList();
var entityParam = Expression.Parameter(typeof(TEntity), "e");
var memberBinding = new MemberBinding[propertiesForProjection.Count + projections.Count];
for (int i = 0; i < propertiesForProjection.Count; i++)
{
var propertyInfo = propertiesForProjection[i].PropertyInfo!;
memberBinding[i] = Expression.Bind(propertyInfo, Expression.MakeMemberAccess(entityParam, propertyInfo));
}
for (int i = 0; i < projections.Count; i++)
{
var projection = projections[i];
var expression = projection.Expression.Body;
var assignExpression = ReplacingExpressionVisitor.Replace(projection.Expression.Parameters[0], entityParam, expression);
memberBinding[propertiesForProjection.Count + i] = Expression.Bind(projection.Member, assignExpression);
}
var memberInit = Expression.MemberInit(Expression.New(typeof(TEntity)), memberBinding);
var selectLambda = Expression.Lambda<Func<TEntity, TEntity>>(memberInit, entityParam);
var newQuery = query.Select(selectLambda);
return newQuery;
}
}

How to construct Where Expression dynamically in Entity Framework?

I've taken a look at this answer on how to dynamically create an OrderBy expression in Entity Framework. But I'd like to also build a dynamic Where expression. Something along the lines of this:
public IEnumerable<InventoryItem> GetAll(string filterBy, object value)
{
var results = new List<InventoryItem>();
using (var db = new InventoryDb())
{
if (QueryHelper.PropertyExists<InventoryItem>(filterBy))
{
var query = db.rri_InventoryItems.WhereByProperty(filterBy, value);
foreach(var item in query.Where(expr))
{
results.Add(ConvertItem(item));
}
}
}
return results;
}
Passing in the property to filter by and a value as ab object. Queryable has two methods for Where that both take two parameters, so I am not even sure which is the proper one.
And it's at this point I get a little more lost. I'm not sure how to refactor the original OrderByProerty method to provide a WhereByProperty. I know what I have here is completely wrong. I'm not sure what to do with it.
Ideally, I'd want to extend this even more by providing a collection of objects that could be used to build a query with ands and or operators.
Queryable has two methods for Where that both take two parameters, so I am not even sure which is the proper one.
You need the one that receives Expression<Func<T, bool>> predicate.
Here is how you can build dynamically a predicate similar to (T item) => item.Property == value:
public static partial class QueryableExtensions
{
public static IQueryable<T> WhereEquals<T>(this IQueryable<T> source, string member, object value)
{
var item = Expression.Parameter(typeof(T), "item");
var memberValue = member.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);
var memberType = memberValue.Type;
if (value != null && value.GetType() != memberType)
value = Convert.ChangeType(value, memberType);
var condition = Expression.Equal(memberValue, Expression.Constant(value, memberType));
var predicate = Expression.Lambda<Func<T, bool>>(condition, item);
return source.Where(predicate);
}
}
I've tried to write it in such a way so you can step over the code in order to understand what it does. The only line that might need some explanation is:
var memberValue = member.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);
This is a simple way of handling nested properties like obj.Prop1.Prop2 etc. If you don't need such capability, you can simply use this instead:
var memberValue = Expression.PropertyOrField(item, member);
I didn't need nested properties (yet). I modified your code slightly and have this that is working:
public static IQueryable<T> WhereEquals<T>(
this IQueryable<T> source, string propertyName, object value)
{
if (typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) == null)
{
return null;
}
ParameterExpression parameter = Expression.Parameter(typeof(T), "item");
Expression whereProperty = Expression.Property(parameter, propertyName);
Expression constant = Expression.Constant(value);
Expression condition = Expression.Equal(whereProperty, constant);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(condition,parameter);
return source.Where(lambda);
}

Linq Expression building for Entity Framework involving complex object

I am using Entity Framework version 4. I need to compare a large (~1 million record) SQL Server table to a longish (~2000) array of complex objects returned from a web service. Five different properties need to be compared to determine whether an instance of the complex object is already in the database.
I created a function that returns an expression for use in .Where and .Any methods. It looks like this (where A is the complex object, and tblA is the EF class):
function Expression<tblA, bool> GetSearchPredicate(A a)
{
return ta => ta.Field1.Equals(a.Field1)
&& ta.Field2.Equals(a.Field2)
&& ta.Field3.Equals(a.Field3)
&& ta.Field4.Equals(a.Field4)
&& ta.Field5.Equals(a.Field5);
}
This works. And I can compare all 2000 instances of A by doing this:
IEnumerable<A> objects = [web service call];
var result = objects.Select(a => !db.tblA.Any(GetSearchPredicate(a)));
That works, too. But it's slow. So I looked into building a utility method that could build an expression that could be transmitted down to the database directly through EF.
I used the code in this question as a basis for building that utility method. The example in that question shows comparing a single property to a series of constants, while my version would have to compare multiple properties to multiple constants. My best effort is below:
public static IQueryable<TEntity> WhereIn<TEntity>
(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates
)
{
if (predicates == null) throw new ArgumentNullException("predicates");
IEnumerable<ParameterExpression> p = predicates.Select(pred => pred.Parameters.Single()).ToArray();
IEnumerable<Expression> equals = predicates.Select(value =>
(Expression)value.Body);
Expression bigEqual = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
var result1 = Expression.Lambda<Func<TEntity, bool>>(bigEqual, p.First());
var result = query.Where(result1);
return result;
}
This would be invoked like this:
IEnumerable<A> objects = [web service call];
var result = db.tblA.WhereIn(objects.Select(a => GetSearchPredicate(a)));
What I get is a message saying that "ta" (the placeholder for the TEntity object) is not bound. I thought this was because I had multiple expressions (the variable predicates) being combined, and maybe this message was being thrown because I was only passing the parameter from the first of the predicates IEnumerable. But this happens even if predicates is one expression long.
I am reasonably sure, based on the method I linked to, that I could build an expression comparing each of the five properties to a constant (the values of A.Field1 through A.Field5), rather than passing in the parameter predicates that already has them assembled into a series of expressions. But I would rather not, since that would require my method to know that it's working with types A and tblA, and that's the opposite of generic and general-purpose. (It'd also be complex and messy.)
I hope the examples I've shown explain what I want to do. Can it be done in a generic way?
You will need to replace the parameter in the predicate bodies with a single parameter. Something like this should work:
public static Expression<Func<T, bool>> BuildOr<T>(
IEnumerable<Expression<Func<T, bool>>> predicates)
{
Expression body = null;
ParameterExpression p = null;
Expression<Func<T, bool>> first = null;
foreach (Expression<Func<T, bool>> item in predicates)
{
if (first == null)
{
first = item;
}
else
{
if (body == null)
{
body = first.Body;
p = first.Parameters[0];
}
var toReplace = item.Parameters[0];
var itemBody = ReplacementVisitor.Transform(item, toReplace, p);
body = Expression.OrElse(body, itemBody);
}
}
if (first == null)
{
throw new ArgumentException("Sequence contains no elements.", "predicates");
}
return (body == null) ? first : Expression.Lambda<Func<T, bool>>(body, p);
}
private sealed class ReplacementVisitor : ExpressionVisitor
{
private IList<ParameterExpression> SourceParameters { get; set; }
private Expression ToFind { get; set; }
private Expression ReplaceWith { get; set; }
public static Expression Transform(
LambdaExpression source,
Expression toFind,
Expression replaceWith)
{
var visitor = new ReplacementVisitor
{
SourceParameters = source.Parameters,
ToFind = toFind,
ReplaceWith = replaceWith,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == ToFind) ? ReplaceWith : node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return ReplaceNode(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
Your WhereIn method then becomes:
public static IQueryable<TEntity> WhereIn<TEntity>(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates)
{
if (predicates == null) throw new ArgumentNullException("predicates");
var predicate = BuildOr(predicates);
return query.Where(predicate);
}

Entity Framework eager loading/"IncludeEverything()"?

In EF4, I have a small object map and the volume of data is also small. So for queries I want to eager load all associated data. Is there any single method call that can do the job, like "IQueryable.IncludeEverything()", rather than call Include() repeatedly with hardcoded property names?
There's nothing out of the box, but you could use MetadataWorkspace to implement it:
public static IQueryable<T> IncludeEverything<T>(this IQueryable<T> query, ObjectContext context)
where T : class
{
var ospaceEntityType = context.MetadataWorkspace.GetItem<EntityType>(
typeof(T).FullName, DataSpace.OSpace);
var cspaceEntityType = context.MetadataWorkspace.GetEdmSpaceType(ospaceEntityType);
var includedTypes = new HashSet<EdmType>();
includedTypes.Add(cspaceEntityType);
return IncludeEverything(query, cspaceEntityType as EntityType, "", includedTypes);
}
private static IQueryable<T> IncludeEverything<T>(IQueryable<T> query,
EntityType entity,
string path,
HashSet<EdmType> includedTypes)
where T : class
{
foreach (var navigationProperty in entity.NavigationProperties)
{
var propertyEdmType = navigationProperty.TypeUsage.EdmType;
if (includedTypes.Contains(propertyEdmType))
{
continue;
}
includedTypes.Add(propertyEdmType);
var propertyCollectionType = propertyEdmType as CollectionType;
EntityType propertyEntityType;
if (propertyCollectionType != null)
{
propertyEntityType = propertyCollectionType.TypeUsage.EdmType as EntityType;
} else
{
propertyEntityType = propertyEdmType as EntityType;
}
var propertyPath = string.IsNullOrEmpty(path) ? "" : path + ".";
propertyPath += navigationProperty.Name;
query = query.Include(propertyPath);
query = IncludeEverything(query, propertyEntityType, propertyPath, includedTypes);
}
return query;
}
Note that this code is just for illustration. It doesn't have parameter validation, it may include the same enities several times and it won't include all the related entities if there are cycles in your model.

LINQ to Entities - cannot cast 'System.DateTime' to type 'System.Object' in orderBy

I am trying to order an IQueryable of entities by date from a passed in Expression< Func< T, object>> and am getting the error: "Unable to cast the type 'System.Nullable`1' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types." The entity has a nullable datetime property on it on which I am trying to sort:
Example: (where e.Date is a nullable DateTime)
Expression<Func<T,object>> sorter = (e) => e.Date;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Thanks in advance!
I wrote a simple class for ordering entities based on a lambda expression at runtime.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace DataModeling
{
public class QueryOrderer<TEntity>
where TEntity : class
{
private LambdaExpression defaultSortExpression;
private Dictionary<string, LambdaExpression> orderFieldLookup;
public QueryOrderer()
{
orderFieldLookup = new Dictionary<string, LambdaExpression>();
}
public void AddOrderMapping<TProp>(string fieldName, Expression<Func<TEntity, TProp>> selector)
{
orderFieldLookup[fieldName] = selector;
}
public void SetDefaultSortExpression<TProp>(Expression<Func<TEntity, TProp>> selector)
{
defaultSortExpression = selector;
}
public IQueryable<TEntity> GetOrderedEntries(string field, bool isDescending, IQueryable<TEntity> entries)
{
return orderEntries(entries, field, isDescending);
}
private IQueryable<TEntity> orderEntries(IQueryable<TEntity> entries, string fieldName, bool isDescending)
{
dynamic lambda = getOrderByLambda(entries, fieldName);
if (lambda == null)
{
return entries;
}
if (isDescending)
{
return Queryable.OrderByDescending(entries, lambda);
}
else
{
return Queryable.OrderBy(entries, lambda);
}
}
private dynamic getOrderByLambda(IQueryable<TEntity> entries, string fieldName)
{
if (!String.IsNullOrWhiteSpace(fieldName) && orderFieldLookup.ContainsKey(fieldName))
{
return orderFieldLookup[fieldName];
}
else
{
return defaultSortExpression;
}
}
}
}
You use this class by initially setting up all of the fields:
QueryOrderer<User> orderer = new QueryOrderer<User>();
orderer.SetDefaultSortExpression(u => u.FullName);
orderer.AddOrderMapping("UserId", u => u.UserId);
orderer.AddOrderMapping("Name", u => u.FullName);
orderer.AddOrderMapping("Email", u => u.Email);
orderer.AddOrderMapping("CreatedOn", u => u.CreatedOn);
...
var users = orderer.GetOrderedEntries("CreatedOn", isDescending: false, context.Users);
I nice feature of this code is that it handles look-up values perfectly. For instance, if you're trying to sort using the description rather than a key, you can use the outer context when building up the sort expression:
orderer.AddOrderMapping("UserType",
u => context.UserTypes
.Where(t => t.UserTypeId == u.UserTypeId)
.Select(t => t.Description)
.FirstOrDefault());
Entity Framework is smart enough to just fold the sub-query right into the outer query.
Two problem here: First you use object in your sorter, you should use DateTime. Secondly every element must have a place in the order so you have to define what should happen with elements where Date is null:
Expression<Func<T, DateTime>> sorter = (e) => e.Date ?? DateTime.MaxValue;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Try to reconstruct expression body
private LambdaExpression CreateLambdaPropertyGetter(Expression<Func<TEntity, object>> expression)
{
Expression body;
if (expression.Body is UnaryExpression && ((UnaryExpression)expression.Body).NodeType == ExpressionType.Convert)
body = ((UnaryExpression)expression.Body).Operand;
else
body = expression.Body;
var lambda = Expression.Lambda(body, expression.Parameters);
return lambda;
}
Try using Func delegate instead on Expression<Func>
Func<T,object> sorter = (e) => e.Date;
IOrderedEnumerable<T> sortedData = data.OrderBy(sorter);