Можете ли вы убедить DataContext рассматривать столбец как всегда грязный? - PullRequest
8 голосов
/ 13 октября 2009

Есть ли способ заставить 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

В моем случае проблема заключается в том, что в настоящее время во всех таблицах есть триггер аудита с поясом и скобками, который проверяет, обновлен ли столбец аудита, и, если нет, предполагает, что разработчик виноват (подставляя 1010 *, хотя он может также с готовностью выдать ошибку).

Что я действительно хотел бы сделать, так это сказать «всегда обновлять этот столбец, даже если он не грязный» - возможно ли это?

Ответы [ 5 ]

8 голосов
/ 14 октября 2009

Основываясь на ответе КристоферА , я получил что-то как ниже; это зло и хрупкость (рефлексия часто бывает), но на данный момент, возможно, должно хватить. Другая сторона битвы - изменить поведение триггеров:

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);
                }
            }
        }
    }
}
1 голос
/ 13 октября 2009

Подробности по адресу: http://blog.benhall.me.uk/2008/01/custom-insert-logic-with-linq-to-sql.html

Вы можете переопределить поведение обновления по умолчанию. Есть 2 способа сделать это

Самым простым является создание хранимой процедуры (если вы не можете сделать это в своей базе данных, второй метод должен работать), которая принимает параметры объекта вашего клиента и обновляет таблицу:

  1. Создайте хранимую процедуру, имеющую параметр для каждого свойства клиентов, которое необходимо обновить.
  2. Импортируйте эту хранимую процедуру в файл DBML Linq To SQL.
  3. Теперь вы можете щелкнуть правой кнопкой мыши на объекте ваших клиентов и выбрать «Настроить поведение».
  4. Выберите свой класс Customers в раскрывающемся списке Class и "Update" в раскрывающемся списке поведения.
  5. Установите переключатель «Настройка» и выберите только что созданную вами хранимую процедуру.
  6. Теперь вы можете отобразить свойства класса в хранимую процедуру.

Теперь, когда Linq to SQL попытается обновить таблицу 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. :(
    }
}
1 голос
/ 14 октября 2009

Если вы хотите пойти по [грязному] маршруту отражения, вы можете попробовать что-то вроде:

1) Переопределить SubmitChanges
2) Пройдите набор изменений
3) Используйте отражение, чтобы получить трекер изменений для каждого обновленного объекта (см. Какой самый чистый способ сделать объект Linq «грязным»? )
4) Сделать столбец грязным (в классе StandardTrackedObject есть поле dirtyMemberCache)

1 голос
/ 13 октября 2009

К сожалению, я думаю, вам придется использовать новый DataContext

0 голосов
/ 14 октября 2009

У меня работает следующее. Обратите внимание, что я использую поставщика linq2sql от DevArt, но это может не иметь значения:

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, за исключением его идентификатора, который является первичным ключом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...