Cómo hacer que el contexto de datos de Entity Framework sea de solo lectura

Necesito exponer un contexto de datos de Entity Framework a complementos de terceros. El propósito es permitir que estos complementos solo obtengan datos y no permitirles realizar inserciones, actualizaciones o eliminaciones o cualquier otro comando de modificación de la base de datos. Por lo tanto, ¿cómo puedo hacer que un contexto de datos o una entidad sean de solo lectura?

preguntado el 03 de mayo de 12 a las 19:05

Déles un contexto con un usuario que no tenga acceso de escritura a la base de datos. -

Gracias. Estoy usando una base de datos SQLite. Acabo de descubrir que se puede abrir en modo de solo lectura a través de una opción de cadena de conexión. -

no les des un DbContext, dales un IQueryable o varios. -

6 Respuestas

Además de conectarse con un usuario de solo lectura, hay algunas otras cosas que puede hacer con su 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>();
    }
}

Respondido el 04 de diciembre de 12 a las 16:12

era obvio que eres un 'hombre interno' :) - esto es mucho más interesante que una conexión de 'solo lectura' - NSGaga-principalmente-inactivo

Tenga en cuenta que el uso AsNoTracking() hará que sea imposible usar la carga diferida. - Tom Pažourek

No olvide anular public override Task<int> SaveChangesAsync() también. - Pete

No confíes en esto, porque (context as IObjectContextAdapter).ObjectContext.SaveChanges() seguirá funcionando. La mejor opción es utilizar el DbContext(string nameOrConnectionString); constructor con una cadena de conexión de lectura/escritura para la creación de la base de datos y una cadena de conexión de solo lectura después. - Jürgen Steinblock

@bricelam En EntityFrameworkCore debería ser public IQueryable<Customer> Customers => Set<Customer>().AsNoTracking(); - dipix

A diferencia de la respuesta aceptada, creo que sería mejor favorecer la composición sobre la herencia. Entonces no habría necesidad de mantener métodos como SaveChanges para lanzar una excepción. Además, ¿por qué necesita tener tales métodos en primer lugar? Debe diseñar una clase de manera que su consumidor no se deje engañar cuando mira su lista de métodos. La interfaz pública debe estar alineada con la intención y el objetivo reales de la clase, mientras que en la respuesta aceptada tener SaveChanges no implica que Context sea de solo lectura.

En lugares donde necesito tener un contexto de solo lectura, como en el lado de lectura de CQRS patrón, uso la siguiente implementación. No proporciona nada más que capacidades de consulta a su consumidor.

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();
    }
}

Al usar ReadOnlyDataContext, puede tener acceso solo a las capacidades de consulta de DbContext. Digamos que tiene una entidad llamada Orden, luego usaría la instancia de ReadOnlyDataContext de la siguiente manera.

readOnlyDataContext.Set<Order>().Where(q=> q.Status==OrderStatus.Delivered).ToArray();

Una opción alternativa, si desea elegir manualmente (y limitar) qué entidades se exponen a través de este nuevo contexto. Eliminaría el método genérico anterior (el bloque completo con TEntity) y usaría algo similar al siguiente.

    public IQueryable<MyFirstThing> MyFirstHandPickThings => this.dbContext.Set<MyFirstThing>().AsNoTracking();

    public IQueryable<MySecondThing> MySecondHandPickThings => this.dbContext.Set<MySecondThing>().AsNoTracking();

respondido 17 mar '21, 12:03

¿Este método permite el uso de un inicio de sesión sql de db_datareader solo? Con un DBContext estándar, EF lanza el permiso CREATE TABLE denegado incluso cuando mi código de consulta no incluye ningún SaveChanges (). - llegando al nexo

Y hacerlo heredar de IDisposable - hkarask

En lugar de usar Set<>, sugeriría Query<>. public IQueryable<TEntity> Get<TEntity>() where TEntity : class { return _dbContext.Query<TEntity>().AsNoTracking(); } - allan nielsen

@hkarask: no estoy seguro de que haría eso. Dado que esta llamada no creó el DbContext, no debe desecharlo. Esto podría conducir a algunos errores difíciles de rastrear más tarde. - allan nielsen

@AllanNielsen Query<> está marcado como obsoleto. De acuerdo con esto, se debe usar Set<>. - Frank

public sealed class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
        : base(options)
    {
        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    }
}

y anula SaveChanges para lanzar Exception

Respondido 23 ago 21, 10:08

Como DbQuery<T> ya no está disponible en Núcleo del marco de la entidad, necesita modificar un poco la respuesta de @bricelam y usar directamente IQueryable<T> en lugar:

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

    // [...]
}

Respondido 11 Oct 21, 13:10

En mi escenario con EF Core/.NET 5.0, quería tener seguridad en tiempo de compilación para SaveChanges. Esto solo funcionó con "nuevo" en lugar de "anular".

Estoy usando contextos de lectura/escritura y solo lectura uno al lado del otro, donde uno hereda del otro ya que hay muchas tablas adjuntas. Esto es lo que uso, siendo "ContextData" mi R/W DbContext original:

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.");
    }
}

Tenga en cuenta que:

  • Tuve que usar "nuevo" en lugar de "anular" al sobrescribir SaveChanges*() heredados para tener advertencias/errores. Con "anular", no hay errores/advertencias de tiempo de compilación en absoluto.

  • Con "anular" obtienes CS0809 [1], pero no con "nuevo"

  • El uso de "nuevo" solo funcionará para la clase en sí, pero no en el contexto del padre:

    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
    
  • Se requiere la elección adecuada de argumentos (opcionales) para las variantes de SaveChanges y SaveChangesAsync. (Esto es para .NET 5.0, no he comprobado si varía para otras versiones de EF Core/EF)

En resumen

  1. "anular" proporcionaría una herencia completa, pero no funciona en mi entorno
  2. "nuevo" proporciona la función deseada, pero devolverá resultados inesperados para ciertos escenarios de polimorfismo
  3. No usar la herencia en absoluto será doloroso si tiene muchas tablas

==> No existe la bala de plata, y la elección depende del gusto y las circunstancias...

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

Respondido 11 Oct 21, 17:10

Situación: necesitaba hacer referencia a DB1 para crear registros en DB2 y quería proteger DB1 en el proceso. DB1 y DB2 son copias de esquema entre sí.

Actualicé el archivo de contexto de entidad generado automáticamente. Y coloque una opción de solo lectura cuando cree una instancia del contexto de la entidad con una anulación de SaveChanges() para anular las escrituras cuando use la opción de solo lectura.

Desventajas:

  1. Tienes que crear una cadena de conexión EF separada en la configuración de configuración
  2. Deberá tener cuidado al actualizar automáticamente el modelo. Guarde una copia de los cambios en su código y recuerde aplicarlo después de las actualizaciones del modelo.
  3. No se proporciona ningún aviso de que no se realizó el guardado. Elegí no proporcionar un aviso porque mi uso es muy limitado y realizamos muchos guardados.

Las ventajas:

  1. No tiene que implementar una solución de tipo CQRS.
  2. Al usar el mismo modelo de entidad, no tiene que crear uno segundo y mantenerlo también.
  3. Sin cambios en la base de datos ni en sus cuentas de usuario.

Solo asegúrese de nombrar su instanciación de contexto para nombrarla usando ReadOnly o algo similar.

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(); }
    }

..... }

Respondido 25 ago 21, 21:08

está bien. Abajo marcado, pero ¿por qué? - Scooter

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.