/// <summary> /// Extract property metadata from the match expression /// </summary> /// <typeparam name="TEntity">Type of the entity being upserted</typeparam> /// <param name="entityType">Metadata type of the entity being upserted</param> /// <param name="matchExpression">The match expression provided by the user</param> /// <param name="queryOptions">Options for the current query that will affect it's behaviour</param> /// <returns>A list of model properties used to match entities</returns> protected static ICollection <IProperty> ProcessMatchExpression <TEntity>(IEntityType entityType, Expression <Func <TEntity, object> >?matchExpression, RunnerQueryOptions queryOptions) { if (entityType == null) { throw new ArgumentNullException(nameof(entityType)); } List <IProperty> joinColumns; if (matchExpression is null) { joinColumns = entityType.GetProperties() .Where(p => p.IsKey()) .ToList(); } else if (matchExpression.Body is NewExpression newExpression) { joinColumns = new List <IProperty>(); foreach (MemberExpression arg in newExpression.Arguments) { if (arg == null || arg.Member is not PropertyInfo || !typeof(TEntity).Equals(arg.Expression?.Type)) { throw new InvalidOperationException(Resources.MatchColumnsHaveToBePropertiesOfTheTEntityClass); } var property = entityType.FindProperty(arg.Member.Name); if (property == null) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.UnknownProperty, arg.Member.Name)); } joinColumns.Add(property); } } else if (matchExpression.Body is UnaryExpression unaryExpression) { if (unaryExpression.Operand is not MemberExpression memberExp || memberExp.Member is not PropertyInfo || !typeof(TEntity).Equals(memberExp.Expression?.Type)) { throw new InvalidOperationException(Resources.MatchColumnsHaveToBePropertiesOfTheTEntityClass); } var property = entityType.FindProperty(memberExp.Member.Name); if (property == null) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.UnknownProperty, memberExp.Member.Name)); } joinColumns = new List <IProperty> { property }; } else if (matchExpression.Body is MemberExpression memberExpression) { if (!typeof(TEntity).Equals(memberExpression.Expression?.Type) || memberExpression.Member is not PropertyInfo) { throw new InvalidOperationException(Resources.MatchColumnsHaveToBePropertiesOfTheTEntityClass); } var property = entityType.FindProperty(memberExpression.Member.Name); if (property == null) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.UnknownProperty, memberExpression.Member.Name)); } joinColumns = new List <IProperty> { property }; } else { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ArgumentMustBeAnAnonymousObjectInitialiser, "match"), nameof(matchExpression)); } if (!queryOptions.AllowIdentityMatch && joinColumns.Any(p => p.ValueGenerated != ValueGenerated.Never)) { throw new InvalidMatchColumnsException(); } return(joinColumns); }
/// <inheritdoc/> public abstract Task <int> RunAsync <TEntity>(DbContext dbContext, IEntityType entityType, ICollection <TEntity> entities, Expression <Func <TEntity, object> >?matchExpression, Expression <Func <TEntity, TEntity, TEntity> >?updateExpression, Expression <Func <TEntity, TEntity, bool> >?updateCondition, RunnerQueryOptions queryOptions, CancellationToken cancellationToken) where TEntity : class;
private void RunCore <TEntity>(DbContext dbContext, IEntityType entityType, ICollection <TEntity> entities, Expression <Func <TEntity, object> >?matchExpression, Expression <Func <TEntity, TEntity, TEntity> >?updateExpression, Expression <Func <TEntity, TEntity, bool> >?updateCondition, RunnerQueryOptions queryOptions) where TEntity : class { // Find matching entities in the dbContext var matches = FindMatches(entityType, entities, dbContext, matchExpression); Action <TEntity, TEntity>? updateAction = null; Func <TEntity, TEntity, bool>?updateTest = updateCondition?.Compile(); if (updateExpression != null) { // If update expression is specified, create an update delegate based on that if (!(updateExpression.Body is MemberInitExpression entityUpdater)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ArgumentMustBeAnInitialiserOfTheTEntityType, "updater"), nameof(updateExpression)); } var properties = entityUpdater.Bindings.Select(b => b.Member).OfType <PropertyInfo>(); var updateFunc = updateExpression.Compile(); updateAction = (dbEntity, newEntity) => { var tmp = updateFunc(dbEntity, newEntity); foreach (var prop in properties) { var property = entityType.FindProperty(prop.Name); prop.SetValue(dbEntity, prop.GetValue(tmp) ?? property.GetDefaultValue()); } }; } else if (!queryOptions.NoUpdate) { // Otherwise create a default update delegate that updates all non match, non auto generated columns var joinColumns = ProcessMatchExpression(entityType, matchExpression, queryOptions); var properties = entityType.GetProperties() .Where(p => p.ValueGenerated == ValueGenerated.Never) .Select(p => typeof(TEntity).GetProperty(p.Name)) .Where(p => p != null) .Except(joinColumns.Select(c => c.PropertyInfo)); updateAction = (dbEntity, newEntity) => { foreach (var prop in properties) { var property = entityType.FindProperty(prop.Name); prop.SetValue(dbEntity, prop.GetValue(newEntity) ?? property.GetDefaultValue()); } }; } foreach (var(dbEntity, newEntity) in matches) { if (dbEntity == null) { foreach (var prop in typeof(TEntity).GetProperties()) { if (prop.GetValue(newEntity) == null) { var property = entityType.FindProperty(prop.Name); if (property != null) { var defaultValue = property.GetDefaultValue(); if (defaultValue != null) { prop.SetValue(newEntity, defaultValue); } } } } dbContext.Add(newEntity); continue; } if (updateTest?.Invoke(dbEntity, newEntity) == false) { continue; } updateAction?.Invoke(dbEntity, newEntity); } }
/// <inheritdoc/> public abstract int Run <TEntity>(DbContext dbContext, IEntityType entityType, ICollection <TEntity> entities, Expression <Func <TEntity, object> >?matchExpression, Expression <Func <TEntity, TEntity, TEntity> >?updateExpression, Expression <Func <TEntity, TEntity, bool> >?updateCondition, RunnerQueryOptions queryOptions) where TEntity : class;
/// <inheritdoc/> public override Task <int> RunAsync <TEntity>(DbContext dbContext, IEntityType entityType, ICollection <TEntity> entities, Expression <Func <TEntity, object> >?matchExpression, Expression <Func <TEntity, TEntity, TEntity> >?updateExpression, Expression <Func <TEntity, TEntity, bool> >?updateCondition, RunnerQueryOptions queryOptions, CancellationToken cancellationToken) { if (dbContext is null) { throw new ArgumentNullException(nameof(dbContext)); } if (entityType == null) { throw new ArgumentNullException(nameof(entityType)); } RunCore(dbContext, entityType, entities, matchExpression, updateExpression, updateCondition, queryOptions); return(dbContext.SaveChangesAsync(cancellationToken)); }