【问题标题】:Can you convince a DataContext to treat a column as always dirty?你能说服 DataContext 将列视为总是脏的吗?
【发布时间】:2023-03-08 19:33:05
【问题描述】:

有没有办法强制 LINQ-to-SQL 将列视为脏列?全球范围内就足够了....

基本上,我在与 L2S 交谈的遗留系统上的一些审计代码有问题,想象一下:

var ctx = new SomeDataContext(); // disposed etc - keeping it simple for illustration
var cust = ctx.Customers.First(); // just for illustration
cust.SomeRandomProperty = 17; // whatever
cust.LastUpdated = DateTime.UtcNowl;
cust.UpdatedBy = currentUser;
ctx.SubmitChanges(); // uses auto-generated TSQL

这很好,但如果同一个用户连续两次更新它,UpdatedBy 是 NOP,TSQL 将(大致):

UPDATE [dbo].[Customers]
SET SomeRandomColumn = @p0 , LastUpdated = @p1 -- note no UpdatedBy
WHERE Id = @p2 AND Version = @p3

在我的情况下,问题是当前所有表上都有一个大括号审计触发器,它检查审计列是否已更新,如果没有,则假定开发人员有错(替换为 @ 987654325@,尽管它可能同样容易引发错误)。

我真正想做的是说“总是更新这个专栏,即使它不脏”——这可能吗?

【问题讨论】:

  • 不太确定我是否理解这个问题(可能不是)。你不能使用:partial void OnLoaded() { this.UpdatedBy = currentUser; } 所以它总是设置 UpdatedBy?
  • 如果 UpdatedBy 不被视为真正的更改(因为它与上次的值相同),则不会发生该列的 UPDATE
  • 嗯,你不能通过手动附加实体来做到这一点吗?以前从未这样做过,但我相信它默认会更新所有列。见blogs.rev-net.com/ddewinter/2009/04/07/…尤其是第二章。
  • 实体已附加,因为它是由 数据上下文提供给我们的。
  • 哇,马克,这确实是一个艰难的过程!看看我能不能一起破解一些东西:)

标签: c# .net linq-to-sql


【解决方案1】:

基于KristoferA's answer,我最终得到了以下类似的东西;这是邪恶和脆弱的(反思经常是),但现在可能就足够了。战斗的另一面是改变触发器的行为:

partial class MyDataContext // or a base-class
{
    public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
    {
        this.MakeUpdatesDirty("UpdatedBy", "Updated_By");
        base.SubmitChanges(failureMode);
    }
}
public static class DataContextExtensions
{
    public static void MakeUpdatesDirty(
        this DataContext dataContext,
        params string[] members)
    {
        if (dataContext == null) throw new ArgumentNullException("dataContext");
        if (members == null) throw new ArgumentNullException("members");
        if (members.Length == 0) return; // nothing to do
        foreach (object instance in dataContext.GetChangeSet().Updates)
        {
            MakeDirty(dataContext, instance, members);
        }
    }
    public static void MakeDirty(
        this DataContext dataContext, object instance ,
        params string[] members)
    {
        if (dataContext == null) throw new ArgumentNullException("dataContext");
        if (instance == null) throw new ArgumentNullException("instance");
        if (members == null) throw new ArgumentNullException("members");
        if (members.Length == 0) return; // nothing to do
        const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
        object commonDataServices = typeof(DataContext)
            .GetField("services", AllInstance)
            .GetValue(dataContext);
        object changeTracker = commonDataServices.GetType()
            .GetProperty("ChangeTracker", AllInstance)
            .GetValue(commonDataServices, null);
        object trackedObject = changeTracker.GetType()
            .GetMethod("GetTrackedObject", AllInstance)
            .Invoke(changeTracker, new object[] { instance });
        var memberCache = trackedObject.GetType()
            .GetField("dirtyMemberCache", AllInstance)
            .GetValue(trackedObject) as BitArray;

        var entityType = instance.GetType();
        var metaType = dataContext.Mapping.GetMetaType(entityType);
        for(int i = 0 ; i < members.Length ; i++) {
            var member = entityType.GetMember(members[i], AllInstance);
            if(member != null && member.Length == 1) {
                var metaMember = metaType.GetDataMember(member[0]);
                if (metaMember != null)
                {
                    memberCache.Set(metaMember.Ordinal, true);
                }
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    不幸的是,我认为您将不得不使用新的 DataContext

    【讨论】:

    • DataContext 不是问题;这只会发生在来自单个DataContext 的单个提交中;我的意思是新值与从数据库加载的值相比没有改变,但我仍然希望它发出包含此列的UPDATE 语句。
    【解决方案3】:

    详情在:http://blog.benhall.me.uk/2008/01/custom-insert-logic-with-linq-to-sql.html

    您可以覆盖默认更新行为。有两种方法可以做到这一点

    最简单的方法是创建一个存储过程(如果您不能在数据库上执行此操作,则第二种方法应该可以工作),它获取客户对象的参数并更新表:

    1. 为需要更新的客户的每个属性创建具有参数的存储过程。
    2. 将该存储过程导入到您的 Linq To SQL DBML 文件中。
    3. 现在您可以右键单击客户实体并选择“配置行为”。
    4. 在“类别”下拉列表中选择您的“客户”类别,并在“行为”下拉列表中选择“更新”。
    5. 选择“自定义”单选按钮并选择您刚刚创建的存储过程。
    6. 现在您可以将类的属性映射到存储过程。

    现在,当 Linq to SQL 尝试更新您的客户表时,它将改用您的存储过程。请小心,因为这将覆盖所有客户的更新行为。

    第二种方法是使用部分方法。我实际上还没有尝试过,所以希望这可能会给你一些大致的追求方向。在您的数据上下文的部分类中,为更新创建一个部分方法(它将是 Update_____ 与您的类在空白中的任何内容。我建议在您的数据上下文的设计器文件中搜索以确保您获得正确的)

    public partial SomeDataContext
    {
        partial void UpdateCustomer(Customer instance)
        {
           // this is where you'd do the update, but I'm not sure exactly how it's suppose to work, though. :(
        }
    }
    

    【讨论】:

    • 是的,这已经是我的备份了 - 对于这种情况,我希望使用内置的 TSQL 生成来保留它...
    【解决方案4】:

    如果您想沿着 [dirty] 反射路线走,您可以尝试以下方式:

    1) 覆盖 SubmitChanges
    2) 浏览变更集
    3) 使用反射来获取每个更新对象的更改跟踪器(请参阅What's the cleanest way to make a Linq object "dirty"?
    4) 使列变脏(StandardTrackedObject 类中有一个dirtyMemberCache 字段)

    【讨论】:

    • 真的,在黑暗的一面是强大的……如果只有 MSFT 将一些有用的类和成员公开,那么它可以以更干净的方式完成……: )
    【解决方案5】:

    以下内容对我有用。请注意,虽然我使用的是 DevArt 的 linq2sql 提供程序,但这可能并不重要:

    MyDataContext dc = new MyDataContext();
    
    Message msg = dc.Messages.Single(m => m.Id == 1);
    Message attachingMsg = new Message();
    attachingMsg.Id = msg.Id;
    
    dc.Messages.Attach(attachingMsg);
    
    attachingMsg.MessageSubject = msg.MessageSubject + " is now changed"; // changed
    attachingMsg.MessageBody = msg.MessageBody; // not changed
    dc.SubmitChanges();
    

    这会产生以下 sql:

    UPDATE messages SET messageSubject = :p1, messageBody = :p2 WHERE Id = :key1
    

    因此,messageBody 会更新,即使它的值没有改变。 为此需要进行的另一项更改是,对于我的实体 Message 的每个属性(列),我设置了 UpdatedCheck = UpdateCheck.Never,除了它的 ID,它是主键。

    【讨论】:

    • 这应该会抛出一个错误,说明您正在尝试附加一个 id 已经存在的消息。
    猜你喜欢
    • 2010-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 2010-10-14
    • 2021-08-18
    • 1970-01-01
    • 2021-04-06
    相关资源
    最近更新 更多