C# Linq2Sql更改跟踪不工作

C# Linq2Sql更改跟踪不工作,c#,sql-server,linq-to-sql,sqltransaction,C#,Sql Server,Linq To Sql,Sqltransaction,我正在运行下面的代码,根据每天早上发送给我们的银行交易历史文件更新一些记录。这是非常基本的东西,但由于某种原因,当我讲到最后时,dbContext.GetChangeSet()报告所有操作的“0” public void ProcessBatchFile(string fileName) { List<string[]> failed = new List<string[]>(); int recCount = 0; DateTime dtStar

我正在运行下面的代码,根据每天早上发送给我们的银行交易历史文件更新一些记录。这是非常基本的东西,但由于某种原因,当我讲到最后时,
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);
}

我刚刚发现了问题:我的DBA在为我创建表时没有在表上放置主键,因此LinqToSql没有在实体类中生成任何“PropertyChanged”事件/处理程序,这就是为什么DataContext不知道正在进行更改。显然,如果您的表没有主键,Linq2Sql将不会跟踪对该表的任何更改,这是有意义的,但如果有某种通知,那就更好了。我确信我的DBA没有考虑到这一点,因为这只是一种“跟踪”文本文件中的哪些行项目已被处理,并且与任何其他表没有直接关系的方式。

catch(ObjectDisposedException){}
正在吞噬严重的bug。修复错误。不要隐藏错误消息。此错误处理被微妙地破坏。如果您使一个实体无效,并且它在写入时失败,那么将来的所有写入操作也将失败,因为该实体被卡在上下文中。我建议您放弃那些事务助手方法。通常的
使用(var-tran=BeginTran()){tran.Commit();}
就是您所需要的。做同样的事情,但更简单。我正在维护一个100kloc的代码库,它大量使用L2和事务。不过,事务使用的是
TransactionScope
,但这应该是类似的。关于隐藏错误,我认为它是允许使用错误API的代码气味。你也可以用这种方式隐藏真正的bug。;运行一个实验以确认可以进行任何更改。添加
batchRecords.First().SomeProperty=1234;db.SubmitChanges()。这能通过吗?应该这样。然后可以移动该线并测试不同的位置。这是模糊的,但我们几乎没有选择:)需要调查。我想问题归结为“当我设置实体属性时,更改从未写入”。对的这意味着我们可以忽略发布代码中的几乎所有内容。
/// <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; }
}