如何将多个值传递给 EF 7 中的 ExecuteUpdate 的 SetPropertyCalls。

huangapple go评论61阅读模式
英文:

How to pass multiple values to SetPropertyCalls for ExecuteUpdate in EF 7

问题

以下是要翻译的代码部分:

我有以下查询:

```csharp
IQueryable<Location> query = unitOfWork.LocationRepository.Query()
    .Where(x => x.ID == "{some-id}");

我想要使用以下值更新Location属性:

List<(Func<Location, object>, object)> parameters = new List<(Func<Location, object>, object)>
{
    (x => x.Name, "New Name"), // 字符串
    (x => x.Date, DateTime.Now) // 日期时间
};

如何根据上面的列表动态构建SetProperty?

query.ExecuteUpdate(sett => sett.SetProperty(... 这里??? ...));

我从简单的开始:

Func<Location, string> func = x => x.Name;

query.ExecuteUpdate(sett => sett.SetProperty(func, "abc123"));

但这会失败,我会收到System.InvalidCastException:'无法将类型为'System.Linq.Expressions.TypedParameterExpression'的对象强制转换为类型'System.Linq.Expressions.LambdaExpression'。

当我直接使用Lambda时:

// 这样可以工作
query.ExecuteUpdate(sett => sett.SetProperty(x => x.Name, "abc123"));

那么如何将多个Func<>传递给SetProperty?

更新 7/7:添加了上下文:

我正在创建一个扩展方法来提高应用程序的性能。
与加载实体,更改单个属性并保存它相比,我希望快速设置属性值并使用EF7 ExecuteUpdate()快速保存它。
DbContext.SaveChanges()中实现了一些逻辑,用于在保存时更新实体(设置当前用户名、最后更新时间等...),我希望在ExecuteUpdate()中也能实现相同的功能。
我期望在同一时间保存多个属性(最多5个以保持效率),这就是我需要一个列表的原因。

public static async Task&lt;int&gt; ExecuteUpdateCustom&lt;T, V&gt;(this IQueryable&lt;T&gt; source,
    List&lt;(Func&lt;T, V&gt; propertyExpression, V value)&gt; propertiesAndValuesSet) where T : class
{
    if (source is IHasUserDetails)
    {
        List&lt;(Func&lt;T, V&gt;, V)&gt; parameters = new List&lt;(Func&lt;T, V&gt;, V)&gt;
        {
            (x =&gt; x.UpdatedBy, getCurrentUser()),
            (x =&gt; x.ModifiedTime, DateTime.UtcNow)
        };

        // 链接
        propertiesAndValuesSet.AddRange(parameters);
    }

    return await source.ExecuteUpdateAsync(setters =&gt; setters.SetProperty(...propertiesAndValuesSet...));
}
英文:

I have the following query:

           IQueryable&lt;Location&gt; query = unitOfWork.LocationRepository.Query()
                .Where(x =&gt; x.ID == &quot;{some-id}&quot;);

I want to update the Location properties with values:

            List&lt;(Func&lt;Location, object&gt;, object)&gt; parameters = new List&lt;(Func&lt;Location, object&gt;, object)&gt;
            {
                (x =&gt; x.Name, &quot;New Name&quot;), // string
                (x =&gt; x.Date, DateTime.Now) // DateTime
            };

How to build SetProperty dynamically from the list above?

            query.ExecuteUpdate(sett =&gt; sett.SetProperty(... here??? ...));

I started with simple:

Func&lt;Location, string&gt; func = x =&gt; x.Name;

            query.ExecuteUpdate(sett =&gt; sett.SetProperty(func, &quot;abc123&quot;));

But this fails and I get System.InvalidCastException: 'Unable to cast object of type 'System.Linq.Expressions.TypedParameterExpression' to type 'System.Linq.Expressions.LambdaExpression'.'

When I use lambda directly:


// This works
query.ExecuteUpdate(sett =&gt; sett.SetProperty(x =&gt; x.Name, &quot;abc123&quot;));

So how to pass multiple Func<> to the SetProperty?

Update 7/7: Context added:

I am creating an extension method to improve the performance of the app.
Instead of loading entity, changing single property and saving it I want to set property value and save it quickly with EF7 ExecuteUpdate().
There is some logic implemented in DbContext.SaveChanges() which updates the entity when saving (setting current user name, last updated time, ...) and I want the same for ExecuteUpdate().
I expect several properties to be saved in the same time (max. 5 to have it efficient), that's why I need a List.

public static async Task&lt;int&gt; ExecuteUpdateCustom&lt;T, V&gt;(this IQueryable&lt;T&gt; source,
    List&lt;(Func&lt;T, V&gt; propertyExpression, V value)&gt; propertiesAndValuesSet) where T : class
{
    if (source is IHasUserDetails)
    {
        List&lt;(Func&lt;T, V&gt;, V)&gt; parameters = new List&lt;(Func&lt;T, V&gt;, V)&gt;
        {
            (x =&gt; x.UpdatedBy, getCurrentUser()),
            (x =&gt; x.ModifiedTime, DateTime.UtcNow)
        };

        // Chain
        propertiesAndValuesSet.AddRange(parameters);
    }

    return await source.ExecuteUpdateAsync(setters =&gt; setters.SetProperty(...propertiesAndValuesSet...));
}

答案1

得分: 2

以下是翻译好的内容:

你可以像这样声明列表:

List&lt;Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt;&gt; parameters = new()
{
    { sett =&gt; sett.SetProperty(x =&gt; x.Name, &quot;New Name&quot;) }, // string
    { sett =&gt; sett.SetProperty(x =&gt; x.Date, DateTime.Now) }, // DateTime
};

将它们组合起来有点棘手。你基本上需要为每个表达式制造一个新的 Expression,它执行 return firstExpression(secondExpression);

我认为以下代码应该可以工作:

Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt; CombineSetters(
    IEnumerable&lt;Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt;&gt; setters
)
{
	Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt; expr = sett =&gt; sett;

    foreach (var expr2 in setters)
    {
        var call = (MethodCallExpression)expr2.Body;
        expr = Expression.Lambda&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt;(
            Expression.Call(expr.Body, call.Method, call.Arguments),
            expr2.Parameters
        );
    }

    return expr;
}

然后你可以这样做:

await source.ExecuteUpdateAsync(finalExpression);
英文:

You can declare the list like this

List&lt;Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt;&gt; parameters = new()
{
    { sett =&gt; sett.SetProperty(x =&gt; x.Name, &quot;New Name&quot;) }, // string
    { sett =&gt; sett.SetProperty(x =&gt; x.Date, DateTime.Now) }, // DateTime
};

Combining them is a little tricky. you essentially need to manufacture a new Expression for each of those, which does return firstExpression(secondExpression);

I think the following code should work:

Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt; CombineSetters(
    IEnumerable&lt;Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt;&gt; setters
)
{
	Expression&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt; expr = sett =&gt; sett;

    foreach (var expr2 in setters)
    {
        var call = (MethodCallExpression)expr2.Body;
        expr = Expression.Lambda&lt;Func&lt;SetPropertyCalls&lt;TSource&gt;, SetPropertyCalls&lt;TSource&gt;&gt;&gt;(
            Expression.Call(expr.Body, call.Method, call.Arguments),
            expr2.Parameters
        );
    }

    return expr;
}

You can then do

await source.ExecuteUpdateAsync(finalExpression);

huangapple
  • 本文由 发表于 2023年7月6日 20:30:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76628860.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定