Entity Framework Code First只读实体

13

有没有一种方法可以将实体标记为只读,并且不需要指定任何键?


2
实体被映射到视图上,我不想在其上进行更新/插入操作,也没有关键字。 - Otake
EF 默认情况下不会对视图进行更新。 - Gert Arnold
@GertArnold,那是不正确的。你可以在EF中更新/插入视图。 - Otake
这个信息已经过时了吗?可能是的,EF发展得非常快。 - Gert Arnold
我认为这已经过时了。你可以使用数据库优先方法来操作edm文件,或者在代码优先中映射到视图。它将向数据库引擎发送一个良好的查询,如果你的视图是可更新的,它就会起作用。 - Otake
显示剩余2条评论
4个回答

12

有几种方法可以在Code First中强制只读。第一种是在查询时使用AsNoTracking()

var readOnlyPeople = (from p in context.People
                        where p.LastName == "Smith"
                        select p).AsNoTracking();

这告诉 Code First 不要跟踪这些实体的更改,因此当您调用 SaveChanges() 时,对这些对象所做的任何更改都不会被持久化。

第二件事是在调用 SaveChanges() 之前将状态设置为 Unchanged

context.Entry(person).State = EntityState.Unchanged;
context.SaveChanges();

这将告诉Code First忽略对该实体所做的任何更改。

至于没有键的情况,所有实体都必须有一个键。这可能不一定映射到数据库中的主键,但它“必须唯一标识实体集内的实体类型实例”。


2
嗨,Brice。你的建议对我来说不是理想的解决方案(我真的更喜欢只标记实体为只读 - 在NH中存在),但我认为在EF中我想要的东西还没有一个简洁的解决方案。关于“所有实体都必须有一个键”,我有点同意你的看法,但有时你必须使用一些视图,它们没有任何键,如果我可以将实体标记为只读,为什么还需要一个键。 - Otake
实际上,您可以使用类似于此处建议的ICacheableEntity接口的IReadOnlyEntity标记接口(也有描述此内容的博客文章,但我找不到它们)https://dev59.com/lljUa4cB1Zd3GeqPV-wc#6593261。它们之间的关系存在一些需要考虑的问题(如果有人感兴趣,请告诉我)。最终,我们正在以更自动化的方式执行Brice建议的操作。 - Cohen
如果您的视图没有自然键,您可以添加一个来帮助EntityFramework。在视图定义中: SELECT NEWID() as [VirtualKey] ...在实体映射中: // 主键 this.HasKey(t => t.VirtualKey); - Elton

11

在EF6中使用Code-First,我创建了一些反映视图的实体,这些实体显然不应该被修改或保存。为了防止实体被更改,我使用了protected set属性:

public class TransplantCenterView
{
    public string TransplantsThisYear { get; protected set; }
}

Entity Framework仍然能够设置此属性,但其他开发人员无法在没有编译时错误的情况下意外更改它。这样做很好,但似乎更好的解决方案是完全消除跟踪。


感谢reggaeguitar的答案,看起来有一个解决方法(如果以下内容有帮助,请向他的答案投票),这使我可以将我的代码从以下方式更改:

public class MyContext : DbContext
{
    public DbSet<TransplantCenterView> TransplantCenterViews { get; set; }
}
public class MyContext : DbContext
{
    // appears the DbSet is still needed to make Set<Entity>() work
    protected DbSet<TransplantCenterView> _transplantCenterViews { get; set; }
    // this .AsNoTracking() disables tracking for our DbSet.
    public DbQuery<TransplantCenterView> TransplantCenterViews
    {
        get { return Set<TransplantCenterView>().AsNoTracking(); }
    }
}

我不知道这方面有什么利弊,但是我的现有代码继续运行没有遇到任何问题,所以看起来是一个胜利。


请查看我的答案,了解如何在实体上禁用跟踪。 - reggaeguitar
开发人员仍然可以调用MyContext.Set<TransplantCenterView>().DoAnyThing(),因此我仍然更喜欢您的protected set解决方案。 - Steven Ryssaert

2
如果您希望整个实体都是只读的,可以这样做:
/// Using a dbquery since this is readonly.
/// </summary>
public DbQuery<State> States
{
  get
  {
    // Don't track changes to query results
    return Set<State>().AsNoTracking();
 }
}

来源 http://www.adamtuliper.com/2012/12/read-only-entities-in-entity-framework.html

本文将介绍如何在Entity Framework中使用只读实体。只读实体是指只能被查询而不能被修改的实体对象。通过使用只读实体,可以提高应用程序的性能和安全性。


4
将 DbSet 更改为 DbQuery。按照上述说明更改 getter。编译成功,一切顺利。但是,当我打算执行从视图加载数据的页面时,出现了运行时错误:“实体类型‘MyEntityName’不是当前上下文中模型的一部分。” 因此,添加了 protected DbSet<MyEntityName> _hiddenMyEntitiesName {get; set;} 然后问题就解决了。最好将 DbSet<> 添加到您上面的代码中,以便其他人不会遇到相同的困惑......或者是否有其他方法?但感谢您让我走上了正确的道路。 - Kevin Nelson
有趣的是,我没有遇到那个错误。我正在映射表而不是视图,也许这就是区别所在? - reggaeguitar
嗯...所以,在你的上下文中没有任何DbSet<State>属性,但它仍然可以工作?奇怪...也可能是版本问题。我回到了VS 2015和EF6。 - Kevin Nelson
我没有DbSet<State>,只有DbQuery<State>,而且我的旧LINQ查询工作正常。我也在使用VS 2015 EF6。 - reggaeguitar
不要将 protected DbSet<MyEntityName> _hiddenMyEntitiesName {get; set;} 添加到你的上下文中,而是添加 modelBuilder.Entity<MyEntityName>(); 到 OnModelCreating 方法中。 - John S
@JohnS 为什么?有什么区别? - reggaeguitar

0

你也可以通过类型将其设置为只读,作为更“全局”的规则来限制特定实体的修改。只需覆盖相应的SaveChanges*方法,并在保存期间看到EF试图添加它时设置实体的状态。

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    foreach (var entry in ChangeTracker.Entries())
    {
        if (entry.Entity.GetType() == typeof(<YOUR_TYPE_HERE>) && entry.State == EntityState.Added)
        {
            entry.State = EntityState.Detached;
        }
    }

    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接