Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Abp Module without dependencies on main app #1476

Closed
6 of 16 tasks
brunobertechini opened this issue Oct 17, 2016 · 78 comments
Closed
6 of 16 tasks

Create Abp Module without dependencies on main app #1476

brunobertechini opened this issue Oct 17, 2016 · 78 comments

Comments

@brunobertechini
Copy link

brunobertechini commented Oct 17, 2016

Abp architecture for MicroServices/SOA

Goal: Create a working sample using Abp in order to illustrate MicroServices/SOA approach.

The concept I am willing to achieve is SOA Done Right, or Microservices, or BoundedContexts if you will. They all refer to the same concept like One Service (separated from main app) should be autonomous and do not depend on anything else.

In this concept of SOA services, whithin each service you are free to use DDD, CQRS, any database you want. For now, I am using Abp/DDD approach for the Blog Module because it is the first one being developed for this sample and will adhere better with Abp for our discussions.

Sample: https://github.com/brunobertechini/abp-microservices

  • Service without reference to main app
  • Main app without reference to service projects
  • Migrations executed automatically for each service
  • Features per Service
  • Authorization (Permissions) per Service
  • Localization per Service
  • Implement ServiceBus Layer to be able to use DomainEvents, NServiceBus, MassTransit
  • Implement UnitTest abstraction
  • Discover Controllers (pure mvc controller) in custom module
  • Migration seed for custom authorization providers

Items that need interaction/issues to be opened in AspnetBoilerplate:

  • Move MimeTypeNames from Abp.AspNetZeroCore.Net to a more common project (avoid dependencie on Abp.AspNetZeroCore.Net only to have MimeTypeNames)
  • Move IAppFolders to Core.Shared
  • Move ErpServiceBase to Core.Shared
  • Move EpPlusExcelExporterBase to Application.Shared
  • Review Localization Provider for single/splitted approach for main and custom apps
  • Flexible configuration for running migrations as part of PostInitialize of EntityFrameworkCoreModule

Pending: Add the full list of requirements to this post.

I will update this list whenever I have a time-frame available.

P.s.: I have edited this HEAD post in order to have a on-going task list for this issue.

------ Original Post ---------
Hello Halil,

I would like to manage to create a complete separate module using Abp / ModuleZero in order to have the nice features like multtenancy and so on.

I can't see a way to do that with current infrastructure provided by Abp. I would like to know if you have any tips for me so I can create a fork of Abp/Abp.Zero and make the necessary changes to do that.

Related to #88 (see my comments)

Lets state the main priorities

  1. Create a Abp module structure (like the BlogSample you have) without runtime dependency on MainApp Database.
  2. Use MultiTenancy
  3. Use Filters for automatic tenant set (using custom AbpSession)
  4. Use Automapper features
  5. Use custom dbcontext

If you just point me to the direction you have in mind I can work it out.

Bruno

@hikalkan
Copy link
Member

Hi,

Actually, depending and sharing on a common database was harder since it requires dbcontext inheritance.

I created blog module like that since I think in a real big application, you will have a common/shared base code and plugins/modules depends on that common code.

Anyway, I think it should be easier to create such a completely independent module. I would want to create one but I'm very busy on angular2 integration nowadays. Have you tried it? What difficulties you have? Maybe I can help you on your specific problems.

@iyhammad
Copy link
Contributor

Actually I've did that.

  1. I've created every module with his own dbcontext inherits abpdbcontext.
  2. For shared data, I've created mechanism by which any module has a shared entity, it raises an event if the entity change and the event data is json serialization of the entity.
  3. Any module can subscribe to any shared entity change event and read the json from the event data and may save it in his own table.
  4. The event itself is defined in a Shared module that you can depend on if you need access to those shared entities data.
  5. Every module has his own database schema by which I can define an employee entity for example in many modules but I don't have any issue because they are in different db scheme.

I've used the way that's used in the blog sample module but I had a tough time with database migration

@brunobertechini
Copy link
Author

brunobertechini commented Oct 17, 2016

@hikalkan The main problem and the first one is: Is that possible to inherit from AbpZeroDbContext without have the entities depending on main app ? Or should I go for AbpDbContext inheritance rather than AbpZeroDbContext ?

@iyhammad Can you shed some light on your DbContext? Are you inheriting from AbpDbContext and NOT AbpZeroDbContext ? If so, How do you managed to use the [AutoRepositoryTypes] ? I mean, are you able to use IMyCustomModuleRepository ?

@brunobertechini
Copy link
Author

@iyhammad are you able to share the source code? You can ommit sensitive data of course.

@iyhammad
Copy link
Contributor

Hi,
I'm inheriting from AbpDbContext. I'm not using [AutoRepositoryTypes] now. The default instance did the work for me. I can inject IRepository anywhere. And also I can create custom repositories and use [AutoRepositoryTypes]. Every dbcontext work alone now.
I definitely share some code. Let me work on it.

@brunobertechini
Copy link
Author

@iyhammad Thanks for sharing this information.

When you say "The default instance did the work for me." what does that mean?

@iyhammad
Copy link
Contributor

If you didn't specify [AutoRepositoryTypesAttribute] on your DbContext ABP will Register IRepository for all your DbContext DbSets.
So in your services you can inject IRepository. you can read this http://www.aspnetboilerplate.com/Pages/Documents/Repositories#DocIRepositoryClasses

@brunobertechini
Copy link
Author

Thanks @iyhammad

I see. I had only tried inheriting from AbpZeroDbContext. I will try with AbpDbContext.

In the meantime, do you know if its possible to use multitenancy filters and softdelete without AbpZeroDbContext? Do I need to replicate the code ?

@brunobertechini
Copy link
Author

Which class register IRepository<> ? Its not working for me.

It never gets registered

Which module should I depend upon ?

@iyhammad
Copy link
Contributor

You should depend on AbpZeroEntityFramework

@iyhammad
Copy link
Contributor

I always recommend you to create a new empty Abp solution and have a look at it.
This particular dependency you will find it in the entity framework module in the generated solution.

@brunobertechini
Copy link
Author

brunobertechini commented Oct 18, 2016

@iyhammad The problem relies on remove dependencies from main app

If I derive from AbpZero the module will need the main app database in order to work propertly. I want to avoid that.

Microservices should not have dependencies (not even in runtime)

Thats the purpose of my question (this issue)

I need a way to use it without dependency on main app database (using inheritance)

@brunobertechini
Copy link
Author

The usual solution works fine (they get injected correctly) but Im trying to setup a completely independent module as per my first post.

@brunobertechini
Copy link
Author

I think if we split AbpZeroDbContext without any dbsets to a super class (abstract) we can inherit from it without getting dependencies on roles/users/tenants etc

@hikalkan is that feasible ? Would that inject correct repositories ?

I will check abpzero source code in order to see if I can find a solution for this particular case.

Anyway this is just the initial step for Abp.Soa.Microservice :)

@hikalkan
Copy link
Member

Hi @brunobertechini

I read the whole discussion, I'm writing my response as I understand:

  • Sample blog module (https://github.com/aspnetboilerplate/sample-blog-module) has no dependency to the main application. It has only dependency to Abp.Zero package since I assumed that it will use users and other entities coming from AbpZeroDbContext.
  • In a true implemented microservice, it should have it's own database, it's own DbContext (directly derived from AbpDbContext). Even you need to User entity, you should define a User entity in your own service (probably with much less property). This can also be done even in same database. You can have multiple contextes where they use a subset of tables/fields. In same database approach, you will have 'migration' problem since EF does not have a good mechanism to manage it.
  • AbpZeroDbContext just declares dbsets. if you remove these dbsets, no reason to inherit from it.

@brunobertechini
Copy link
Author

brunobertechini commented Oct 19, 2016

@hikalkan Thanks for sharing.

  1. Sample blog module depends on main app at runtime. You can see that as it is inheriting from AbpZeroDbContext. If you go for a Add-Migration with an empty database you will see that those DbSets inheriteds will be created. Thus, without the main app database setup and working it will not function propertly.
  2. For each microservice of my own I am creating a separated database. I agree in terms of having my own User/Role etc class if I need to.
  3. If i dont inherit from AbpZeroDbContext should my repositories (IRepository) be injected ?

@hikalkan I believe you got my goal so far,

The issue Im haivng it now is: By Inheriting from AbpDbContext why IRepository and IDbContextProvider are not injected ?

I had to remove dependency on AbpZeroCoreModule because it requires classes like Tenant/User/Role (which I dont have for this specific case for example).

I already tested to create all classes pretty much like the sample blog module. But at runtime Abp does not manage to insert the correct row at the microservice database.

@brunobertechini
Copy link
Author

I made a mistake...Repositories for my dbcontext (inherited from AbpDbContext) were not being injected because i've missed to add a dependency on my data module to AbpEntityFrameworkModule.

Now they are getting injected.

Im moving forward with this approach and lets see.

I will share my findings when I have this piece working

Lets keep this discussion open :)

@guilmori
Copy link

I'm also interested in any findings about having many independent/dynamic modules.
This is definitely the kind of architecture I'm aiming for future dev.

@brunobertechini
Copy link
Author

@hikalkan Im having this exception in this microservice trying to use AbpIntegratedTestBase

Effort.Exceptions.EffortException : Database has not been initialized

If using CodeFirst try to add the following line:
context.Database.CreateIfNotExists()

Can you please point me on the direction where the Effort Database is initialized ?

@brunobertechini
Copy link
Author

Ok as a workaround I have created the following code at AppTestBase constructor:

UsingDbContext(context =>
            {
                context.Database.CreateIfNotExists();
            });

@brunobertechini
Copy link
Author

brunobertechini commented Oct 19, 2016

@hikalkan

Im trying to use a DbContext inherited from AbpDbContext but in my tests I got an exception from TenantCache which depends on IRepository that does not exists in my dbcontext.

We should not add DbSet into microservices. This is coupling. I believe this happens because there are some REAL validation against an existing tenant. Which should be configurable at least.

If I add IDbSet then the excpetion comes from validation:

Abp.AbpException : There is no tenant with given id: 1

What about creating a configuration to disable tenant existence validation ?

@brunobertechini
Copy link
Author

I narrowed down to DbPerTenantConnectionStringResolver since it needs to retrieve the connection string from tenant. I couldnt find any other implementatior for SingleDb.

@hikalkan
Copy link
Member

@brunobertechini can you share stack trace so we may see dependencies.
Effort may not be proper for such multi-db tests (at lest, current configuration in ABP template is not for that).

Also, I suggest an idea: Let build such a module together as open source. You code your simplified requirements, I review code, comment and contribute.

@brunobertechini
Copy link
Author

@hikalkan Im not using multi-db. Im using only one db (my microservice db)

I am already preparing a sample code to share using github.

It will be available soon.

Just finishing some urgent work :)

@mhascak
Copy link

mhascak commented Nov 15, 2016

+1 i will be glad for sample code

@Manu-LT
Copy link

Manu-LT commented Jan 20, 2017

Hello Guys,

How this story finished? did you moved to another post? there's no more comments here,

Thanks and Regards

@hikalkan
Copy link
Member

hikalkan commented Feb 3, 2017

Hi all,

I created a sample project and will want to write an article on that. Project repository: https://github.com/aspnetboilerplate/modular-todo-app

What I did:

Current main application has a direct dependency to TodoModule.Web, but it could be easily added as plugin. I added direct dependency just to make it simpler. Actually it does not depend on any class in the module.

Check source code and ask questions if you have. As I said, I'll create an article when I have time for it.

@andmattia
Copy link

Is it possibile to do the same with angular implementation?

@eureky
Copy link

eureky commented May 11, 2018

@ismcagdas thanks. I'm aware of the table mapping. I've tried to create a User entity in the first project and created Users table in db, then a MyUser entity in the second project which is mapped to the same Users table in the same db, but add-migration tool generated xxxxxxx_migration.cs file still contains code for creating Users table. Is there a way to prevent add-migration from creating the existing tables ? or do I have to to modify the generated migration code by hand ?

@ismcagdas
Copy link
Member

I've tried to create a User entity in the first project and created Users table in db

Have you created the table by migration at the first place ?

@eureky
Copy link

eureky commented May 11, 2018

Yes, I have run update-database in project 1 first. and Users table is already exist in database. If try to run update-database in project 2, it will fail with error like "There is already an object named 'Users' in the database." @ismcagdas

@hitaspdotnet
Copy link

hitaspdotnet commented May 11, 2018

@ismcagdas When we have multiple contexts with depending on AbpDbContext at migration time Abp generating basic tables (AbpUser, Settings, AuditedLog,...). I need to remove all duplicate model builder from myNewMigration.cs
My module not have any users navigation else one entity with FullAudited interface. So ABP generated their related tables. I don't know why its happen when their uses same DB & same connectionString.

@hitaspdotnet
Copy link

hitaspdotnet commented May 11, 2018

@eureky You can remove duplicate tables from yourMigration.cs in your customModule to running your Db-Update without this error. Then waiting for fix issue.

@ismcagdas
Copy link
Member

@eureky I think I understand your problem now but this is related to EF rather than ABP.
If you have two different db context and want to use same table for different entities in those different dbContexts, you need to implement a custom solution because EF cannot now if there is a table or not for other dbContexts.

So, you can create a 3. DbContext just for managing migrations. Then you can create two interfaces for your DbContests. IDbContext1, IDbContext2 for example and define your DbSets in those interfaces.
Derive your 3. DbContext from both IDbContext1, IDbContext2 and use it for the migrations.

Probably there will be some problematic cases like when you want to use same entity in both dbContexts. In that case, you can override OnModelCreating and ignore them manually.

@eureky
Copy link

eureky commented May 11, 2018

@ismcagdas Yes, I understand it's EF. So can I infer that in the Todo example, the code for creating AbpUser table in todo module was removed manually ? (https://github.com/aspnetboilerplate/modular-todo-app/blob/master/src/todo-module/TodoModule.EntityFramework/Migrations/201702031754143_Initial_Todo_Migration.cs)

I will look into and try your solution later. I think it's OK for a module to manage migrations for it's own entities. But the major problem is the entity referenced from other module. Looks like it can only be handled in a manual way.

@ismcagdas
Copy link
Member

@eureky migrations are disabled for Todo Module's DbContext https://github.com/aspnetboilerplate/modular-todo-app/blob/master/src/todo-module/TodoModule.EntityFramework/Migrations/Configuration.cs and TodoUser doesn't add a new field to existing User entity (AbpUsers table).

@brunobertechini
Copy link
Author

@eureky , @ismcagdas , @hitaspdotnet

Just read your doubts/comments and here are my two cents:

First of all, I believe we are talking about AbpZeroDbContext and not AbpDbContext correct?

This Abp problem of AbpZeroDbContext is one (very important) issue that drived me to create this project to accomplish complete separation of concerns.

If you inherit from AbpZeroDbContext you are saying your module MUST HAVE all tables implemented by it (users, roles, etc -- provided by module zero). This should be a concern only for your "platform" or main app module. Not for every custom module.

Even if you REMOVE the instructions from migration file, the RUNTIME dependency still remains (at runtime it will search and look for tables).
This is a big controversial issue.

My recommendation for now, is dont inherit from AbpZeroDbContext, instead, use AbpDbContext. for a clean environment and add your dbsets.

Please let me know if you have any doubts.

Bruno

@brunobertechini
Copy link
Author

brunobertechini commented May 11, 2018

Updated first post with more TODO items.

@hitaspdotnet
Copy link

When we have multiple contexts with depending on AbpDbContext at migration time Abp generating basic tables (AbpUser, Settings, AuditedLog,...).

I referred directly to AbpDbContext. Where did you see AbpZeroDbContext in my comment?

In fact, you cannot use AbpZeroDbContext without referring to the user, roles and tenant entity.

@brunobertechini
Copy link
Author

@hitaspdotnet AbpDbContext does not contain USers and Roles. So there is no way migrations creating instructions for these tables:

@ismcagdas When we have multiple contexts with depending on AbpDbContext at migration time Abp generating basic tables (AbpUser, Settings, AuditedLog,...). I need to remove all duplicate model builder from myNewMigration.cs
My module not have any users navigation else one entity with FullAudited interface. So ABP generated their related tables. I don't know why its happen when their uses some DB & same connectionString.

AbpDbContext does not generate that.

Bruno

@brunobertechini
Copy link
Author

@hitaspdotnet can you share some code?

@hitaspdotnet
Copy link

hitaspdotnet commented May 11, 2018

 public partial class Initial_Migration : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "AbpLanguages",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    DeleterUserId = table.Column<long>(nullable: true),
                    DeletionTime = table.Column<DateTime>(nullable: true),
                    DisplayName = table.Column<string>(maxLength: 64, nullable: false),
                    Icon = table.Column<string>(maxLength: 128, nullable: true),
                    IsDeleted = table.Column<bool>(nullable: false),
                    IsDisabled = table.Column<bool>(nullable: false),
                    LastModificationTime = table.Column<DateTime>(nullable: true),
                    LastModifierUserId = table.Column<long>(nullable: true),
                    Name = table.Column<string>(maxLength: 10, nullable: false),
                    TenantId = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpLanguages", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "AbpUsers",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    AccessFailedCount = table.Column<int>(nullable: false),
                    AuthenticationSource = table.Column<string>(maxLength: 64, nullable: true),
                    ConcurrencyStamp = table.Column<string>(maxLength: 128, nullable: true),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    DefaultBillingAddressId = table.Column<long>(nullable: true),
                    DefaultShippingAddressId = table.Column<long>(nullable: true),
                    DeleterUserId = table.Column<long>(nullable: true),
                    DeletionTime = table.Column<DateTime>(nullable: true),
                    EmailAddress = table.Column<string>(maxLength: 256, nullable: false),
                    EmailConfirmationCode = table.Column<string>(maxLength: 328, nullable: true),
                    GoogleAuthenticatorKey = table.Column<string>(nullable: true),
                    HomeLocationId = table.Column<long>(nullable: true),
                    InstagramLink = table.Column<string>(nullable: true),
                    IsActive = table.Column<bool>(nullable: false),
                    IsDeleted = table.Column<bool>(nullable: false),
                    IsEmailConfirmed = table.Column<bool>(nullable: false),
                    IsLockoutEnabled = table.Column<bool>(nullable: false),
                    IsPhoneNumberConfirmed = table.Column<bool>(nullable: false),
                    IsTwoFactorEnabled = table.Column<bool>(nullable: false),
                    LastLoginTime = table.Column<DateTime>(nullable: true),
                    LastModificationTime = table.Column<DateTime>(nullable: true),
                    LastModifierUserId = table.Column<long>(nullable: true),
                    LockoutEndDateUtc = table.Column<DateTime>(nullable: true),
                    Name = table.Column<string>(maxLength: 32, nullable: false),
                    NormalizedEmailAddress = table.Column<string>(maxLength: 256, nullable: false),
                    NormalizedUserName = table.Column<string>(maxLength: 32, nullable: false),
                    Password = table.Column<string>(maxLength: 128, nullable: false),
                    PasswordResetCode = table.Column<string>(maxLength: 328, nullable: true),
                    PhoneNumber = table.Column<string>(maxLength: 32, nullable: true),
                    ProfilePictureId = table.Column<Guid>(nullable: true),
                    SecurityStamp = table.Column<string>(maxLength: 128, nullable: true),
                    ShareSocials = table.Column<bool>(nullable: false),
                    ShouldChangePasswordOnNextLogin = table.Column<bool>(nullable: false),
                    SignInToken = table.Column<string>(nullable: true),
                    SignInTokenExpireTimeUtc = table.Column<DateTime>(nullable: true),
                    Surname = table.Column<string>(maxLength: 32, nullable: false),
                    TelegramLink = table.Column<string>(nullable: true),
                    TenantId = table.Column<int>(nullable: true),
                    TwitterLink = table.Column<string>(nullable: true),
                    UserGuid = table.Column<Guid>(nullable: false),
                    UserName = table.Column<string>(maxLength: 32, nullable: false),
                    VendorId = table.Column<long>(nullable: true),
                    WorkLocationId = table.Column<long>(nullable: true),
                    YouTubeLink = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpUsers", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "BlogCategories",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    DeleterUserId = table.Column<long>(nullable: true),
                    DeletionTime = table.Column<DateTime>(nullable: true),
                    Description = table.Column<string>(maxLength: 2000, nullable: true),
                    IsDeleted = table.Column<bool>(nullable: false),
                    LastModificationTime = table.Column<DateTime>(nullable: true),
                    LastModifierUserId = table.Column<long>(nullable: true),
                    Name = table.Column<string>(maxLength: 128, nullable: false),
                    ShortDescription = table.Column<string>(maxLength: 400, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_BlogCategories", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "AbpPermissions",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    IsGranted = table.Column<bool>(nullable: false),
                    Name = table.Column<string>(maxLength: 128, nullable: false),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpPermissions", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AbpPermissions_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "AbpSettings",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    LastModificationTime = table.Column<DateTime>(nullable: true),
                    LastModifierUserId = table.Column<long>(nullable: true),
                    Name = table.Column<string>(maxLength: 256, nullable: false),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: true),
                    Value = table.Column<string>(maxLength: 2000, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpSettings", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AbpSettings_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateTable(
                name: "AbpUserClaims",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    ClaimType = table.Column<string>(maxLength: 256, nullable: true),
                    ClaimValue = table.Column<string>(nullable: true),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpUserClaims", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AbpUserClaims_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "AbpUserLogins",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
                    ProviderKey = table.Column<string>(maxLength: 256, nullable: false),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpUserLogins", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AbpUserLogins_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "AbpUserRoles",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    RoleId = table.Column<int>(nullable: false),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpUserRoles", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AbpUserRoles_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "AbpUserTokens",
                columns: table => new
                {
                    Id = table.Column<long>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    LoginProvider = table.Column<string>(maxLength: 64, nullable: true),
                    Name = table.Column<string>(maxLength: 128, nullable: true),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: false),
                    Value = table.Column<string>(maxLength: 512, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AbpUserTokens", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AbpUserTokens_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "BlogPosts",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    AllowComments = table.Column<bool>(nullable: false),
                    ApplicationLanguageId = table.Column<int>(nullable: false),
                    BlogCategoryId = table.Column<int>(nullable: false),
                    Body = table.Column<string>(nullable: false),
                    BodyOverview = table.Column<string>(nullable: true),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    EndDateUtc = table.Column<DateTime>(nullable: true),
                    MetaDescription = table.Column<string>(nullable: true),
                    MetaKeywords = table.Column<string>(maxLength: 400, nullable: true),
                    MetaTitle = table.Column<string>(maxLength: 400, nullable: true),
                    StartDateUtc = table.Column<DateTime>(nullable: true),
                    Status = table.Column<int>(nullable: false),
                    Tags = table.Column<string>(nullable: true),
                    TenantId = table.Column<int>(nullable: true),
                    Title = table.Column<string>(maxLength: 400, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_BlogPosts", x => x.Id);
                    table.ForeignKey(
                        name: "FK_BlogPosts_AbpLanguages_ApplicationLanguageId",
                        column: x => x.ApplicationLanguageId,
                        principalTable: "AbpLanguages",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_BlogPosts_BlogCategories_BlogCategoryId",
                        column: x => x.BlogCategoryId,
                        principalTable: "BlogCategories",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "BlogComment",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    BlogPostId = table.Column<int>(nullable: false),
                    CommentText = table.Column<string>(nullable: false),
                    CreationTime = table.Column<DateTime>(nullable: false),
                    CreatorUserId = table.Column<long>(nullable: true),
                    IsApproved = table.Column<bool>(nullable: false),
                    TenantId = table.Column<int>(nullable: true),
                    UserId = table.Column<long>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_BlogComment", x => x.Id);
                    table.ForeignKey(
                        name: "FK_BlogComment_BlogPosts_BlogPostId",
                        column: x => x.BlogPostId,
                        principalTable: "BlogPosts",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_BlogComment_AbpUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AbpUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_AbpPermissions_UserId",
                table: "AbpPermissions",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_AbpSettings_UserId",
                table: "AbpSettings",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_AbpUserClaims_UserId",
                table: "AbpUserClaims",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_AbpUserLogins_UserId",
                table: "AbpUserLogins",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_AbpUserRoles_UserId",
                table: "AbpUserRoles",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_AbpUserTokens_UserId",
                table: "AbpUserTokens",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_BlogComment_BlogPostId",
                table: "BlogComment",
                column: "BlogPostId");

            migrationBuilder.CreateIndex(
                name: "IX_BlogComment_UserId",
                table: "BlogComment",
                column: "UserId");

            migrationBuilder.CreateIndex(
                name: "IX_BlogPosts_ApplicationLanguageId",
                table: "BlogPosts",
                column: "ApplicationLanguageId");

            migrationBuilder.CreateIndex(
                name: "IX_BlogPosts_BlogCategoryId",
                table: "BlogPosts",
                column: "BlogCategoryId");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "AbpPermissions");

            migrationBuilder.DropTable(
                name: "AbpSettings");

            migrationBuilder.DropTable(
                name: "AbpUserClaims");

            migrationBuilder.DropTable(
                name: "AbpUserLogins");

            migrationBuilder.DropTable(
                name: "AbpUserRoles");

            migrationBuilder.DropTable(
                name: "AbpUserTokens");

            migrationBuilder.DropTable(
                name: "BlogComment");

            migrationBuilder.DropTable(
                name: "BlogPosts");

            migrationBuilder.DropTable(
                name: "AbpUsers");

            migrationBuilder.DropTable(
                name: "AbpLanguages");

            migrationBuilder.DropTable(
                name: "BlogCategories");
        }
    }

And DbContext

  [AutoRepositoryTypes(
        typeof(IRepository<>),
        typeof(IRepository<,>),
        typeof(BlogServiceRepositoryBase<>),
        typeof(BlogServiceRepositoryBase<,>)
    )]
    public class BlogServiceDbContext : AbpDbContext
    {

        public virtual DbSet<BlogCategory> BlogCategories { get; set; }

        public virtual DbSet<BlogComment> BlogComments { get; set; }

        public virtual DbSet<BlogPost> BlogPosts { get; set; }

        public BlogServiceDbContext(DbContextOptions<BlogServiceDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            ConfigureEntities(modelBuilder);
        }

        private void ConfigureEntities(ModelBuilder modelBuilder)
        {
        }
    }

BlogPost and BlogComment entities depended on FullAuditedEntity

@hitaspdotnet
Copy link

Even if you REMOVE the instructions from migration file, the RUNTIME dependency still remains (at runtime it will search and look for tables).
This is a big controversial issue.

I have ready to run multi-language blog service with all available Abp wonderful feature.
But I have big problem with adding BlogService localize source to source manager for editing values from UI. https://github.com/aspnetzero/aspnet-zero-core/issues/1074#issuecomment-385975138

@brunobertechini
Copy link
Author

brunobertechini commented May 15, 2018

@hitaspdotnet Regarding your BlogServiceDbContext , is this the only dbcontext in your whole solution ?

Is this a separated service? This code should not generate more than 3 tables specified as your DbSets.

Please check if this dbcontext is not being added twice alltogether with default main app dbcontext.

Are you able to share a repository so I can help you out ?

Perhaps this is something related to configuration. I remember I had this issue in the past, but I need to recall from my git history how I did it.

If you look at the source code of 3.5.0 AbpDbContext.cs, you can see that there is no DbSet configured, thus, no tables should be generated.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Abp.Collections.Extensions;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using Abp.Domain.Uow;
using Abp.Events.Bus;
using Abp.Events.Bus.Entities;
using Abp.Extensions;
using Abp.Reflection;
using Abp.Runtime.Session;
using Abp.Timing;
using Castle.Core.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Abp.EntityFrameworkCore
{
    /// <summary>
    /// Base class for all DbContext classes in the application.
    /// </summary>
    public abstract class AbpDbContext : DbContext, ITransientDependency
    {
        /// <summary>
        /// Used to get current session values.
        /// </summary>
        public IAbpSession AbpSession { get; set; }

        /// <summary>
        /// Used to trigger entity change events.
        /// </summary>
        public IEntityChangeEventHelper EntityChangeEventHelper { get; set; }

        /// <summary>
        /// Reference to the logger.
        /// </summary>
        public ILogger Logger { get; set; }

        /// <summary>
        /// Reference to the event bus.
        /// </summary>
        public IEventBus EventBus { get; set; }

        /// <summary>
        /// Reference to GUID generator.
        /// </summary>
        public IGuidGenerator GuidGenerator { get; set; }

        /// <summary>
        /// Reference to the current UOW provider.
        /// </summary>
        public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; }

        /// <summary>
        /// Reference to multi tenancy configuration.
        /// </summary>
        public IMultiTenancyConfig MultiTenancyConfig { get; set; }

        /// <summary>
        /// Can be used to suppress automatically setting TenantId on SaveChanges.
        /// Default: false.
        /// </summary>
        public virtual bool SuppressAutoSetTenantId { get; set; }

        protected virtual int? CurrentTenantId => GetCurrentTenantIdOrNull();

        protected virtual bool IsSoftDeleteFilterEnabled => CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(AbpDataFilters.SoftDelete) == true;

        protected virtual bool IsMayHaveTenantFilterEnabled => CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(AbpDataFilters.MayHaveTenant) == true;

        protected virtual bool IsMustHaveTenantFilterEnabled => CurrentTenantId != null && CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(AbpDataFilters.MustHaveTenant) == true;

        private static MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(AbpDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic);

        /// <summary>
        /// Constructor.
        /// </summary>
        protected AbpDbContext(DbContextOptions options)
            : base(options)
        {
            InitializeDbContext();
        }

        private void InitializeDbContext()
        {
            SetNullsForInjectedProperties();
        }

        private void SetNullsForInjectedProperties()
        {
            Logger = NullLogger.Instance;
            AbpSession = NullAbpSession.Instance;
            EntityChangeEventHelper = NullEntityChangeEventHelper.Instance;
            GuidGenerator = SequentialGuidGenerator.Instance;
            EventBus = NullEventBus.Instance;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                ConfigureGlobalFiltersMethodInfo
                    .MakeGenericMethod(entityType.ClrType)
                    .Invoke(this, new object[] { modelBuilder, entityType });
            }
        }

        protected void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType entityType)
            where TEntity : class
        {
            if (entityType.BaseType == null && ShouldFilterEntity<TEntity>(entityType))
            {
                var filterExpression = CreateFilterExpression<TEntity>();
                if (filterExpression != null)
                {
                    modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
                }
            }
        }

        protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) where TEntity : class
        {
            if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
            {
                return true;
            }

            if (typeof(IMayHaveTenant).IsAssignableFrom(typeof(TEntity)))
            {
                return true;
            }

            if (typeof(IMustHaveTenant).IsAssignableFrom(typeof(TEntity)))
            {
                return true;
            }

            return false;
        }

        protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
            where TEntity : class
        {
            Expression<Func<TEntity, bool>> expression = null;

            if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
            {
                /* This condition should normally be defined as below:
                 * !IsSoftDeleteFilterEnabled || !((ISoftDelete) e).IsDeleted
                 * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502)
                 * So, we made a workaround to make it working. It works same as above.
                 */

                Expression<Func<TEntity, bool>> softDeleteFilter = e => !((ISoftDelete)e).IsDeleted || ((ISoftDelete)e).IsDeleted != IsSoftDeleteFilterEnabled;
                expression = expression == null ? softDeleteFilter : CombineExpressions(expression, softDeleteFilter);
            }

            if (typeof(IMayHaveTenant).IsAssignableFrom(typeof(TEntity)))
            {
                /* This condition should normally be defined as below:
                 * !IsMayHaveTenantFilterEnabled || ((IMayHaveTenant)e).TenantId == CurrentTenantId
                 * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502)
                 * So, we made a workaround to make it working. It works same as above.
                 */
                Expression<Func<TEntity, bool>> mayHaveTenantFilter = e => ((IMayHaveTenant)e).TenantId == CurrentTenantId || (((IMayHaveTenant)e).TenantId == CurrentTenantId) == IsMayHaveTenantFilterEnabled;
                expression = expression == null ? mayHaveTenantFilter : CombineExpressions(expression, mayHaveTenantFilter);
            }

            if (typeof(IMustHaveTenant).IsAssignableFrom(typeof(TEntity)))
            {
                /* This condition should normally be defined as below:
                 * !IsMustHaveTenantFilterEnabled || ((IMustHaveTenant)e).TenantId == CurrentTenantId
                 * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502)
                 * So, we made a workaround to make it working. It works same as above.
                 */
                Expression<Func<TEntity, bool>> mustHaveTenantFilter = e => ((IMustHaveTenant)e).TenantId == CurrentTenantId || (((IMustHaveTenant)e).TenantId == CurrentTenantId) == IsMustHaveTenantFilterEnabled;
                expression = expression == null ? mustHaveTenantFilter : CombineExpressions(expression, mustHaveTenantFilter);
            }

            return expression;
        }

        public override int SaveChanges()
        {
            try
            {
                var changeReport = ApplyAbpConcepts();
                var result = base.SaveChanges();
                EntityChangeEventHelper.TriggerEvents(changeReport);
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                throw new AbpDbConcurrencyException(ex.Message, ex);
            }
        }

        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            try
            {
                var changeReport = ApplyAbpConcepts();
                var result = await base.SaveChangesAsync(cancellationToken);
                await EntityChangeEventHelper.TriggerEventsAsync(changeReport);
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                throw new AbpDbConcurrencyException(ex.Message, ex);
            }
        }

        protected virtual EntityChangeReport ApplyAbpConcepts()
        {
            var changeReport = new EntityChangeReport();

            var userId = GetAuditUserId();

            foreach (var entry in ChangeTracker.Entries().ToList())
            {
                ApplyAbpConcepts(entry, userId, changeReport);
            }

            return changeReport;
        }

        protected virtual void ApplyAbpConcepts(EntityEntry entry, long? userId, EntityChangeReport changeReport)
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    ApplyAbpConceptsForAddedEntity(entry, userId, changeReport);
                    break;
                case EntityState.Modified:
                    ApplyAbpConceptsForModifiedEntity(entry, userId, changeReport);
                    break;
                case EntityState.Deleted:
                    ApplyAbpConceptsForDeletedEntity(entry, userId, changeReport);
                    break;
            }

            AddDomainEvents(changeReport.DomainEvents, entry.Entity);
        }

        protected virtual void ApplyAbpConceptsForAddedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
        {
            CheckAndSetId(entry);
            CheckAndSetMustHaveTenantIdProperty(entry.Entity);
            CheckAndSetMayHaveTenantIdProperty(entry.Entity);
            SetCreationAuditProperties(entry.Entity, userId);
            changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Created));
        }

        protected virtual void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
        {
            SetModificationAuditProperties(entry.Entity, userId);
            if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
            {
                SetDeletionAuditProperties(entry.Entity, userId);
                changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
            }
            else
            {
                changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated));
            }
        }

        protected virtual void ApplyAbpConceptsForDeletedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
        {
            CancelDeletionForSoftDelete(entry);
            SetDeletionAuditProperties(entry.Entity, userId);
            changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
        }

        protected virtual void AddDomainEvents(List<DomainEventEntry> domainEvents, object entityAsObj)
        {
            var generatesDomainEventsEntity = entityAsObj as IGeneratesDomainEvents;
            if (generatesDomainEventsEntity == null)
            {
                return;
            }

            if (generatesDomainEventsEntity.DomainEvents.IsNullOrEmpty())
            {
                return;
            }

            domainEvents.AddRange(generatesDomainEventsEntity.DomainEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData)));
            generatesDomainEventsEntity.DomainEvents.Clear();
        }

        protected virtual void CheckAndSetId(EntityEntry entry)
        {
            //Set GUID Ids
            var entity = entry.Entity as IEntity<Guid>;
            if (entity != null && entity.Id == Guid.Empty)
            {
                var dbGeneratedAttr = ReflectionHelper
                    .GetSingleAttributeOrDefault<DatabaseGeneratedAttribute>(
                    entry.Property("Id").Metadata.PropertyInfo
                    );

                if (dbGeneratedAttr == null || dbGeneratedAttr.DatabaseGeneratedOption == DatabaseGeneratedOption.None)
                {
                    entity.Id = GuidGenerator.Create();
                }
            }
        }

        protected virtual void CheckAndSetMustHaveTenantIdProperty(object entityAsObj)
        {
            if (SuppressAutoSetTenantId)
            {
                return;
            }

            //Only set IMustHaveTenant entities
            if (!(entityAsObj is IMustHaveTenant))
            {
                return;
            }

            var entity = entityAsObj.As<IMustHaveTenant>();

            //Don't set if it's already set
            if (entity.TenantId != 0)
            {
                return;
            }

            var currentTenantId = GetCurrentTenantIdOrNull();

            if (currentTenantId != null)
            {
                entity.TenantId = currentTenantId.Value;
            }
            else
            {
                throw new AbpException("Can not set TenantId to 0 for IMustHaveTenant entities!");
            }
        }

        protected virtual void CheckAndSetMayHaveTenantIdProperty(object entityAsObj)
        {
            if (SuppressAutoSetTenantId)
            {
                return;
            }

            //Only works for single tenant applications
            if (MultiTenancyConfig?.IsEnabled ?? false)
            {
                return;
            }

            //Only set IMayHaveTenant entities
            if (!(entityAsObj is IMayHaveTenant))
            {
                return;
            }

            var entity = entityAsObj.As<IMayHaveTenant>();

            //Don't set if it's already set
            if (entity.TenantId != null)
            {
                return;
            }

            entity.TenantId = GetCurrentTenantIdOrNull();
        }

        protected virtual void SetCreationAuditProperties(object entityAsObj, long? userId)
        {
            EntityAuditingHelper.SetCreationAuditProperties(MultiTenancyConfig, entityAsObj, AbpSession.TenantId, userId);
        }

        protected virtual void SetModificationAuditProperties(object entityAsObj, long? userId)
        {
            EntityAuditingHelper.SetModificationAuditProperties(MultiTenancyConfig, entityAsObj, AbpSession.TenantId, userId);
        }

        protected virtual void CancelDeletionForSoftDelete(EntityEntry entry)
        {
            if (!(entry.Entity is ISoftDelete))
            {
                return;
            }

            entry.Reload();
            entry.State = EntityState.Modified;
            entry.Entity.As<ISoftDelete>().IsDeleted = true;
        }

        protected virtual void SetDeletionAuditProperties(object entityAsObj, long? userId)
        {
            if (entityAsObj is IHasDeletionTime)
            {
                var entity = entityAsObj.As<IHasDeletionTime>();

                if (entity.DeletionTime == null)
                {
                    entity.DeletionTime = Clock.Now;
                }
            }

            if (entityAsObj is IDeletionAudited)
            {
                var entity = entityAsObj.As<IDeletionAudited>();

                if (entity.DeleterUserId != null)
                {
                    return;
                }

                if (userId == null)
                {
                    entity.DeleterUserId = null;
                    return;
                }

                //Special check for multi-tenant entities
                if (entity is IMayHaveTenant || entity is IMustHaveTenant)
                {
                    //Sets LastModifierUserId only if current user is in same tenant/host with the given entity
                    if ((entity is IMayHaveTenant && entity.As<IMayHaveTenant>().TenantId == AbpSession.TenantId) ||
                        (entity is IMustHaveTenant && entity.As<IMustHaveTenant>().TenantId == AbpSession.TenantId))
                    {
                        entity.DeleterUserId = userId;
                    }
                    else
                    {
                        entity.DeleterUserId = null;
                    }
                }
                else
                {
                    entity.DeleterUserId = userId;
                }
            }
        }

        protected virtual long? GetAuditUserId()
        {
            if (AbpSession.UserId.HasValue &&
                CurrentUnitOfWorkProvider != null &&
                CurrentUnitOfWorkProvider.Current != null &&
                CurrentUnitOfWorkProvider.Current.GetTenantId() == AbpSession.TenantId)
            {
                return AbpSession.UserId;
            }

            return null;
        }

        protected virtual int? GetCurrentTenantIdOrNull()
        {
            if (CurrentUnitOfWorkProvider != null &&
                CurrentUnitOfWorkProvider.Current != null)
            {
                return CurrentUnitOfWorkProvider.Current.GetTenantId();
            }

            return AbpSession.TenantId;
        }

        protected virtual Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
        {
            var parameter = Expression.Parameter(typeof(T));

            var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
            var left = leftVisitor.Visit(expression1.Body);

            var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
            var right = rightVisitor.Visit(expression2.Body);

            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), parameter);
        }

        class ReplaceExpressionVisitor : ExpressionVisitor
        {
            private readonly Expression _oldValue;
            private readonly Expression _newValue;

            public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
            {
                _oldValue = oldValue;
                _newValue = newValue;
            }

            public override Expression Visit(Expression node)
            {
                if (node == _oldValue)
                {
                    return _newValue;
                }

                return base.Visit(node);
            }
        }
    }
}

@hitaspdotnet
Copy link

@brunobertechini I cant share repo because it's ASPNET Zero based solution.

Is this a separated service?

Yes, Just a reference to [Web.MVC] for depending nothing else.
I just tested this with empty ABP template with sample class inherited [FullAuditedEntity].

  1. Download the template from AspNet Boilerplate download page + Module Zero
  2. Update connectionString and run Update-Database using PMC
  3. Change AbpZeroDbContext to AbpDbContext
  4. Make simple class with inheriting from [FullAuditedEntity]
  5. Add DbSet to your DbContext
  6. Run Add-Migration "MigrationName"
  7. Check the generated code
    So migration generated related tables (duplicate tables) for AbpFullAuditedEntity

In the end, thank you for sharing ALL LINES of AbpDbContext!!!

@hitaspdotnet
Copy link

hitaspdotnet commented May 27, 2018

I reviewed your project completely. I researched about micro-services for two months. I tell you directly that Angular based micro-service is very complicated, to the extent that the Microsoft team that produced the eShopOnContiners sample code has dropped out of its training and provided a brief explanation for the UI based on Angular uses MVC for Identity calls. So in ABP you haven't just Username & Password checker (as eShopOnContiner), you have tenants, roles, features, localization, edition, ...
I believe if you haven't developers team greater of Microsoft eShopOnContainers Team migrate your repository from Angular to MVC you can got that very faster.
Next, you can use SPA for your public Website and fetching your feed data from micro-services DB to your UI where users are just visitors.

Regards
GL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests