Devart ChangeConflictException 但值仍写入数据库

Devart ChangeConflictException but values still written to database

我间歇性 Devart.Data.Linq.ChangeConflictException: Row not found or changed 抬起丑陋的头。搞笑的是,变化还是写入了数据库!

堆栈跟踪显示:

Devart.Data.Linq.ChangeConflictException: Row not found or changed. at Devart.Data.Linq.Engine.b4.a(IObjectEntry[] A_0, ConflictMode A_1, a A_2) at Devart.Data.Linq.Engine.b4.a(ConflictMode A_0) at Devart.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode) at Devart.Data.Linq.DataContext.SubmitChanges() at Billing.Eway.EwayInternal.SuccessCustomerRenewal(String username, Bill bill, EwayTransaction transaction) in c:\Users\Ian\Source\Repos\billing-class-library\Billing\Billing\Eway\EwayInternal.cs:line 552 at Billing.Eway.Eway.BillAllUsers() in c:\Users\Ian\Source\Repos\billing-class-library\Billing\Billing\Eway\Eway.cs:line 138

我的 Billing.Eway.EwayInternal.SuccessCustomerRenewal 代码:

    internal static void SuccessCustomerRenewal(string username, Bill bill, EwayTransaction transaction)
{
    // Give them their points!
    ApplyBillToCustomerAccount(username, bill, true);
    BillingEmail.SendRenewalSuccessEmail(username, bill, transaction);

    using (MsSqlDataClassesDataContext msSqlDb = new MsSqlDataClassesDataContext())
    {
        // TODO: Remove this logging
        msSqlDb.Log = new StreamWriter(@"logs\db\" + Common.GetCurrentTimeStamp() + "-MsSQL.txt", true) { AutoFlush = true };

        EwayCustomer ewayCustomer = msSqlDb.EwayCustomers.First(c => c.Username == username);
        ewayCustomer.NextBillingDate = Common.GetPlanExpiry(bill.BillPlan);

        using (MySqlDataContext mySqlDb = new MySqlDataContext())
        {
            // TODO: Remove this logging
            mySqlDb.Log = new StreamWriter(@"logs\db\" + Common.GetCurrentTimeStamp() + "-MySQL.txt", true) { AutoFlush = true };
            BillingMySqlContext.Customer grasCustomer = mySqlDb.Customers.First(c => c.Username == username);

            // Extend their membership date out so that the plan doesn't expire because of a failed credit card charge.
            grasCustomer.MembershipDate =
                ewayCustomer.NextBillingDate.AddDays(1);
            mySqlDb.SubmitChanges();  // <-- This is line 552
        }

        msSqlDb.SubmitChanges();
    }
}

我知道问题出现在 mySqlDb.SubmitChanges() 行,因为该数据库上下文是使用 Devart 的数据库上下文(MySQL 数据库的 Linq 解决方案):另一个上下文使用纯 MS Linq。

不仅将更改写入 MySql DB(内部 using 块),还会写入 MsSql DB(外部 usingusing 块)。但这就是神奇的成功结束的地方。

如果可以的话,我会写一个最小的、完整的和可验证的例子,但奇怪的是我不能

那么,为什么在 Devart.Data.Linq.ChangeConflictException 之后更改会保存到数据库中?当我之前遇到 System.Data.Linq.ChangeConflictException 更改未保存时。


编辑 1:

我现在还包含了 .PDB 文件并获得了异常确切来源的行号确认。

编辑 2:

我现在明白为什么我无法生成 ChangeConflictException,这是怎么回事?
这些是 MembershipDate:_

的属性
[Column(Name = @"Membership_Date", Storage = "_MembershipDate", CanBeNull = false, DbType = "DATETIME NOT NULL", UpdateCheck = UpdateCheck.Never)]

我知道我可以明确地强制我的更改通过以覆盖任何潜在的冲突,但这似乎是不可取的(我不知道我会覆盖什么!)。同样,我可以将提交包裹在 try 块中,然后重试(每次都重新读取)直到成功,但这看起来很笨拙。我应该如何处理这个间歇性问题?

编辑 3:

不是多次调用造成的。此函数由单实例应用程序在一个地方调用。每次 运行 时它都会创建日志条目,并且它们只会被创建一次。从那以后,我将电子邮件调用移到了方法的顶部:电子邮件只发送一次,发生异常,并且数据库更改 仍在进行 .

我相信它与 using 块有关。在通过调试器解决一个不相关的问题时,我进入了 using 块,但在 SubmitChanges() 调用之前停止了执行。并且更改仍然写入数据库。我的理解是 using 块是为了确保资源被清理(连接关闭等),但似乎整个块都在执行。研究的新途径...
但它仍然没有回答 ChangeConflictException 在给定 Devart .

的情况下是如何可能的

编辑 4:

所以我并没有发疯,即使我在 using 块中间结束执行后,数据库更改也确实提交了,但是 .

编辑 5:

根据@Evk 的建议,我包含了一些数据库日志记录(并更新了上面的堆栈跟踪和代码片段)。这个异常的发生率似乎已经下降了,因为它是我实施日志记录后才发生的。以下是更多详细信息:

外部(MS SQL)日志文件:

SELECT TOP (1) [t0].[id], [t0].[Username], [t0].[TokenId], [t0].[PlanId], [t0].[SignupDate], [t0].[NextBillingDate], [t0].[PaymentType], [t0].[RetryCount], [t0].[AccountStatus], [t0].[CancelDate] FROM [dbo].[EwayCustomer] AS [t0] WHERE [t0].[Username] = @p0 -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [dyonis] -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.18408a

(只显示更新显示的SELECT调用(.First()),none)。

内部(MySQL)日志文件:

SELECT t1.Customer_ID, t1.Username, t1.Account_Group, t1.Account_Password, t1.First_Name, t1.Last_Name, t1.Account_Type, t1.Points, t1.PromoPoints, t1.Phone, t1.Cell, t1.Email, t1.Address1, t1.Address2, t1.City, t1.State, t1.Country, t1.Postcode, t1.Membership_Group, t1.Suspend_On_Zero_Points, t1.Yahoo_ID, t1.MSN_ID, t1.Skype_ID, t1.Repurchase_Thresh, t1.Active, t1.Delete_Account, t1.Last_Activity, t1.Membership_Expires_After_x_Days, t1.Membership_Date, t1.auth_name, t1.created_by, t1.created_on, t1.AccountGroup_Points_Used, t1.AccountGroup_Points_Threashold, t1.LegacyPoints, t1.Can_Make_Reservation, t1.Gallery_Access, t1.Blog_Access, t1.Private_FTP, t1.Photometrica, t1.Promo_Code, t1.Promo_Expire_DTime, t1.Gift_FirstName, t1.Gift_LastName, t1.Gift_Email, t1.Gift_Phone, t1.Gift_Active, t1.NoMarketingEmail, t1.Can_Schedule, t1.Refered_By, t1.Q1_Hear_About_Us, t1.Q2_Exp_Level, t1.Q3_Intrests, t1.GIS_DTime_UTC, t1.Membership_Expire_Notice_Sent, t1.Promo_Expire_Notice_Sent, t1.isEncrypted, t1.PlanId FROM grasbill.customers t1 WHERE t1.Username = :p0 LIMIT 1 -- p0: Input VarChar (Size = 6; DbType = AnsiString) [dyonis] -- Context: Devart.Data.MySql.Linq.Provider.MySqlDataProvider Mapping: AttributeMappingSource Build: 4.4.519.0

UPDATE grasbill.customers SET Membership_Date = :p1 WHERE Customer_ID = :key1 -- p1: Input DateTime (Size = 0; DbType = DateTime) [8/3/2016 4:42:53 AM] -- key1: Input Int (Size = 0; DbType = Int32) [7731] -- Context: Devart.Data.MySql.Linq.Provider.MySqlDataProvider Mapping: AttributeMappingSource Build: 4.4.519.0

(显示 SELECT 和更新调用)

所以日志文件并没有真正提供任何关于正在发生的事情的线索,但是 MS SQL 数据库已经更新了! NextBillingDate 字段已正确设置,按照此行:

ewayCustomer.NextBillingDate = Common.GetPlanExpiry(bill.BillPlan);

如果它没有更新,用户将在下一个计时器滴答声(5 分钟后)再次被计费,我可以从日志中看到这并没有发生。

另一个值得注意的有趣的事情是日志文件时间戳。从上面的代码中可以看出,我获取了日志文件名的当前 (UTC) 时间。这是 Windows 文件资源管理器显示的信息:

MS SQL 日志文件创建于 04:42 (UTC),最后修改于 14:42(UTC+10,Windows 本地时间),但是MySQL 日志文件最后一次修改时间为 15:23 (UTC+10),创建后 41 分钟。现在我假设日志文件 StreamWriter 在离开范围后立即关闭。这种延迟是异常的预期副作用吗?垃圾收集器是否花了 41 分钟才意识到我不再需要对 StreamWriter 的引用?还是有其他事情发生?

好吧,6 个月后,我终于弄清了这个问题的真相。不确定它是否会对其他人有所帮助,但无论如何我都会详细说明。

这里有 2 个问题,其中 1 个是愚蠢的(通常都是这样),但有一个是我不知道或没有预料到的。

问题 1

即使出现异常也神奇地对数据库进行更改的原因是因为该函数 ApplyBillToCustomerAccount(username, bill, true); 中的 第一行 代码更新了数据库! <脸掌>

问题 2

(Devart) ChangeConflictException 不仅在数据发生变化时抛出,而且 also if you're not making any changes 也抛出。 MS SQL 可以非常精确地存储 DateTime 秒,但是 MySQL(或者我至少是 运行 的那个)只能存储 。这就是间歇性出现的地方。如果我的数据库调用足够快,或者刚好接近第二个边界,它们都会同时四舍五入。 Devart 看到没有要写入的更改,并抛出 ChangeConflictException.

我最近对数据库进行了一些优化,从而提高了响应能力,并且大大增加了此异常的发生率。这就是线索之一。

我还尝试按照链接的 Devart post 中的说明将 Found Rows 参数更改为 true,但发现它对我的情况没有帮助。或者也许我做错了。不管怎样,现在我已经找到了问题的根源,我可以消除重复的数据库更新。