实体框架,用于读取列但阻止其更新

时间:2015-06-11 09:57:45

标签: c# entity-framework

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

例如我有一个对象

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

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

(1)使用以下属性

装饰HistoricDataColumnName属性
[NotMapped]

(2)将以下内容添加到EntityTypeConfiguration的{​​{1}}

MyObject

9 个答案:

答案 0 :(得分:8)

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

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

DatabaseGenerated

  

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

答案 1 :(得分:4)

Codewise,您可以将设置器简单地设置为受保护。 EF使用反射来实现您的模型。我认为现在隐藏的setter也向所有其他程序员显示,该字段不应再被修改。

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

答案 2 :(得分:4)

因为你说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

enter image description here

答案 3 :(得分: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是一种无操作。

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

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

答案 4 :(得分:2)

对于刚刚在列上,这是过度杀伤,但一般情况下,您可以覆盖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();
}

但您也可以通过将状态从修改更改为未更改来撤消所有修改。

- 更新 -

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

所以我认为最重要的事情是将字段设置为客户端的数据库中的只读。但是你可能无法锁定一列。

即使这不是问题,我认为(对于设计)最好将所有历史数据移动到其他表。只允许只读访问。您可以1:1映射这些表。使用Entity Framework,您仍然可以非常轻松地访问历史信息。

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

答案 5 :(得分:2)

internal访问修饰符

您可以将设置器更改为public class MyObject { public string CurrentDataColumnName { get; internal set; } public string HistoricDataColumnName { get; internal set; } }

protected

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

public class MyObject { public string CurrentDataColumnName { get; protected set; } public string HistoricDataColumnName { get; protected set; } } 访问修饰符

这可能是在EF&#34;只读&#34;中制作属性的最常见用法。这实际上只允许构造函数访问setter(以及类中的其他方法,以及从类派生的类)。

protected
  

我认为protected internal是您正在寻找的。

protected访问修饰符

您也可以将这两者结合起来,使其成为internalpublic class MyObject { public string CurrentDataColumnName { get; protected internal set; } public string HistoricDataColumnName { get; protected internal set; } }

internal

访问修饰符复习课程

  • protected成员只能在同一个程序集中访问
  • protected internal成员可在其类和派生类实例中访问。
  • 可以从当前程序集或从包含类派生的类型访问params.permit(page_hierarchy: {}) 成员。

答案 6 :(得分:2)

问题是关于EF 6,但在具有Metadata.IsStoreGeneratedAlways属性的EF Core中很容易实现。感谢{Core} repo for the answer上的ajcvickers。

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

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

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

EF会在没有任何问题的情况下实现数据库中的对象,但是你无法改变这些属性中的值。