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是一种无操作。
请查看以下答案作为补充说明。 它也可能有用:
按照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
访问修饰符
您也可以将这两者结合起来,使其protected
或internal
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将从数据库中实现对象而没有任何问题,但是您无法在这些属性中更改值。