我需要向第三方插件暴露一个Entity Framework数据上下文。目的是允许这些插件仅获取数据,而不允许它们执行插入、更新或删除等任何其他数据库修改命令。因此,如何使数据上下文或实体只读。
我需要向第三方插件暴露一个Entity Framework数据上下文。目的是允许这些插件仅获取数据,而不允许它们执行插入、更新或删除等任何其他数据库修改命令。因此,如何使数据上下文或实体只读。
除了与只读用户连接外,您还可以对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>();
}
}
AsNoTracking()
将使惰性加载变得不可能。 - Tom Pažourekpublic override Task<int> SaveChangesAsync()
方法。 - Pete(context as IObjectContextAdapter).ObjectContext.SaveChanges()
仍将起作用。最佳选择是使用DbContext(string nameOrConnectionString);
构造函数,使用读/写连接字符串进行数据库创建,并在之后使用只读连接字符串。 - Jürgen Steinblockpublic IQueryable<Customer> Customers => Set<Customer>().AsNoTracking();
,意思是获取一个不跟踪变化的客户查询集合。 - DiPixpublic 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();
IDisposable
接口。 - hkaraskpublic IQueryable<TEntity> Get<TEntity>() where TEntity : class { return _dbContext.Query<TEntity>().AsNoTracking(); }
- Allan Nielsenpublic 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.");
}
}
I had to use "new" instead of "override" when overwriting inherited SaveChanges*() in order to have warnings/errors. With "override", there where no compile time errors/warnings at all.
With "override" you get CS0809 [1], but not with "new"
Using "new" will only work for the class itself, but not in context of the parent:
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
Proper choice of (optional) arguments is required for the variants of SaveChanges and SaveChangesAsync. (This is for .NET 5.0, I have not checked whether it varies for other versions of EF Core/EF)
结论
==> 没有万能的解决方案,选择取决于个人喜好和具体情况...
[1] https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0809?f1url=%3FappId%3Droslyn%26k%3Dk(CS0809)
SaveChanges()
方法放置于 #pragma warning disable CS0809
和 #pragma warning restore CS0809
之间,以禁用警告并仍然使用 override
。 - The Thirsty ApeContext
和ReadOnlyContext
的接口(duh)。IContext
而不是Context
。
public interface IContext : IDisposable
{
DbSet<Customer> Customers{ get; }
int SaveChanges();
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
public class Context :DbContext, IContext
{
public DbSet<Customer> Customers { get; set; }
public Context(DbContextOptions options)
: base(options)
{
}
}
Context
来实现ReadOnlyContext
,并限制其功能以使其只读。但我们还创建了一个匹配的IReadOnlyContext
接口,通过公开IQueryable
而不是DbSet
并且不公开SaveChanges
来进一步限制它的功能。在应用程序中使用时,我们会注入IReadOnlyContext
而不是ReadOnlyContext
。public interface IReadOnlyContext : IDisposable
{
IQueryable<Customer> Customers { get; }
}
public class ReadOnlyContext : Context, IReadOnlyContext
{
public new IQueryable<Customer> Customers => base.Customers.AsQueryable();
public ReadOnlyContext(DbContextOptions options)
: base(options)
{
}
[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.");
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<IReadOnlyContext, ReadOnlyContext>(
contextOptions => contextOptions
.UseSqlServer(
_configuration["ConnectionStrings:ReadOnlyConnection"] ??
_configuration["ConnectionStrings:DefaultConnection"],
sqlServerOptions => sqlServerOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
);
services.AddDbContext<IContext, Context>(
contextOptions => contextOptions
.UseSqlServer(
_configuration["ConnectionStrings:DefaultConnection"],
sqlServerOptions =>
sqlServerOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
)
);
}
您可以看到,它很好地运用了依赖注入的方法,同时允许使用单独的连接字符串。如果您想要连接到Azure数据库的只读副本,则需要这个功能,点击此处了解更多。
public sealed class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
}
并重写 SaveChanges 方法以抛出异常。
由于在Entity Framework Core中不再可用DbQuery<T>
,因此您需要略微修改@bricelam的答案,并直接使用IQueryable<T>
代替:
public class ReadOnlyContext : DbContext
{
public IQueryable<Customer> Customers => this.Set<Customer>().AsNoTracking();
// [...]
}
CREATE USER 'readonly-user'@'localhost' IDENTIFIED BY 'highly-secure-password';
GRANT SELECT ON special-db.* TO 'readonly-user'@'localhost';
FLUSH PRIVILEGES;
"ConnectionStrings": {
"readonlyDb": "Server=localhost; Port=3306; Database=special-db; Uid=readonly-user; Pwd=highly-secure-password;"
}
public class Koala
{
protected Koala() { }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; private set; }
public string Name { get; private set; }
}
public class ReadOnlyContext : DbContext
{
public ReadOnlyContext(DbContextOptions<MyDbContext> options) : base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
ChangeTracker.Tracked += ChangeTracker_Tracked;
}
private void ChangeTracker_Tracked(object sender, EntityTrackedEventArgs e)
{
throw new Exception("The dbcontext is in readonly mode.");
}
}
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(); }
}
..... }
DbContext
,给他们一个或多个IQueryable
。 - ta.speot.is