Sorry but I don't see any difference from my code. Note the I need to access in the same request to both repository:
public async Task<DatiSatinariDto> GetAsync(int id)
{
var logs = await _auditDataRepository.GetAsync(20136); // <-- DEFAULT DATABASE
var datiSanitari = await _datiSantitariRepository.GetAsync(id); // <-- EXTERNAL DATABASE
var result = datiSanitari.MapTo<DatiSatinariDto>();
return result;
}
The errore message is the same:
ERROR 2018-03-12 17:43:33,830 [17 ] Mvc.ExceptionHandling.AbpExceptionFilter - Both an existing DbConnection and a connection string have been configured. When an existing DbConnection is used the connection string must be set on that connection. System.InvalidOperationException: Both an existing DbConnection and a connection string have been configured. When an existing DbConnection is used the connection string must be set on that connection. at Microsoft.EntityFrameworkCore.Storage.RelationalConnection..ctor(RelationalConnectionDependencies dependencies)
Yes, many attempts produce different error, but the main is this:
ERROR 2018-03-12 08:55:44,332 [3 ] Mvc.ExceptionHandling.AbpExceptionFilter - Both an existing DbConnection and a connection string have been configured. When an existing DbConnection is used the connection string must be set on that connection. System.InvalidOperationException: Both an existing DbConnection and a connection string have been configured. When an existing DbConnection is used the connection string must be set on that connection. at Microsoft.EntityFrameworkCore.Storage.RelationalConnection..ctor(RelationalConnectionDependencies dependencies)
If I try to change the connection on the existing Dbconnection the error is:
ERROR 2018-03-12 09:25:43,181 [9 ] Mvc.ExceptionHandling.AbpExceptionFilter - Not allowed to change the 'ConnectionString' property. The connection's current state is open. System.InvalidOperationException: Not allowed to change the 'ConnectionString' property. The connection's current state is open. at System.Data.SqlClient.SqlConnection.ConnectionString_Set(DbConnectionPoolKey key) at System.Data.SqlClient.SqlConnection.set_ConnectionString(String value)
For @BBakerMMC : What you say is exactly what I would like to obtain.
Hi, I need to use two different dbcontext at same request to fill out a dto. One dbcontext use the default Abp database where I have Host and all Tenants. The other dbcontext needs read and write a second database in order to collect additional data but in the same client request. In addition, every tenant has his own second database and so the connection string changes at runtime. None of the solutions I've tried works.
When the second context is created, the ExistingConnection is not null but I can't change it and I can't open a new one one the same transaction.
How can I achieve my requirement ?
public override void PreInitialize()
{
// Default Context on Abp Database
Configuration.Modules.AbpEfCore().AddDbContext<AdtContext>(options =>
{
if (options.ExistingConnection != null)
{
AdtConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
AdtConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
// Second Db Context on different database that change for every Tenant
Configuration.Modules.AbpEfCore().AddDbContext<AdtLegacyContext>(options =>
{
if (options.ExistingConnection != null)
{
AdtLegacyConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
AdtLegacyConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
}
The configurers:
public class AdtConfigurer
{
private const string MigrationTableName = "__AdtMigrationsHistory";
private const string MigrationTableSchema = "adt";
//https://github.com/aspnet/EntityFrameworkCore/issues/2180
public static void Configure(DbContextOptionsBuilder<AdtContext> builder, string connectionString)
{
builder.UseSqlServer(connectionString, o =>
o.MigrationsHistoryTable(MigrationTableName, MigrationTableSchema)
.UseRowNumberForPaging());
}
public static void Configure(DbContextOptionsBuilder<AdtContext> builder, DbConnection connection)
{
builder.UseSqlServer(connection, o =>
o.MigrationsHistoryTable(MigrationTableName, MigrationTableSchema)
.UseRowNumberForPaging());
}
}
public class AdtLegacyConfigurer
{
private const string MigrationTableName = "__AdtMigrationsHistory";
private const string MigrationTableSchema = "adt";
//https://github.com/aspnet/EntityFrameworkCore/issues/2180
//Quando l'issue viene risolta si può togliere UseRowNumberForPaging
public static void Configure(DbContextOptionsBuilder<AdtLegacyContext> builder, string connectionString)
{
builder.UseSqlServer(connectionString, o =>
o.MigrationsHistoryTable(MigrationTableName, MigrationTableSchema)
.UseRowNumberForPaging());
}
public static void Configure(DbContextOptionsBuilder<AdtLegacyContext> builder, DbConnection connection)
{
builder.UseSqlServer(connection, o =>
o.MigrationsHistoryTable(MigrationTableName, MigrationTableSchema)
.UseRowNumberForPaging());
}
}
The Default Context (All dataset omitted for brevity)
public class AdtContext: AbpDbContext
{
public DbSet<AuditData> Audits { get; set; }
public AdtContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new AuditConfiguration());
}
}
The second context (All dataset omitted for brevity):
public class AdtLegacyContext : AbpDbContext
{
private readonly ILegacyConnectionStringService _legacyConnectionStrings;
public AdtLegacyContext(DbContextOptions options, ILegacyConnectionStringService legacyConnectionStrings) : base(options)
{
_legacyConnectionStrings = legacyConnectionStrings;
}
public DbSet<TrcAnagraficaData> TrcAnagrafiche { get; set; }
.......
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var tenantId = AbpSession.GetTenantId();
var connectionString = _legacyConnectionStrings.GetConnectionString(tenantId); // <-- This resolve the connection string for tenant
optionsBuilder.UseSqlServer(connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new TrcAnagraficaConfiguration());
......
}
}
Thank you.
Hi, the [DisableAuditing] attribute doesn't work if when my AppService implements AsyncCrudAppService. How can I disable the auditing?
Thanks
Hi, I suspect that the "scope" meaning of Feature is ignored. If I set a new feature with scope FeatureScopes.Edition, it is assignable to Tenant as well (Default Angular UI)
From docs:
Scope: A value in the FeatureScopes enum. It can be Edition (if this feature can be set only for edition level), Tenant (if this feature can be set only for tenant level) or All (if this feature can be set for editions and tenants, where a tenant setting overrides its edition's setting). Default value is All.
Is an obsolete settings? I've noted that in downloaded code you don't use scope nowhere (AppFeatures.MaxUserCount, AppFeatures.TestCheckFeature, and so on)
OK @aaron !!! It works Now I have what I need.
Thank you so much.
Yes I would like to throw UserFriendlyException but where I can catch the DbConcurrencyError ?
Expected behaviour: using IRepository<MyEntity> if DbConcurrencyError happens I want to catch the exception and display to user a custom message instead of error.
Now I have in response to client;
{
"result": null,
"targetUrl": null,
"success": false,
"error": {
"code": 0,
"message": "An internal error occurred during your request!",
"details": null,
"validationErrors": null
},
"unAuthorizedRequest": false,
"__abp": true
}
and in log
ERROR 2018-01-23 17:43:24,295 [10 ] Mvc.ExceptionHandling.AbpExceptionFilter - Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
Abp.Domain.Uow.AbpDbConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. ---> Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
This is my repository
public class OspiteRepository : IOspiteRepository
{
private readonly IRepository<OspiteData> _ospiteDataRepository;
public OspiteRepository(IRepository<OspiteData> ospiteDataRepository)
{
_ospiteDataRepository = ospiteDataRepository;
}
public void UpdateOspite(Ospite ospite)
{
_ospiteDataRepository.Update(ospite.Snapshot);
}
}
No. Just did now for all save overrides. The code pass in my context on those methods.. but the exception is not raised here but ahead in code. In logs.txt I can see Database operation expected to affect 1 row(s) but actually affected 0 row(s)
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
{
try
{
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
catch (Exception e)
{
throw;
}
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
try
{
return base.SaveChangesAsync(cancellationToken);
}
catch (Exception e)
{
throw;
}
}
Yes I did, but SaveChanges on my context is not raised.
Always the base method is called. I guess I'm missing something but i don't know what.