2010-11-17 41 views
1

我有一个用于审计目的的触发器,它要求每个更新语句中都存在审计列。如何强制LINQ to SQL始终在UPDATE语句中包含一列?

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

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

我已经通过反射挖注意到SubmitChangesDataContext使用ChangeTracker,但我不能工作了,如果有说服LINQ to SQL的该列已经改变了什么好办法,所以应该被包括在内。最糟糕的解决方案是将更新语句中的所有字段包括在内,而不考虑更改。然而,ChangeTracker参考埋在DataContext之内,并且不允许我们注入自己的ChangeTracker

我已经玩弄了分离和重新连接实体的想法,但这似乎令人费解。如果有人有想法,我会很感激。干杯。

+1

你可能想看看[这个问题](http://stackoverflow.com/questions/1560513/can-you-convince -a-datacontext-to-treat-a-column-as-always-dirty) – 2010-11-17 04:45:50

+0

谢谢,不能完全得到正确的措词,但它是*确切*相同的问题。作为参考,我可以通过如果我使用两个数据上下文,或者如果我做两个更新(也许第二个追逐审计表并修改最新的记录..),但我无法找到使用一个数据上下文的方法,因为您无法将同一个id的实例附加到上下文中。反思可能就是这样。 – 2010-11-17 05:17:58

+0

试图自我封闭,因为它是你的问题Marc的重复。你可以发表你的评论作为答案,以便我可以接受它吗?目前看来是最好的解决方案。 – 2010-11-23 00:20:37

回答

1

最简单的办法:

所有列

你必须禁用并发检查(UpdateCheck.Never)(你可以转移双击它,在设计师,或者如果使用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); 
     } 
    } 
} 

我发现(如果你使用T4模板的LINQ to SQL这种类型的最佳解决方案这更简单):

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

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

然后修改您的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)) 
     { 
      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); 
      } 
     } 
    } 
}