Open Closed

Abp.EntityFramework does not work with Core 2.x #7550


0
timmackey created

Abp.EntityFrameworkCore interface does not implement IDbSet interface as specified on this page: https://aspnetboilerplate.com/Pages/Documents/EntityFramework-Integration?searchKey=AbpDbContext

I want to access MS EFCore directly instead of using Repository (which runs 15X slower than ASP.NET EF). My project is Angluar/Core.

When 'Abp.EntityFrameworkCore' is included in my project I keep getting the following error at Web.Host.Startup.cs, line 134: System.TypeLoadException HResult=0x80131522 Message=Method 'get_IsTraceEnabled' in type 'Abp.Castle.Logging.Log4Net.Log4NetLogger' from assembly 'Abp.Castle.Log4Net, Version=4.6.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. Source=Abp.Castle.Log4Net


19 Answer(s)
  • 0
    maliming created

    use Abp.EntityFrameworkCore package

    https://aspnetboilerplate.com/Pages/Documents/Entity-Framework-Core

  • 0
    timmackey created

    Implemented code per the above link.

    Error occurs in Startup.cs at "//Configure Log4Net logging" on "AddFacility" method.

    System.TypeLoadException
      HResult=0x80131522
      Message=Method 'get_IsTraceEnabled' in type 'Abp.Castle.Logging.Log4Net.Log4NetLogger' from assembly 'Abp.Castle.Log4Net, Version=4.6.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
      Source=Abp.Castle.Log4Net
      StackTrace:
       at Abp.Castle.Logging.Log4Net.Log4NetLoggerFactory.Create(String name)
       at Castle.Facilities.Logging.LoggingFacility.RegisterDefaultILogger(ILoggerFactory factory)
       at Castle.Facilities.Logging.LoggingFacility.Init()
       at Castle.MicroKernel.DefaultKernel.AddFacility(IFacility facility)
       at Castle.Windsor.WindsorContainer.AddFacility[T](Action`1 onCreate)
       at ngTTM.Web.Startup.Startup.<ConfigureServices>b__4_6(AbpBootstrapperOptions options) in C:\Users\Tim\Documents\__ngTTMv700\aspnet-core\src\ngTTM.Web.Host\Startup\Startup.cs:line 146
       at Abp.AbpBootstrapper..ctor(Type startupModule, Action`1 optionsAction)
       at Abp.AbpBootstrapper.Create[TStartupModule](Action`1 optionsAction)
       at Abp.AspNetCore.AbpServiceCollectionExtensions.AddAbpBootstrapper[TStartupModule](IServiceCollection services, Action`1 optionsAction)
       at Abp.AspNetCore.AbpServiceCollectionExtensions.AddAbp[TStartupModule](IServiceCollection services, Action`1 optionsAction)
       at ngTTM.Web.Startup.Startup.ConfigureServices(IServiceCollection services) in C:\Users\Tim\Documents\__ngTTMv700\aspnet-core\src\ngTTM.Web.Host\Startup\Startup.cs:line 143
    

    Web.Host/Startup/Startup.c

            public IServiceProvider ConfigureServices(IServiceCollection services)
            {
                //MVC
                services.AddMvc(options =>
                {
                    options.Filters.Add(new CorsAuthorizationFilterFactory(DefaultCorsPolicyName));
                }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
                services.AddSignalR(options => { options.EnableDetailedErrors = true; });
    
                //Configure CORS for angular2 UI
                services.AddCors(options =>
                {
                    options.AddPolicy(DefaultCorsPolicyName, builder =>
                    {
                        //App:CorsOrigins in appsettings.json can contain more than one address with splitted by comma.
                        builder
                            .WithOrigins(
                                // App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
                                _appConfiguration["App:CorsOrigins"]
                                    .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                    .Select(o => o.RemovePostFix("/"))
                                    .ToArray()
                            )
                            .SetIsOriginAllowedToAllowWildcardSubdomains()
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials();
                    });
                });
    
                IdentityRegistrar.Register(services);
                AuthConfigurer.Configure(services, _appConfiguration);
    
                //Identity server
                if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
                {
                    IdentityServerRegistrar.Register(services, _appConfiguration);
                }
    
                if (WebConsts.SwaggerUiEnabled)
                {
                    //Swagger - Enable this line and the related lines in Configure method to enable swagger UI
                    services.AddSwaggerGen(options =>
                    {
                        options.SwaggerDoc("v1", new Info { Title = "ngTTM API", Version = "v1" });
                        options.DocInclusionPredicate((docName, description) => true);
                        options.UseReferencedDefinitionsForEnums();
                        options.ParameterFilter<SwaggerEnumParameterFilter>();
                        options.SchemaFilter<SwaggerEnumSchemaFilter>();
                        options.OperationFilter<SwaggerOperationIdFilter>();
                        options.OperationFilter<SwaggerOperationFilter>();
                        options.CustomDefaultSchemaIdSelector();
    
                        //Note: This is just for showing Authorize button on the UI. 
                        //Authorize button's behaviour is handled in wwwroot/swagger/ui/index.html
                        options.AddSecurityDefinition("Bearer", new BasicAuthScheme());
                    });
                }
    
                //Recaptcha
                services.AddRecaptcha(new RecaptchaOptions
                {
                    SiteKey = _appConfiguration["Recaptcha:SiteKey"],
                    SecretKey = _appConfiguration["Recaptcha:SecretKey"]
                });
    
                if (WebConsts.HangfireDashboardEnabled)
                {
                    //Hangfire(Enable to use Hangfire instead of default job manager)
                    services.AddHangfire(config =>
                    {
                        config.UseSqlServerStorage(_appConfiguration.GetConnectionString("Default"));
                    });
                }
    
                if (WebConsts.GraphQL.Enabled)
                {
                    services.AddAndConfigureGraphQL();
                }
    
                // configure NgTtmAbpDbContext context
                services.AddAbpDbContext<NgTtmAbpDbContext>(options =>
                {
                    options.DbContextOptions.UseSqlServer(options.ConnectionString);
                });
    
                //Configure Abp and Dependency Injection
                return services.AddAbp<ngTTMWebHostModule>(options =>
                {
                    //Configure Log4Net logging
                    options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                        f => f.UseAbpLog4Net().WithConfig("log4net.config")
                    );
    
                    options.PlugInSources.AddFolder(Path.Combine(_hostingEnvironment.WebRootPath, "Plugins"), SearchOption.AllDirectories);
                });
            }
    

    DbContextLibrary/NgTtmAbpDbContext.cs

    using Abp.EntityFrameworkCore;
    using Abp.Modules;
    using Microsoft.EntityFrameworkCore;
    using ngTTM.TtmDataModel;
    
    namespace DbContextLibrary
    {
        [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
        public class NgTtmAbpDbContext : AbpDbContext
        {
            public virtual DbSet<AnswerMatch> AnswerMatchs { get; set; }
    
            public virtual DbSet<Answer> Answers { get; set; }
    
            public virtual DbSet<Question> Questions { get; set; }
    
            public virtual DbSet<QuestionBank> QuestionBanks { get; set; }
    
            public NgTtmAbpDbContext(DbContextOptions<NgTtmAbpDbContext> options)
                : base(options)
            {
            }
        }
    }
    

    Web.Host/Startup/ngTTMWebHostModule.cs

            public override void PreInitialize()
            {
                Configuration.Modules.AbpWebCommon().MultiTenancy.DomainFormat = _appConfiguration["App:ServerRootAddress"] ?? "http://localhost:22742/";
                Configuration.Modules.AspNetZero().LicenseCode = _appConfiguration["AbpZeroLicenseCode"];
                Configuration.DefaultNameOrConnectionString = GetConnectionString("Default");
            }
    
            private string GetConnectionString(string conn)
            {
                var appsettingsjson = JObject.Parse(File.ReadAllText("appsettings.json"));
                var dbConnectionStrings = (JObject)appsettingsjson["ConnectionStrings"];
    
                string connStr = dbConnectionStrings.Property("Default").Value.ToString();
                return connStr;
            }
    
  • 0
    maliming created

    Method 'get_IsTraceEnabled' in type 'Abp.Castle.Logging.Log4Net.Log4NetLogger' from assembly 'Abp.Castle.Log4Net, Version=4.6.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

    https://github.com/aspnetboilerplate/aspnetboilerplate/issues/4445#issuecomment-495637354 https://github.com/aspnetboilerplate/aspnetboilerplate/pull/4539

    Usually you only need to upgrade the ABP package.

  • 0
    timmackey created

    Upgraded Abp.Castle.Log4Net, Abp.RedisCcache, Abp, Abp.ZeroCore from 4.6.0 to 4.8.1. Startup works fine. Using context causes Exception:

    Message: LoadQuestionsXml.cs, ParseQuestionFile
    Exception Type: System.InvalidOperationException
    Exception: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
    Source: Microsoft.EntityFrameworkCore
    Stack Trace: 
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
       at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
       at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
       at Microsoft.EntityFrameworkCore.DbContext.get_Model()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
       at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
       at ImportFilesLibrary.LoadQuestionsXml.ParseQuestionFile(MemoryStream FileContent, Int32 QuestionBankId, Nullable`1 userId) in C:\Users\Tim\Documents\__ngTTMv700\aspnet-core\...myfile.cs
    

    ...at " var data = context.QuestionBanks.FirstOrDefault(b => b.Id == 123);"

    public class myClass
    {
            private static DbContextOptions<NgTtmAbpDbContext> dbContextOptions = new DbContextOptions<NgTtmAbpDbContext>();
    
    public void MyFunction()
    {
        using (NgTtmAbpDbContext context = new NgTtmAbpDbContext(dbContextOptions))
        {
                var data =  context.QuestionBanks.FirstOrDefault(b => b.Id == 123);
        }
    

    Tried resolving error message, but Google searched solutions did not work.

  • 0
    maliming created

    In general you should not use AbpDbContext directly. You should use Repository. https://aspnetboilerplate.com/Pages/Documents/Repositories

    I suggest you first check the abp & abp zero documentation.

    https://aspnetboilerplate.com/Pages/Documents/ https://docs.aspnetzero.com/documents/common/latest/

  • 0
    timmackey created

    I initially migrated some ASP.NET code to my ANZ app and used injection and IRepository as is used throughout ANZ. However, the performance for db-intensive operations is 15X SLOWER (due I surmise to the transactional nature of the interface). The db-intensive portion of my app is not built on the transaction model. Your first reply in this thread directed me to Enitity-Framework-Core, which I implemented. Now you're telling me that I shouldn't use it. The app I'm migrating from ASP.NET accessed EF directly. ANZ InitialHostDbBuilder class uses EF context directly. I need to access EF directly for performance reasons. Why can't access context directly in my app? Given https://aspnetboilerplate.com/Pages/Documents/Entity-Framework-Core is published documentation, how to I make it work?

  • 0
    maliming created

    You can try to get DbContext using IDbContextProvider instead of creating it manually.

  • 0
    timmackey created

    Could you please direct me to the documentation and some sample code?

  • 0
    maliming created

    Very simple, inject the IDbContextProvider interface and then call the method to get the DbContext.

    public interface IDbContextProvider<out TDbContext>
    	where TDbContext : DbContext
    {
    	TDbContext GetDbContext();
    
    	TDbContext GetDbContext(MultiTenancySides? multiTenancySide );
    }
    

    https://github.com/aspnetzero/aspnet-zero-core/blob/16582df85fbceba18fe61fdd613d1093ef25921c/aspnet-core/src/MyCompanyName.AbpZeroTemplate.Application/HealthChecks/AbpZeroTemplateDbContextUsersHealthCheck.cs#L14

    image.png

  • 0
    timmackey created

    I implemented the changes as described above. My app compiles and runs without encountering Exceptions. The records in the db are as expected. However, the performance of the IDbContextProvider interface is 10X SLOWER than the equivalent MS EntityFramework context db access in ASP.NET, and is transactional. No data is actually written to the db until the thread completes and the IDbContextProvider is Disposed by the task caller. Multiple calls to "context.SaveChanges()" have no effect on the db. Why must I use IDbContextProvider interface? How can I get better performance from this interface? How can I override the transactional nature of this interface (without resorting to more overhead via UnitOfWork)? Why can I not use MS EntityFrameworkCore directly? And if I can, how?

  • 0
    maliming created

    Because the entire framework is based on dependency injection.

    No database provider has been configured for this DbContext. ....

    try

    var builder = new DbContextOptionsBuilder<AbpZeroTemplateDbContext>();
    builder.UseSqlServer(_appConfiguration.GetConnectionString("Default"));
    var db = new AbpZeroTemplateDbContext(builder.Options);
    var user = db.Users.FirstOrDefault();
    
    
  • 0
    timmackey created

    Getting an Exception when the above is implemented:

    Exception Type: System.InvalidOperationException
    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'.
    Source: Microsoft.EntityFrameworkCore
    Stack Trace: 
       at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.PropertyMappingValidationConvention.Apply(InternalModelBuilder modelBuilder)
       at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
       at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
       at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
    --- End of stack trace from previous location where exception was thrown ---
       at System.Lazy`1.CreateValue()
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
       at lambda_method(Closure , ServiceProviderEngineScope )
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
       at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
       at Microsoft.EntityFrameworkCore.DbContext.get_Model()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
       at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
       at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
       at ImportFilesLibrary.LoadQuestionsXml.ParseQuestionFile(MemoryStream FileContent, Int32 QuestionBankId, Nullable`1 userId) in C:\Users\Tim\Documents\__ngTTMv700\aspnet-core\ttm\ImportFilesLibrary\LoadQuestionsXml.cs:line 69
    

    at LoadQuestionsXml.cs:line 69 is fhe following code:

                    QuestionBank questionBank =
                        context.QuestionBanks
                               .FirstOrDefault(b => b.QuestionBankId == QuestionBankId);
    

    NgTtmAbpDbContext.cs

    using Abp.EntityFrameworkCore;
    using Abp.Modules;
    using Microsoft.EntityFrameworkCore;
    using ngTTM.TtmDataModel;
    
    namespace DbContextLibrary
    {
        [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
        public class NgTtmAbpDbContext : AbpDbContext
        {
            public virtual DbSet<AnswerMatch> AnswerMatchs { get; set; }
    
            public virtual DbSet<Answer> Answers { get; set; }
    
            public virtual DbSet<Question> Questions { get; set; }
    
            public virtual DbSet<QuestionBank> QuestionBanks { get; set; }
    
            public NgTtmAbpDbContext(DbContextOptions<NgTtmAbpDbContext> options)
                : base(options)
            {
            }
        }
    }
    

    Implemented the code fragment you suggested as:

                    var builder = new DbContextOptionsBuilder<NgTtmAbpDbContext>();
                    string connStr = _appConfiguration.GetConnectionString("Default");
                    builder.UseSqlServer(connStr);
                    NgTtmAbpDbContext context = new NgTtmAbpDbContext(builder.Options);
    
  • 0
    maliming created

    hi @timmackey Can you reproduce your problem using Zero's Demo project? Send to: liming.ma@volosoft.com

  • 0
    timmackey created

    The previously reported Exception has been eliminated by modifying the class as follows:

        public class NgTtmAbpDbContext : AbpZeroDbContext<Tenant, Role, User, NgTtmAbpDbContext>
    

    No Exceptions occur, however, an earlier created table is not being updated:

                    QuestionBank questionBank =
                        context.QuestionBanks
                               .FirstOrDefault(b => b.QuestionBankId == QuestionBankId);
                    if (questionBank == null)
                    {
                        status.FailureReason = FailureReason("UnableToOpenQuestionBankId_0", QuestionBankId.ToString());
                        status.Success = false;
                        return status;
                    }
    
                    questionBank.ItemsTotal = elementCount * 2;
                    questionBank.ItemsProcessed = 0;
                    context.SaveChanges();
    

    "context.SaveChanges();" is not updating QuestionBanks table.

  • 0
    maliming created

    hi @timmackey

    The code you shared made me unable to determine the problem.

    Can you reproduce your problem using Zero's Demo project? Send to: liming.ma@volosoft.com

  • 0
    timmackey created

    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.

  • 0
    ismcagdas created

    Hi @timmackey

    Your last problem might be related to https://github.com/aspnetboilerplate/aspnetboilerplate/issues/4691

  • 0
    ismcagdas created

    Hi @timmackey

    Did you have a chance to try this ?

  • 0
    timmackey created

    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.