我需要向 3rd 方插件公开实体框架数据上下文。目的是允许这些插件仅获取数据,而不是让它们发出插入、更新或删除或任何其他数据库修改命令。因此,我怎样才能使数据上下文或实体只读。
6 回答
除了与只读用户连接之外,您还可以对 DbContext 执行一些其他操作。
public class MyReadOnlyContext : DbContext
{
// Use ReadOnlyConnectionString from App/Web.config
public MyContext()
: base("Name=ReadOnlyConnectionString")
{
}
// Don't expose Add(), Remove(), etc.
public DbQuery<Customer> Customers
{
get
{
// Don't track changes to query results
return Set<Customer>().AsNoTracking();
}
}
public override int SaveChanges()
{
// Throw if they try to call this
throw new InvalidOperationException("This context is read-only.");
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Need this since there is no DbSet<Customer> property
modelBuilder.Entity<Customer>();
}
}
与公认的答案相反,我相信更倾向于组合而不是继承。那么就不需要保留诸如 SaveChanges 之类的方法来引发异常。此外,为什么你首先需要有这样的方法?您应该设计一个类,使其使用者在查看其方法列表时不会被愚弄。公共接口应该与类的实际意图和目标一致,而在接受的答案中 SaveChanges 并不意味着 Context 是只读的。
在我需要只读上下文的地方,例如CQRS模式的读取端,我使用以下实现。除了向消费者提供查询功能外,它不提供任何其他功能。
public class ReadOnlyDataContext
{
private readonly DbContext _dbContext;
public ReadOnlyDataContext(DbContext dbContext)
{
_dbContext = dbContext;
}
public IQueryable<TEntity> Set<TEntity>() where TEntity : class
{
return _dbContext.Set<TEntity>().AsNoTracking();
}
}
通过使用 ReadOnlyDataContext,您只能访问 DbContext 的查询功能。假设您有一个名为 Order 的实体,那么您将以如下方式使用 ReadOnlyDataContext 实例。
readOnlyDataContext.Set<Order>().Where(q=> q.Status==OrderStatus.Delivered).ToArray();
如果您想手动选择(和限制)通过这个新上下文公开哪些实体,则可以选择另一种选择。您将删除上面基于泛型的方法(其中包含 TEntity 的完整块)并使用类似于下面的方法。
public IQueryable<MyFirstThing> MyFirstHandPickThings => this.dbContext.Set<MyFirstThing>().AsNoTracking();
public IQueryable<MySecondThing> MySecondHandPickThings => this.dbContext.Set<MySecondThing>().AsNoTracking();
public sealed class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
}
并覆盖 SaveChanges 以抛出异常
在我使用 EF Core/.NET 5.0 的场景中,我希望 SaveChanges 具有编译时安全性。这仅适用于“新”而不是“覆盖”。
我并排使用读/写和只读上下文,其中一个从另一个继承,因为附加了很多表。这就是我使用的,“ContextData”是我原来的 R/W DbContext:
public class ContextDataReadOnly : ContextData
{
public ContextDataReadOnly() : base()
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
[Obsolete("This context is read-only", true)]
public new int SaveChanges()
{
throw new InvalidOperationException("This context is read-only.");
}
[Obsolete("This context is read-only", true)]
public new int SaveChanges(bool acceptAll)
{
throw new InvalidOperationException("This context is read-only.");
}
[Obsolete("This context is read-only", true)]
public new Task<int> SaveChangesAsync(CancellationToken token = default)
{
throw new InvalidOperationException("This context is read-only.");
}
[Obsolete("This context is read-only", true)]
public new Task<int> SaveChangesAsync(bool acceptAll, CancellationToken token = default)
{
throw new InvalidOperationException("This context is read-only.");
}
}
注意:
在覆盖继承的 SaveChanges*() 时,我必须使用“new”而不是“override”才能出现警告/错误。使用“覆盖”,根本没有编译时错误/警告。
使用“覆盖”可以获得 CS0809 [1],但不会使用“新”
使用“new”仅适用于类本身,但不适用于父级:
Base b = new Derived(); Derived d = new Derived(); b.SaveChanges(); // Calls Base.SaveChanges, will compile and run without exception d.SaveChanges(); // Calls Derived.SaveChanges, will not compile
SaveChanges 和 SaveChangesAsync 的变体需要正确选择(可选)参数。(这是针对 .NET 5.0 的,我没有检查它是否因其他版本的 EF Core/EF 而异)
结论
- “覆盖”将提供完全继承,但在我的环境中不起作用
- "new" 提供了所需的功能,但对于某些多态性场景会返回意外的结果
- 如果你有很多表,根本不使用继承会很痛苦
==> 没有灵丹妙药,选择取决于品味和环境......
[1] https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0809?f1url=%3FappId%3Droslyn%26k%3Dk(CS0809)
由于在Entity Framework CoreDbQuery<T>
中不再可用,您需要稍微修改@bricelam 的答案并直接使用:IQueryable<T>
public class ReadOnlyContext : DbContext
{
public IQueryable<Customer> Customers => this.Set<Customer>().AsNoTracking();
// [...]
}
情况:我需要参考 DB1 在 DB2 中创建记录,并希望在此过程中保护 DB1。DB1 和 DB2 是彼此的模式副本。
我更新了自动生成的实体上下文文件。并在使用 ReadOnly 选项时通过覆盖 SaveChanges() 来实例化实体上下文以中止写入时放入只读选项。
缺点:
- 您必须在配置设置中创建单独的 EF 连接字符串
- 自动更新模型时必须小心。保留代码更改的副本,并记住在模型更新后应用它。
- 未提供未执行保存的通知。我选择不提供通知,因为我的使用非常有限,而且我们执行了很多保存操作。
优点:
- 您不必实现 CQRS 类型的解决方案。
- 通过使用相同的实体模型,您不必创建第二个并维护它。
- 数据库或其用户帐户没有更改。
只需确保在命名上下文实例时使用 ReadOnly 或类似名称命名它。
public partial class db1_Entities : DbContext
{
public bool IsReadOnly { get; private set; }
public db1_Entities()
: base(ConfigurationManager.ConnectionStrings["db1_Entities"].ConnectionString)
{
}
public db1_Entities(bool readOnlyDB)
: base(ConfigurationManager.ConnectionStrings["db1_ReadOnly_Entities "].ConnectionString)
{
// Don't use this instantiation unless you want a read-only reference.
if (useReferenceDB == false)
{
this.Dispose();
return;
}
else
{ IsReadOnly = true; }
}
public override int SaveChanges()
{
if (IsReadOnly == true)
{ return -1; }
else
{ return base.SaveChanges(); }
}
public override Task<int> SaveChangesAsync()
{
if (isReadOnly == true)
{ return null; }
else
{ return base.SaveChangesAsync(); }
}
..... }