Подтвердить что ты не робот

Entity Framework для чтения столбца, но не обновлять его

Учитывая таблицу базы данных со столбцом, который содержит исторические данные, но который больше не заполняется, есть ли способ в Entity Framework прочитать столбец, но предотвратить его обновление при использовании одного и того же объекта модели?

Например, у меня есть объект

public class MyObject
{
    public string CurrentDataColumnName { get; set; }
    public string HistoricDataColumnName { get; set; }
}

Из документации я не верю, что могу сделать одно из следующих действий, потому что это остановит EF, считывая данные, а также сохраняя его.

(1) Украсите свойство HistoricDataColumnName следующим атрибутом

[NotMapped]

(2) Добавьте следующее в мой EntityTypeConfiguration для MyObject

Ignore(x => x.HistoricDataColumnName)
4b9b3361

Ответ 1

Вы можете пометить столбец как вычисленный, чтобы предотвратить обновление/вставку Entity Framework в этот столбец.

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string HistoricDataColumnName { get; set; }

DatabaseGenerated

Важными функциями базы данных являются способность вычислять свойства. Если вы сопоставляете классы Code First с таблицами, которые содержат вычисленные столбцы, вы не хотите, чтобы Entity Framework пыталась обновите эти столбцы. Но вы хотите, чтобы EF возвращал эти значения из после того, как вы вставили или обновили данные. Вы можете использовать DatabaseGenerated аннотация, чтобы отметить эти свойства в вашем классе наряду с вычисленным перечислением. Другие перечисления: None и Identity.

Ответ 2

Codewise вы можете установить установщик просто защищенный. EF использует отражение для материализации вашей модели. Я думаю, что теперь скрытый установщик также показывает каждому другому программисту, что поле больше не должно изменяться.

Также добавьте [Устаревший] -атрибут с дополнительной информацией, почему свойство больше не может быть установлено из публики.

Ответ 3

Поскольку вы говорите "на уровне EF или ниже", возможным решением является использование триггера для повышения ошибки, если делается попытка изменить столбец или разрешить обновление, но игнорировать изменение в интересующей колонке.

Вариант 1 - повышение ошибки

CREATE TRIGGER MyTable_UpdateTriggerPreventChange
    ON dbo.Table1
    AFTER UPDATE
AS 
BEGIN
    SET NOCOUNT ON;

    if update(HistoricDataColumnName) 
    begin
        raiserror (50001, 16, 10)
    end
END

Вариант 2 - игнорировать изменение

CREATE TRIGGER MyTable_UpdateTriggerIgnore
   ON dbo.Table1
   INSTEAD OF UPDATE
AS 
BEGIN
    SET NOCOUNT ON;

    update dbo.Table1 set HistoricDataColumnName=inserted.HistoricDataColumnName
    from inserted
    where inserted.Id = dbo.Table1.Id
END

Конечно, вы можете сделать что-то подобное для вставок.

В качестве альтернативы raiserror использовать 'throw'

ALTER TRIGGER MyTable_UpdateTriggerPreventChange
    ON dbo.Table1
    AFTER UPDATE
AS 
BEGIN
    SET NOCOUNT ON;

    if update(HistoricDataColumnName) 
    begin
        throw 50002, 'You can''t change the historic data', 1
    end
END

в любом случае вы получите исключение. Это использует LinqPad

введите описание изображения здесь

Ответ 4

Вы можете просто использовать IsModified, чтобы проверить, было ли изменено какое-либо свойство объекта или нет, и таким образом вы все еще можете Чтение, вставка и удаление:

var item = context.MyObjects.Find(id);
item.CurrentDataColumnName = "ChangedCurrentDataColumnName";
item.HistoricDataColumnName = "ChangedHistoricDataColumnName";
context.Entry(item).Property(c => c.HistoricDataColumnName).IsModified = false;
context.SaveChanges();

Используя IsModified = false, вы исключаете свойство HistoricDataColumnName для обновления, поэтому столбец HistoricDataColumnName не будет обновляться в базе данных, но будут обновлены другие свойства.

Установка этого значения в значение false для измененного свойства вернет изменение, установив текущее значение в исходное значение. Если результатом является то, что никакие свойства объекта не будут помечены как измененные, то объект будет отмечен как Без изменений. Установка этого значения в значение false для свойств добавленных, неизмененных или удаленных объектов не является операцией.

В качестве дополнительного объяснения проверьте следующий ответ. Это может быть полезно также:

fooobar.com/questions/453612/...

Ответ 5

Только для столбца это слишком много, но в целом вы можете переопределить SaveChanges в DbContext, чтобы иметь больше контроля над изменениями.

В вашей модели:

public override int SaveChanges()
{
    var modifiedEntries = base.ChangeTracker.Entries<MyObject>()
        .Where(e => e.State == EntityState.Modified).ToList();

    foreach (var entry in modifiedEntries)
    {
         // Overwriting with the same value doesn't count as change.
         entry.CurrentValues["HistoricDataColumnName"] = entry.OriginalValues["HistoricDataColumnName"];
    }
    return base.SaveChanges();
}

Но вы также можете отменить все изменения, изменив состояние с измененного на неизменное.

- ОБНОВЛЕНИЕ -

Меня беспокоит одна вещь. Как только у разработчика есть учетные данные для доступа к базе данных, вы не можете помешать им делать то, что вам не нужно. Они могли бы создать свою собственную модель или напрямую запросить базу данных.

Итак, я думаю, что самое главное - установить поле для чтения только в базе данных для клиента. Но вы не сможете заблокировать один столбец.

Даже если это не проблема, я думаю (для дизайна) лучше переместить все исторические данные в другие таблицы. Простота доступа только для чтения. Вы можете сопоставить эти таблицы 1:1. С Entity Framework вы все равно можете легко получить доступ к исторической информации.

Но в этом случае у вас не будет проблемы, возникшей у вас сейчас, и предоставит вам другие возможности, чтобы другие пользователи не могли изменять историческую информацию.

Ответ 6

internal модификатор доступа

Вы можете изменить установщик на internal

public class MyObject
{
    public string CurrentDataColumnName { get; internal set; }
    public string HistoricDataColumnName { get; internal set; }
}

Это не накладывает столько ограничений, как другие параметры, но в зависимости от ваших требований это может быть весьма полезно.

protected модификатор доступа

Это, вероятно, будет наиболее распространенным использованием создания свойства в EF "только для чтения". Это по существу только позволяет конструктору получить доступ к установщику (и другим методам внутри класса и классам, производным от класса).

public class MyObject
{
    public string CurrentDataColumnName { get; protected set; }
    public string HistoricDataColumnName { get; protected set; }
}

Я думаю, что protected - это то, что вы ищете.

protected internal модификатор доступа

Вы также можете комбинировать эти два, чтобы сделать это protected или internal

public class MyObject
{
    public string CurrentDataColumnName { get; protected internal set; }
    public string HistoricDataColumnName { get; protected internal set; }
}

Курс переопределения модификатора доступа

  • Член internal доступен только в пределах одной сборки
  • Член protected доступен в пределах своего класса и экземпляров производного класса.
  • Доступ к элементу protected internal можно получить из текущей сборки или из типов, которые получены из содержащего класса.

Ответ 7

Вопрос о EF 6, но это легко выполнимо в EF Core с Metadata.IsStoreGeneratedAlways. Благодаря ajcvickers в репозитории EF Core для ответ.

modelBuilder
    .Entity<Foo>()
    .Property(e => e.Bar)
    .ValueGeneratedOnAddOrUpdate()
    .Metadata.IsStoreGeneratedAlways = true;

Ответ 8

Почему это в EF в первую очередь? Почему бы просто не гарантировать, что для любого входа в систему, используемого для доступа к базе данных, есть права на отмену UPDATE/INSERT/DELETE или даже до крайности установить базу данных READ_ONLY в параметрах базы данных?

Мне кажется, что любая попытка предотвратить обновления через EF обречена, поскольку вы всегда можете обойти это и, например, просто выполнить SQL-код непосредственно против EF-соединения.

Ответ 9

Что касается меня, это простое решение - сделайте настройки свойств private:

public class MyObject
{
    public string CurrentDataColumnName { get; private set; }
    public string HistoricDataColumnName { get; private set; }
}

EF будет материализовать объекты из базы данных без каких-либо проблем, но у вас не будет никакого способа изменить значение int этих свойств.