0

I'm running the code below but getting an exception on YogabandyFee when running it.

var payoutResult = await _dbContext.StripePayouts
    .Where(p => p.StripePayoutId == stripePayoutId)
    .Select(p => new { p.GrossAmount, p.TotalTokens, p.NetAmount, p.ReconciledTransfers, p.EditedDate, p.YogabandyFee })
.ExecuteUpdateAsync(setters => setters
    .SetProperty(p => p.GrossAmount, grossAmount)
    .SetProperty(p => p.TotalTokens, totalTokens)
    .SetProperty(p => p.YogabandyFee, p => new YogabandyFee { TotalFee = (grossAmount - p.NetAmount - platformFee) } )
    .SetProperty(p => p.ReconciledTransfers, true)
    .SetProperty(p => p.EditedDate, DateTime.UtcNow)
);

Can this be done? Can I create a new entry in this type of update?

Here is the error:

System.InvalidOperationException: The LINQ expression 'DbSet() .Where(s => s.StripePayoutId == __stripePayoutId_0) .LeftJoin( inner: DbSet(), outerKeySelector: s => EF.Property(s, "Id"), innerKeySelector: y => EF.Property(y, "StripePayoutId"), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )) .Select(s => new { GrossAmount = s.Outer.GrossAmount, TotalTokens = s.Outer.TotalTokens, NetAmount = s.Outer.NetAmount, ReconciledTransfers = s.Outer.ReconciledTransfers, EditedDate = s.Outer.EditedDate, YogabandyFee = s.Inner }) .ExecuteUpdate(setters => setters.SetProperty( propertyExpression: p => p.GrossAmount, valueExpression: __grossAmount_1).SetProperty( propertyExpression: p => p.TotalTokens, valueExpression: __totalTokens_2).SetProperty( propertyExpression: p => p.ReconciledTransfers, valueExpression: True).SetProperty( propertyExpression: p => p.EditedDate, valueExpression: DateTime.UtcNow).SetProperty( propertyExpression: p => p.YogabandyFee, valueExpression: p => new YogabandyFee{ TotalFee = __grossAmount_1 - p.NetAmount - __platformFee_3 } ))' could not be translated. Additional information: The following lambda argument to 'SetProperty' does not represent a valid property to be set: 'p => p.YogabandyFee'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression) at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_01.b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.ExecuteUpdateAsync[TSource](IQueryable1 source, Expression1 setPropertyCalls, CancellationToken cancellationToken) at Functions.ReconcileTransfers.Run(String myQueueItem) in /Users/charles/yogabandy2020/Functions/ReconcileTransfers.cs:line 119

8
  • is your use case to update one object? Or are you trying to update multiple objects. Commented Jul 5, 2024 at 2:43
  • When I update the object StripePayout I also want to create a new child object YogabandyFee, but I don't think this is possible, so I adjusted my code accordingly Commented Jul 6, 2024 at 3:05
  • It looks like you are trying to only update one StripePayout. Even though, you want to add a child object to it, it’s still the same object. Have you tried getting the object from entity framework, updating the object in c# and then use the UpdateAsync method + SaveAsync in entity framework? Commented Jul 6, 2024 at 23:00
  • var payoutResult = await _dbContext.StripePayouts .FirstOrDefault(p => p.StripePayoutId == stripePayoutId); Commented Jul 6, 2024 at 23:02
  • payoutResult.Yogabandy = new YogabandyFee { TotalFee = (grossAmount - p.NetAmount - platformFee); Commented Jul 6, 2024 at 23:03

2 Answers 2

4

ExecuteUpdateAsync will try to create a SQL query based on the c# code you write. In the c# code, you are trying to update two tables at the same time in the same query. It's not possible. Even with writing your own SQL, most DMBS, don't allow it. (with exception of MySQL)

From your error message

The following lambda argument to 'SetProperty' does not represent a valid property to be set: 'p => p.YogabandyFee'

it seems like the parser is looking for a property instead of an object, so, hence, I tried to set the TotalFee of the YogabandyFee:

var payoutResult = await _dbContext.StripePayouts
            .Where(p => p.StripePayoutId == stripePayoutId)
            .Select(p => new { p.GrossAmount, p.TotalTokens, p.NetAmount, p.ReconciledTransfers, p.EditedDate, p.YogabandyFee })
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(p => p.GrossAmount, grossAmount)
                .SetProperty(p => p.TotalTokens, totalTokens)
                .SetProperty(p => p.YogabandyFee.TotalFee, p => grossAmount - p.NetAmount - platformFee )
                .SetProperty(p => p.ReconciledTransfers, true)
                .SetProperty(p => p.EditedDate, DateTime.UtcNow)
            );

And you'll see this error:

Multiple 'SetProperty' invocations refer to different tables ('p => p.YogabandyFee.TotalFee' and 'p => p.EditedDate'). A single 'ExecuteUpdate' call can only update the columns of a single table.

What I'm trying to say is: ExecuteUpdate can only update one table. I think, the answer to "Can I create a new entry in this type of update?" is no.

My suggestions:

  1. Use stored procedures to update both tables at once (https://stackoverflow.com/a/5154670/17431983); tools like Dapper allow you run a stored procedure.
  2. Use UnitOfWork - this allows you to do multiple _dbContext queries in the same transaction

You can also do two separate ExecuteUpdateAsync, one for each table update, if it's not a performance issue.


If you are updating only one StripePayout object and it's small, I think using SaveChanges() suits more.

class Program
{
    static async Task Main(string[] args)
    {
        // await FillDatabase();
        await UpdateStripePayout(1, 1000m, 50, 50m);
    }

    private static async Task FillDatabase()
    {
        using var _dbContext = new AppDbContext();
        
        await _dbContext.StripePayouts.AddAsync(new StripePayout()
        {
            EditedDate = new DateTime(),
            GrossAmount = new decimal(123.4),
            NetAmount = new decimal(123.4),
            ReconciledTransfers = false,
            StripePayoutId = 1,
            TotalTokens = 10,
            YogabandyFee = new YogabandyFee()
        });
        
        await _dbContext.SaveChangesAsync();
    }

    public static async Task UpdateStripePayout(int stripePayoutId, decimal grossAmount, int totalTokens, decimal platformFee)
    {
        using var _dbContext = new AppDbContext();
        
        var payoutResult = await _dbContext.StripePayouts
                .FirstOrDefaultAsync(p => p.StripePayoutId == stripePayoutId);

        if (payoutResult == null)
        {
            return; // You can raise an error here if you like
        }
        
        payoutResult.GrossAmount = grossAmount;
        payoutResult.TotalTokens = totalTokens;
        payoutResult.ReconciledTransfers = true;
        payoutResult.EditedDate = DateTime.UtcNow;
        payoutResult.YogabandyFee = new YogabandyFee()
            { TotalFee = (grossAmount - payoutResult.NetAmount - platformFee) };

        _dbContext.Update(payoutResult);
        await _dbContext.SaveChangesAsync();

        var newResult = await _dbContext.StripePayouts.FirstAsync(p => p.StripePayoutId == stripePayoutId);
        Console.WriteLine(newResult.YogabandyFee.TotalFee);
    }
}

I also found this on Microsoft docs: https://learn.microsoft.com/en-us/ef/core/saving/

If you need to manipulate a graph of objects (i.e. multiple interconnected objects), use SaveChanges; it will figure out the proper ordering of the changes and how to link everything together. "I want to update a blog, changing some of its posts and deleting others"

Sign up to request clarification or add additional context in comments.

Comments

1

System.InvalidOperationException exception is thrown because the ExecuteUpdateAsync method in EF doesn`t support creating or assigning new entities directly in the update operation. Therefore what you can do is first update the main entity and then update the 'YogabandyFee'.

1 Comment

Is there anyway to do it in a single call? Because if I update the main entity using ExecuteUpdateAsync I don't get the id back, I would then have to go a fetch the id or not use ExecuteUpdateAsync. Then I would have to make another call to update YogabandyFee I'm just trying to keep the number of calls to a min.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.