代码之家  ›  专栏  ›  技术社区  ›  Matt Mitchell

如何强制LINQ到SQL始终在UPDATE语句中包含列?

  •  1
  • Matt Mitchell  · 技术社区  · 14 年前

    我有一个用于审计的触发器,它要求在每个update语句中都存在审计列。

    但是,LINQ to SQL只会发送更新中已更改的列。

    考虑到有时同一个用户可能编辑一个列(因此“UpdatedBy”的审计值将相同),LINQ to SQL在尝试更新时将遇到触发错误。

    我在反射器里发现了 SubmitChanges 在里面 DataContext 正在使用 ChangeTracker ,但我无法确定是否有任何好的方法可以说服LINQ to SQL该列已经更改,因此应该包括在内。最糟糕的解决方案是在update语句中包含所有字段,而不管更改是什么。然而 变更跟踪器 参考号埋在 数据上下文 不允许我们注射自己的 变更跟踪器 .

    我曾考虑过分离和重新连接实体的想法,但这似乎充其量是复杂的。如果有人有主意,我会很感激的。干杯。

    1 回复  |  直到 14 年前
        1
  •  1
  •   Matt Mitchell    14 年前

    最简单的解决方案:

    您必须对所有列禁用并发检查(UpdateCheck.Never)(您可以在设计器中按住shift键单击它,或者在使用T4模板时修改模板)。或者,您必须复制下面示例中的每个字段(可以生成模板方法来执行此操作)。

    无论如何:

    MyDataContext db = new MyDataContext();
    MyDataContext db2 = new MyDataContext();
    Car betsy = db.Cars.First(c => c.Name == "Betsy");
    Car betsy2 = new Car();
    betsy2.Id= betsy.Id;
    db2.Cars.Attach(betsy2);
    
    /* Could be GetName() or whatever, but allows same */
    betsy2.UpdatedBy = betsy.UpdatedBy;  
    betsy2.OtherField = "TestTestTest";   
    db2.SubmitChanges();
    

    下面还有一些其他的“解决方案”。不幸的是,所有这些都涉及到一个双重更新/随便什么,如果你正在执行一个删除操作,就不会真正起作用。当您在审计时,可能正在备份到另一个表中,在您得到第二个非“|”或其他东西(如果它总是在事务中,可能不是世界末日)时,您可能希望在触发器中用“|”来更新最后一个审计条目。不过,这一切都不理想。

    混乱的解决方案:

    MyDataContext db = new MyDataContext();
    Car car = db.Cars.First(c => c.Id == 1);
    car.Name = "Betsy";
    car.UpdatedBy = String.Format("{0}|{1}", car.UpdatedBy, DateTime.Ticks);
    db.SubmitChanges();
    
    car.UpdatedBy = car.UpdatedBy.Substring(0, car.UpdatedBy.LastIndexOf('|'));
    db.SubmitChanges();
    

    稍微好一点的解决方案是:

    public partial class MyDataContext : DataContext
    {
        public override void SubmitChanges(ConflictMode failureMode)
        {
            ChangeSet cs = base.GetChangeSet();
            foreach (object e in cs.Updates.Union(cs.Inserts))
            {
                PropertyInfo updatedBy = e.GetType()
                    .GetProperties()
                    .FirstOrDefault(p => p.Name == "UpdatedBy");
    
                if (updatedBy == null)
                {
                    base.SubmitChanges(failureMode);
                    return;
                }
    
                string updatedByValue = updatedBy.GetValue(e, null);
                string tempValue = String.Format("{0}|{1}", updatedByValue, DateTime.Ticks;
                updatedBy.SetValue(e, tempValue);
                base.SubmitChanges(failureMode);
    
                updatedBy.SetValue(e, tempValue.Substring(0, tempValue.LastIndexOf('|')));
                base.SubmitChanges(failureMode);
            }
        }
    }
    

    我找到了这种类型的最佳解决方案(如果您使用的是用于LINQ to SQL的T4模板,这就更简单了):

    为每个被审计实体类型创建一个实现公共接口的部分类文件,或者修改模板,以便被审计实体实现公共接口,例如:

    public interface IAuditable
    {
        string UpdatedBy { get; set; }
    }
    

    然后按如下方式修改您的提交更改:

    public partial class MyDataContext : DataContext
    {
        public override void SubmitChanges(ConflictMode failureMode)
        {
            ChangeSet cs = base.GetChangeSet();
            foreach (object e in cs.Updates.Union(cs.Inserts))
            {
                if (typeof(IAuditable).IsAssignableFrom(e))
                {
                    string tempValue = String.Format("{0}|{1}", ((IAuditable)e).UpdatedBy, DateTime.Ticks);
                    ((IAuditable)e).UpdatedBy = tempValue;
                    base.SubmitChanges(failureMode);
    
                    ((IAuditable)e).UpdatedBy = tempValue.Substring(0, tempValue.LastIndexOf('|'));
                    base.SubmitChanges(failureMode);
                }
            }
        }
    }