【问题标题】:Replace a SQL process that uses MERGE with LINQ in C# code在 C# 代码中用 LINQ 替换使用 MERGE 的 SQL 进程
【发布时间】:2021-02-13 05:22:53
【问题描述】:

我需要将一个进程从 SQL Server 传输到我的应用程序代码。 T-SQL 进程使用 MERGE,如下所示,有条件地更新或插入。

-- Synchronize the InterestRates table with refreshed/new data from ImportRates_Stg table
MERGE InterestRates AS TARGET

USING ImportRates_Stg AS SOURCE

ON (TARGET.Effective = SOURCE.EffectiveDate)

-- When records are matched on the Effective date, update the records if there is any change to the Rate
WHEN MATCHED AND TARGET.Effective = SOURCE.EffectiveDate
    THEN UPDATE SET TARGET.Rate = SOURCE.Rate
-- When no records are matched on the Effective date,
-- insert the incoming records from ImportRates_Stg table to InterestRates table
WHEN NOT MATCHED BY TARGET
    THEN INSERT (Effective, Rate) VALUES (SOURCE.EffectiveDate, Rate);

我需要在 C# 中重现此功能,并且认为 LINQ 可能是实现此目的的最佳方式,但到目前为止,我的所有尝试都失败了。这是我到目前为止的代码。将数据从 excel 文件导入到列表中一切正常。当我到达实际逻辑来替换不起作用的 SQL MERGE 时......

        public async Task<IActionResult> OnPostAsync()
    {
        // Perform an initial check to catch FileUpload class attribute violations.
        if (!ModelState.IsValid)
        {
            return Page();
        }

        string filePath = RatesBatchImportFilepath + Path.GetFileName(Request.Form.Files["RatesExtract"].FileName);

        using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
        {
            await Request.Form.Files["RatesExtract"].CopyToAsync(fileStream);
        }

        var newRates = new List<InterestRate>();

        using (var wb = new XLWorkbook(filePath, XLEventTracking.Disabled))
        {
            var ws = wb.Worksheet(1);
            DataTable dataTable = ws.RangeUsed().AsTable().AsNativeDataTable();

            if (dataTable.Rows.Count > 0)
            {
                foreach (DataRow dataRow in dataTable.Rows)
                {
                    if (dataRow.ItemArray.All(x => string.IsNullOrEmpty(x?.ToString()))) continue;

                    newRates.Add(new InterestRate()
                    {
                        Effective = Convert.ToDateTime(dataRow["PeriodEndingDate"]),
                        Rate = Convert.ToDecimal(dataRow["AY01NetPerf"])
                    });
                };
            }
        }

        IQueryable<InterestRate> existingRates = from s in _context.InterestRates
                                            orderby s.Effective descending
                                            select s;

        foreach (var oldRate in existingRates)
        {
            DateTime ourDate = oldRate.Effective;

            var thisUpdateQuery =
                from thisRate in newRates
                where thisRate.Effective == ourDate
                select thisRate;

            foreach (var rate in thisUpdateQuery)
            {
                oldRate.Effective = rate.Effective;

                newRates.Remove(rate); // this causes an error.
            }
        }

        foreach (var rate in newRates)
        {
            Rates.Add(rate);
        }

        _context.SaveChanges();

        return RedirectToPage("./Index");
    }

这是错误:InvalidOperationException:集合已修改;枚举操作可能无法执行。

【问题讨论】:

  • 它抛出了什么错误?
  • 我更新了问题以包含我遇到的错误。
  • EF 将不得不单独执行 INSERTs 和 UPDATEs 来保存这些更改,更不用说您为获取数据而执行的查询以进行分析。 MERGE 将更有效率,因为它在集合上运行,而且它在语义上更加清晰,因为它简洁地描述了何时插入、何时更新以及为每个操作做什么。这段 C# 代码过多地处理了操作机制(现在从数据库服务器中离开)并且模糊了期望的结果。我建议使用原始 MERGE 语句进行原始 SQL 查询。
  • 如果你ToList()你的thisUpdateQuery会发生什么? foreach (var rate in thisUpdateQuery.ToList())

标签: c# sql-server visual-studio asp.net-core


【解决方案1】:

这里是一个合并函数,包含所有选项,例如在 sql 中合并:

    public static async Task Merge<T>(this List<T> target, List<T> source, Func<T, T, bool> mergeOn, Func<T, T, Task> onMatched = null, Func<List<T>, Task> whenNotMatchedByTarget = null, Func<List<T>, Task> whenNotMatchedBySource = null)
    {
        var sourceTemp = JsonConvert.DeserializeObject<List<T>>(JsonConvert.SerializeObject(source, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }));
        var notMatchedByTarget = new List<T>();
        bool isMatched;
        for (int i = 0; i < target?.Count; i++)
        {
            isMatched = false;
            for (int j = 0; j < sourceTemp?.Count; j++)
            {
                if (mergeOn(target[i], sourceTemp[j]))
                {
                    if (onMatched != null)
                        await onMatched(target[i], sourceTemp[j]);
                    sourceTemp.RemoveAt(j);
                    isMatched = true;
                    break;
                }
            }

            if (!isMatched)
                notMatchedByTarget.Add(target[i]);
        }

        if (whenNotMatchedByTarget != null)
            await whenNotMatchedByTarget(notMatchedByTarget);
        if (whenNotMatchedBySource != null)
            await whenNotMatchedBySource(sourceTemp);
    }

然后像这样使用它:-

await targetList.Merge(source: sourceList,
mergeOn: (t, s) => t.Id == s.Id,
onMatched: Update,
whenNotMatchedByTarget: Delete,
whenNotMatchedBySource: Add);


Task Update(sourceType old, sourceType new){};
Task Delete(List<sourceType> listToBeDeleted){};
Task Add(List<sourceType> listToBeAdded){};

【讨论】:

  • 欢迎来到stackoverflow!如果你能解释你的答案,特别是指出是什么让你的答案比现有答案更好,那就太好了。
  • 与完整选项合并
  • @RavenCohoSalomé 如果可行,请将答案标记为已接受。
【解决方案2】:

这是我认为可行的扩展方法 --

    public static List<T> Merge<T>(this List<T> list, Func<T, T, bool> mergeOnFunc, Action<T, T> ifMatchedByTargetAction)
    {
        List<T> mergedList = new List<T>();
        list.ForEach(rec =>
        {
            if (!mergedList.Any(x => mergeOnFunc(rec, x)))
                mergedList.Add(rec); // if the record hasn't been added yet, add it
            else // if the record has already been added, merge the changes
                ifMatchedByTargetAction(mergedList.Where(x => mergeOnFunc(rec, x)).First(), rec);
        });
        return mergedList;
    }

这是一个如何使用扩展方法的示例(假设您已经将所有可合并对象批处理到一个带有 concat 或类似内容的列表中 - 抱歉,这不是一个理想的用例,但它是我有一个)。

            classAttendanceList = classAttendanceList.Merge(
                (a, b) => { return a.StudentName == b.StudentName; }, // this is your "ON"
                (a, b) => { a.DaysAttended += b.DaysAttended; }); // this is your "WHEN MATCHED"

【讨论】:

    猜你喜欢
    • 2014-02-10
    • 1970-01-01
    • 2015-05-09
    • 2010-09-07
    • 2013-07-10
    • 1970-01-01
    • 2011-07-05
    • 2014-09-16
    • 2011-05-04
    相关资源
    最近更新 更多