The resolution of #7635 obviated a need for this approach.
Converting to virtual solved the issue. See #7550 for complete solution.
The above suggested solution was only partially related to my problem. I solved the issue by conditionally compiling out foreign keys (but not the id) in tables that referenced referenced Tenant. i.e.
[Table("Buildings")]
public class Building : FullAuditedEntity , IMayHaveTenant
{
#if !CUSTOM_DB_CONTEXT_LIB
[ForeignKey("TenantId")]
public virtual Tenant Tenant { get; set; }
#endif
public virtual int? TenantId { get; set; }
[Required]
[StringLength(BuildingConsts.MaxBuildingNameLength, MinimumLength = BuildingConsts.MinBuildingNameLength)]
public virtual string BuildingName { get; set; }
...
}
This allows me to use the same table definition files in ANZ-generated context
public class AnzAppDbContext : AbpZeroDbContext<Tenant, Role, User, AnzAppDbContext>, IAbpPersistedGrantDbContext
and my custom context for .NET Core 2.2, which is...
using Microsoft.EntityFrameworkCore;
namespace CustomDbContextLibrary
{
public class CustomDbContext : DbContext
{
public virtual DbSet<Building> Buildings { get; set; }
...
public virtual DbSet<Room> Rooms { get; set; }
public ExamDbContext(DbContextOptions<ExamDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
The class constructor is
using Microsoft.EntityFrameworkCore;
#if CUSTOM_DB_CONTEXT_LIB
using CustomDbContextLibrary;
using DbContext = CustomDbContextLibrary.CustomDbContext;
#else
using AnzApp.AnzAppDataModel;
using DbContext = AnzApp.EntityFrameworkCore.AnzAppDbContext;
#endif
using Abp.EntityFrameworkCore;
using Abp.Domain.Uow;
using System.Transactions;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Hosting;
private readonly IDbContextProvider<DbContext> _dbContextProvider;
private readonly IConfigurationRoot _appConfiguration;
private readonly DbContextOptionsBuilder<DbContext> _dbContextOptionsBuilder;
//-----------------------------------------------------------------------------------------
public CreateSomethingClass(
IDbContextProvider<DbContext> dbContextProvider,
IHostingEnvironment hostingEnvironment
)
{
_dbContextProvider = dbContextProvider;
_appConfiguration = hostingEnvironment.GetAppConfiguration();
_dbContextOptionsBuilder = new DbContextOptionsBuilder<DbContext>();
string connStr = _appConfiguration.GetConnectionString("Default");
_dbContextOptionsBuilder.UseSqlServer(connStr);
}
Usage is
public virtual bool Create(string guid, string rootFolder)
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
{
DbContext context = new DbContext(_dbContextOptionsBuilder.Options);
{
Building bldg = context.Buildings.First(x => x.BuildingName == "Electric Tower") ;
bldg.BuildingName = "Tower Two";
context.SaveChanges();
...
// thousands of lines of code not shown
}
}
}
This provides performance equivalent to ASP.NET EntityFramework when referencing many tables for high frequency bursts of create/read/update rows.
Making the Create method virtual, as suggested in ticket #7635 eliminates transactional nature of db access. This resolves all issues.
@maliming and @ismcagdas - Thank you for your all your suggestions.
When will version 7.2.3 be available for Download?
WorkingDirectory
confirmed.
@ismcagdas,
I'm not confident that converting my app from EF to SQL would be effective. it's a lot of code that would need to be changed, and I'm concerned that SQL updates will not effect until transaction/thread completion after all the changes.
I'm using EF in ASP.NET Framework 4.5.2 in my original app; excellent performance and the db is updated upon context.SaveChanges()
.
EF in Zero/Core however has slower performance and the db is NOT updated upon context.SaveChanges()
; the db is updated upon transaction/thread completion.
I think the mystery now is why non-transactional unit of work attributes (as recommended by @maliming) are not working.
I implemented the suggested code changes. Records are not committed to the physical db and performance is 3X SLOWER than the same code in ASP.NET.
Is there anything I might have missed that would be preventing commit or result in degraded performance?
using System;
using System.Collections.Generic;
using System.Linq;
#if NETFRAMEWORK
using DataModelLibrary.Exams;
using DbContext = DataModelLibrary.Exams.DbContext_Exam;
#endif
#if NETCOREAPP // https://docs.microsoft.com/en-us/dotnet/standard/frameworks
using Abp.Domain.Uow;
using Abp.EntityFrameworkCore;
using System.Transactions;
using ngTTM.TtmDataModel;
using DbContext = ngTTM.EntityFrameworkCore.ngTTMDbContext;
#endif
// for clarity, all NETFRAMEWORK-specific code has been removed
namespace JobsLibrary
{
public class CreateExamsDemo
{
//-----------------------------------------------------------------------------------------
private readonly IDbContextProvider<DbContext> _dbContextProvider;
//-----------------------------------------------------------------------------------------
public CreateExamsDemo(IDbContextProvider<DbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}
//-----------------------------------------------------------------------------------------
[UnitOfWork(IsDisabled = true)]
// [UnitOfWork(isTransactional: false)]
public bool Create(string guid, string rootFolder)
{
DateTime _StartTime = DateTime.Now;
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
{
DbContext context = _dbContextProvider.GetDbContext();
{
Exam exam = context.Exams.First(x => x.Guid == guid);
// not shown are calls to other functions accessing many db tables
// the code fragment below illustrates the concept
List<ExamQuestion> examQuestionsList = new List<ExamQuestion>();
// add multiple ExamQuestion to list
context.ExamQuestions.AddRange(examQuestionsList);
// acquire table "id" values used in next processing steps (not shown)
context.SaveChanges();
// typical execution time in NETFRAMEWORK is approx. 4 minutes
// typical execution time in NETCOREAPP is approx. 16 minutes
exam.CreateDuration = (float)(DateTime.Now - _StartTime).TotalSeconds;
context.SaveChanges();
return true;
}
}
}
catch (Exception ex)
{
// Exception handler
return false;
}
//-----------------------------------------------------------------------------------------
}
}
}
Where does this function reside? How and when is it invoked? The Abp documentation page does not answer these questions.
Why is it not a good practice? How is a legacy Console app that was created outside the Abp/Zero universe executed?
Created a project based on Zero's Demo project.
Unable to reproduce the error
Exception: Unable to determine the relationship represented by navigation property 'User.DeleterUser' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
in the Demo project due, I suspect, to the specific table/foreign key relationships in my db, which I would be impractical to reproduce in a demo project.
I will submit a new issue that I need to resolve that may reveal a solution to this issue.