Linq2Sql 更改跟踪不起作用

Linq2Sql Change Tracking Not Working

我 运行 下面的代码根据每天早上发送给我们的银行交易历史文件更新一些记录。这是非常基本的东西,但出于某种原因,当我到达终点时,dbContext.GetChangeSet() 报告所有操作为“0”。

public void ProcessBatchFile(string fileName)
{
    List<string[]> failed = new List<string[]>();
    int recCount = 0;
    DateTime dtStart = DateTime.Now;
    using (ePermitsDataContext dbContext = new ePermitsDataContext())
    {
        try
        {
            // A transaction must be begun before any data is read.
            dbContext.BeginTransaction();
            dbContext.ObjectTrackingEnabled = true;

            // Load all the records for this batch file.
            var batchRecords = (from b in dbContext.AmegyDailyFiles
                                where b.FileName == fileName
                                && b.BatchProcessed == false
                                && (b.FailReason == null || b.FailReason.Trim().Length < 1)
                                select b);

            // Loop through the loaded records
            int paymentID;
            foreach (var r in batchRecords)
            {
                paymentID = 0;
                try
                {
                    // We have to 'parse' the primary key, since it's stored as a string value with leading zero's.
                    if (!int.TryParse(r.TransAct.TrimStart('0'), out paymentID))
                        throw new Exception("TransAct value is not a valid integer: " + r.TransAct);

                    // Store the parsed, Int32 value in the original record and read the "real" record from the database.
                    r.OrderPaymentID = paymentID;
                    var orderPayment = this.GetOrderPayment(dbContext, paymentID);

                    if (string.IsNullOrWhiteSpace(orderPayment.AuthorizationCode))
                        // If we haven't processed this payment "Payment Received" do it now.
                        this.PaymentReceived(orderPayment, r.AuthorizationNumber);

                    // Update the PaymentTypeDetailID (type of Credit Card--all other types will return NULL).
                    var paymentTypeDetail = dbContext.PaymentTypes.FirstOrDefault(w => w.PaymentType1 == r.PayType);
                    orderPayment.PaymentTypeDetailID = (paymentTypeDetail != null ? (int?)paymentTypeDetail.PaymentTypeID : null);

                    // Match the batch record as processed.
                    r.BatchProcessed = true;
                    r.BatchProcessedDateTime = DateTime.Now;
                    dbContext.SubmitChanges();
                }
                catch (Exception ex)
                {
                    // If there's a problem, just record the error message and add it to the "failed" list for logging and notification.
                    if (paymentID > 0)
                        r.OrderPaymentID = paymentID;
                    r.BatchProcessed = false;
                    r.BatchProcessedDateTime = null;
                    r.FailReason = ex.Message;
                    failed.Add(new string[] { r.TransAct, ex.Message });
                    dbContext.SubmitChanges();
                }
                recCount++;
            }

            dbContext.CommitTransaction();
        }
        // Any transaction will already be commited, if the process completed successfully.  I just want to make
        //   absolutely certain that there's no chance of leaving a transaction open.
        finally { dbContext.RollbackTransaction(); }
    }

    TimeSpan procTime = DateTime.Now.Subtract(dtStart);

    // Send an email notification that the processor completed.
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    sb.AppendFormat("<p>Processed {0} batch records from batch file '{1}'.</p>", recCount, fileName);
    if (failed.Count > 0)
    {
        sb.AppendFormat("<p>The following {0} records failed:</p>", failed.Count);
        sb.Append("<ul>");
        for (int i = 0; i < failed.Count; i++)
            sb.AppendFormat("<li>{0}: {1}</li>", failed[i][0], failed[i][1]);
        sb.Append("<ul>");
    }
    sb.AppendFormat("<p>Time taken: {0}:{1}:{2}.{3}</p>", procTime.Hours, procTime.Minutes, procTime.Seconds, procTime.Milliseconds);
    EMailHelper.SendAdminEmailNotification("Batch Processing Complete", sb.ToString(), true);
}

dbContext.BeginTransaction() 方法是我添加到 DataContext 中的东西,只是为了方便使用显式事务。我非常有信心这不是问题所在,因为它已在应用程序的其他地方广泛使用。我们的数据库设计使得有必要对一些特定操作使用显式事务,而对 "PaymentReceived" 的调用恰好是其中之一。

我已经检查了代码并确认交易本身的 Rollback() 方法没有被调用,我还检查了调用 CommitTransaction() 之前的 dbContext.GetChangeSet()发生相同的结果。

为了清楚起见,我在下面包含了 BeginTransaction()CommitTransaction()RollbackTransaction() 方法主体。

/// <summary>
/// Begins a new explicit transaction on this context.  This is useful if you need to perform a call to SubmitChanges multiple times due to "circular" foreign key linkage, but still want to maintain an atomic write.
/// </summary>
public void BeginTransaction()
{
    if (this.HasOpenTransaction)
        return;

    if (this.Connection.State != System.Data.ConnectionState.Open)
        this.Connection.Open();

    System.Data.Common.DbTransaction trans = this.Connection.BeginTransaction();
    this.Transaction = trans;
    this._openTrans = true;
}
/// <summary>
/// Commits the current transaction (if active) and submits all changes on this context.
/// </summary>
public void CommitTransaction()
{
    this.SubmitChanges();
    if (this.Transaction != null)
        this.Transaction.Commit();
    this._openTrans = false;
    this.RollbackTransaction(); // Since the transaction has already been committed, this just disposes and decouples the transaction object itself.
}
/// <summary>
/// Disposes and removes an existing transaction on the this context.  This is useful if you want to use the context again after an explicit transaction has been used.
/// </summary>
public void RollbackTransaction()
{
    // Kill/Rollback the transaction, as necessary.
    try
    {
        if (this.Transaction != null)
        {
            if (this._openTrans)
                this.Transaction.Rollback();
            this.Transaction.Dispose();
            this.Transaction = null;
        }
        this._openTrans = false;
    }
    catch (ObjectDisposedException) { } // If this gets called after the object is disposed, we don't want to let it throw exceptions.
    catch { throw; }
}

我刚发现问题:我的 DBA 在为我创建主键时没有在 table 上放置主键,因此 LinqToSql 没有生成任何 "PropertyChanged" event/handler 实体 class 中的内容,这就是 DataContext 不知道正在进行更改的原因。显然,如果您的 table 没有主键,Linq2Sql 将不会跟踪对该 table 的任何更改,这是有道理的,但如果有某种通知就更好了。我确定我的 DBA 没有考虑过它,因为这只是 "tracking" 文本文件中的哪些行项目已被处理并且不直接与任何其他行项目相关的一种方式 tables.