代码之家  ›  专栏  ›  技术社区  ›  Marc Gravell

您能否说服DataContext将列视为总是脏的?

  •  8
  • Marc Gravell  · 技术社区  · 15 年前

    有没有办法强制LINQtoSQL将列视为脏列?在全球范围内就足够了。。。。

    基本上,我在与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
    

    在我的例子中,问题是当前所有表上都有一个belt and brakes审核触发器,它检查审核列是否已更新,如果未更新,则假定开发人员有错(替换为 SUSER_SNAME()

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

    5 回复  |  直到 15 年前
        1
  •  8
  •   Community CDub    8 年前

    基于 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
  •  1
  •   Josh Stodola    15 年前

        3
  •  1
  •   Joel Beckham    15 年前

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

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

    现在,当LINQtoSQL尝试更新Customers表时,它将使用存储过程。只是要小心,因为这将覆盖各地客户的更新行为。

    第二种方法是使用局部方法。我还没有试过,所以希望这能给你一些大致的方向。在你的数据上下文的一个部分类中,为更新做一个部分方法(它将是更新你的类在空白中的任何一个。我建议在你的数据上下文的设计器文件中搜索以确保你得到正确的一个)。

    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. :(
        }
    }
    
        4
  •  1
  •   Community CDub    8 年前

    1) 覆盖提交更改
    2) 查看更改集
    3) 使用反射获取每个更新对象的更改跟踪器(请参见 What's the cleanest way to make a Linq object "dirty"?

        5
  •  0
  •   Razzie    15 年前

    以下内容适合我。请注意,虽然我使用的是来自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的值没有更改,它也会被更新。 UpdatedCheck = UpdateCheck.Never ,但其ID(主键)除外。