entity framework,用于读取列但阻止其更新

给定一个包含历史数据但不再填充的列的数据库表,是否有一种方法可以在Entity Framework中读取该列但是在使用相同的模型对象时阻止它更新?

例如,我有一个对象

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

从文档中我不相信我可以做以下任何一种,因为这将阻止EF读取数据并保持它。

(1)使用以下属性装饰HistoricDataColumnName属性

 [NotMapped] 

(2)将以下内容添加到MyObject EntityTypeConfiguration

 Ignore(x => x.HistoricDataColumnName) 

您可以将列标记为已计算,以防止Entity Framework更新/插入该列。

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

DatabaseGenerated

一个重要的数据库function是具有计算属性的能力。 如果要将Code First类映射到包含计算列的表,则不希望Entity Framework尝试更新这些列。 但是,在插入或更新数据后,您确实希望EF从数据库中返回这些值。 您可以使用DatabaseGenerated注释来标记类中的这些属性以及Computed enum。 其他枚举是None和Identity。

由于您说’在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 

无论哪种方式,你都会被抛出exception。 这是使用LinqPad

在此处输入图像描述

您可以简单地使用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是一种无操作。

请查看以下答案作为补充说明。 它也可能有用:

https://stackoverflow.com/a/13503683/2946329

按照Codewise,您可以将setter简单地设置为protected 。 EF使用reflection来实现您的模型。 我认为现在隐藏的setter也向其他程序员展示了该字段不应再被修改。

另外添加一个[Obsolete] -attribute以及更多信息,为什么不能再从公众设置该属性。

对于刚刚列,这是过度的,但通常您可以覆盖DbContext中的SaveChanges以更好地控制更改。

在你的模型中:

 public override int SaveChanges() { var modifiedEntries = base.ChangeTracker.Entries() .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,您仍然可以非常轻松地访问历史信息。

但在这种情况下,您将不会遇到现在的问题,并会为您提供其他选项以防止他人更改历史信息。

internal访问修饰符

您可以将setter更改为internal

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

这并没有像其他选项那样强加限制,但根据您的要求,这可能非常有用。

protected访问修饰符

这可能是使EF中的属性“只读”的最常见用法。 这基本上只允许构造函数访问setter(以及类中的其他方法,以及从类派生的类)。

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

我认为protected是你正在寻找的。

protected internal访问修饰符

您也可以将这两者结合起来,使其protectedinternal protected

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

访问修改器进修课程

  • internal成员只能在同一个程序集中访问
  • protected成员可在其类和派生类实例中访问。
  • 可以从当前程序集或从包含类派生的类型访问protected internal成员。

问题是关于EF 6,但在EF Core中使用Metadata.IsStoreGeneratedAlways属性很容易实现。 感谢EF Core repo上的ajcvickers提供答案 。

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

为什么首先在EF? 为什么不简单地确保用于访问数据库的任何登录都具有执行UPDATE / INSERT / DELETE撤销的权限,甚至可以在数据库选项中将数据库设置为READ_ONLY?

在我看来,任何阻止通过EF更新的尝试都注定要失败,因为你总能绕过它,例如,只是直接针对EF连接执行SQL代码。

至于我,这是一个简单的解决方案 – 将属性设置器设为private

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

EF将从数据库中实现对象而没有任何问题,但是您无法在这些属性中更改值。