130

我需要向 3rd 方插件公开实体框架数据上下文。目的是允许这些插件仅获取数据,而不是让它们发出插入、更新或删除或任何其他数据库修改命令。因此,我怎样才能使数据上下文或实体只读。

4

6 回答 6

205

除了与只读用户连接之外,您还可以对 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>();
    }
}
于 2012-05-03T20:38:33.330 回答
51

与公认的答案相反,我相信更倾向于组合而不是继承。那么就不需要保留诸如 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();
于 2019-03-10T22:26:57.233 回答
1
public sealed class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
        : base(options)
    {
        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    }
}

并覆盖 SaveChanges 以抛出异常

于 2021-08-23T09:35:44.933 回答
1

在我使用 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 而异)

结论

  1. “覆盖”将提供完全继承,但在我的环境中不起作用
  2. "new" 提供了所需的功能,但对于某些多态性场景会返回意外的结果
  3. 如果你有很多表,根本不使用继承会很痛苦

==> 没有灵丹妙药,选择取决于品味和环境......

[1] https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0809?f1url=%3FappId%3Droslyn%26k%3Dk(CS0809)

于 2021-10-11T16:48:35.253 回答
0

由于在Entity Framework CoreDbQuery<T>中不再可用,您需要稍微修改@bricelam 的答案并直接使用:IQueryable<T>

public class ReadOnlyContext : DbContext
{
    public IQueryable<Customer> Customers => this.Set<Customer>().AsNoTracking();

    // [...]
}
于 2021-10-11T12:37:48.600 回答
-1

情况:我需要参考 DB1 在 DB2 中创建记录,并希望在此过程中保护 DB1。DB1 和 DB2 是彼此的模式副本。

我更新了自动生成的实体上下文文件。并在使用 ReadOnly 选项时通过覆盖 SaveChanges() 来实例化实体上下文以中止写入时放入只读选项。

缺点:

  1. 您必须在配置设置中创建单独的 EF 连接字符串
  2. 自动更新模型时必须小心。保留代码更改的副本,并记住在模型更新后应用它。
  3. 未提供未执行保存的通知。我选择不提供通知,因为我的使用非常有限,而且我们执行了很多保存操作。

优点:

  1. 您不必实现 CQRS 类型的解决方案。
  2. 通过使用相同的实体模型,您不必创建第二个并维护它。
  3. 数据库或其用户帐户没有更改。

只需确保在命名上下文实例时使用 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(); }
    }

..... }

于 2021-08-25T20:42:36.363 回答