C# 实体框架来读取列,但阻止其更新

C# 实体框架来读取列,但阻止其更新,c#,entity-framework,C#,Entity Framework,给定一个包含历史数据但不再填充的列的数据库表,在Entity Framework中是否有方法读取该列,但在使用相同的模型对象时防止其更新 例如,我有一个对象 public class MyObject { public string CurrentDataColumnName { get; set; } public string HistoricDataColumnName { get; set; } } 从文档来看,我不相信我可以做以下任何一项,因为这将停止EF读取数据并保

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

例如,我有一个对象

public class MyObject
{
    public string CurrentDataColumnName { get; set; }
    public string HistoricDataColumnName { get; set; }
}
从文档来看,我不相信我可以做以下任何一项,因为这将停止EF读取数据并保持数据

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

[NotMapped]
(2) 为
MyObject

Ignore(x => x.HistoricDataColumnName)

您可以将该列标记为计算列,以防止实体框架更新/插入该列

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

数据库的一个重要特性是能够计算 财产。如果要将代码优先类映射到 包含计算列,您不希望实体框架尝试 更新这些列。但您确实希望EF从中返回这些值 插入或更新数据后,数据库将被删除。你可以使用 DatabaseGenerated注释来标记类中的那些属性 以及计算的枚举。其他枚举为None和Identity


对于just on column,这是过分的,但一般来说,您可以在DbContext中重写SaveChanges,以便对更改有更多的控制

在您的模型中:

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();
}
public override int SaveChanges()
{
var modifiedEntries=base.ChangeTracker.Entries()
.Where(e=>e.State==EntityState.Modified).ToList();
foreach(修改项中的var条目)
{
//使用相同的值进行覆盖不算作更改。
entry.CurrentValues[“HistoricalCDataColumnName”]=entry.OriginalValues[“HistoricalCDataColumnName”];
}
返回base.SaveChanges();
}
但也可以通过将状态从“已修改”更改为“未更改”来撤消所有修改

--更新--

有一件事让我担心。一旦开发人员拥有访问数据库的凭据,您就不能阻止他们做您不想做的事情。他们可以创建自己的模型或直接查询数据库

因此,我认为最重要的事情是在客户机的数据库中将字段设置为只读。但您可能无法锁定一列

即使这不是问题,我认为(对于设计而言)最好将所有历史数据移动到其他表中。使授予只读访问权限变得容易。您可以将这些表映射为1:1。使用实体框架,您仍然可以非常轻松地访问历史信息


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

代码方面,您可以简单地将setter设置为protected。EF使用反射来具体化您的模型。我认为现在隐藏的setter也向其他程序员表明,该字段不应该再被修改了

还添加了一个[Observe]-属性,其中包含更多信息,即为什么不能再从公共设置属性。

internal
access修饰符 您可以将setter更改为
internal

public class MyObject
{
    public string CurrentDataColumnName { get; internal set; }
    public string HistoricDataColumnName { get; internal set; }
}
public class MyObject
{
    public string CurrentDataColumnName { get; protected internal set; }
    public string HistoricDataColumnName { get; protected internal set; }
}
这不会像其他选项那样施加太多限制,但根据您的需求,这可能非常有用

受保护
访问修改器 这可能是将EF中的属性设置为“只读”的最常见用法。它本质上只允许构造函数访问setter(和类中的其他方法,以及从类派生的类)

我认为
受保护的
是您要找的

受保护的内部
访问修饰符 您也可以这样将两者结合起来,使其
受保护
内部

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

进修课程
  • 内部
    成员只能在同一程序集中访问
  • 受保护的
    成员可以在其类内以及通过派生类实例进行访问
  • 可以从当前程序集或从包含类派生的类型访问受保护的内部成员

既然您说“在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
如果需要的话,您当然可以对insert执行类似的操作

或者使用“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

您只需使用检查特定实体属性是否被修改,这样您仍然可以读取、插入和删除数据:

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是不允许的

检查以下答案作为补充说明。这也可能有帮助:


问题是关于EF 6,但这在具有该属性的EF Core中很容易实现。感谢EF核心回购协议中的ajcvickers

modelBuilder
.实体()
.属性(e=>e.Bar)
.ValueGeneratedOnAddOrUpdate()
.Metadata.IsStoreGeneratedAlways=true;

为什么要在EF中这样做?Wh
public class MyObject
{
    public string CurrentDataColumnName { get; private set; }
    public string HistoricDataColumnName { get; private set; }
}