Base solution for your next web application
Open Closed

Multi Tenant - Multiple Database - 2 DB Context #683


User avatar
0
maharatha created

I was going through the below post :

#488@8b17fa78-f26f-44a4-bd4f-1ea5b00d5f75

But couldn't find an answer. But my requirement is more or less similar.

I want to use all inbuild functionality of ASPNET ZERO but I would like to save all tenant related information other than implemented by AspnetZero to be stored in it's own DB.

To be more specific I want all transnational data (like vendor,customer,etc) to be stored in a separate database for each Tenant.

<a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/MultipleDbContextDemo">https://github.com/aspnetboilerplate/as ... ontextDemo</a> : This is a solution which enables me to seggregate the data.

The problem is I want to programatically change the DBContext connection string based on TenantID. I am unable to access ASPNetZero Sessions outside DBcontext contructor , so I am not sure how to use this to pass the correct connection string. Moreover I am also unable to access Session in my DbContext class. So the hwole question revolves around how to access the TenantID so that I can get the connection string and pass on to the database.

public class TesTrans : AbpDbContext { /* Setting "Default" to base class helps us when working migration commands on Package Manager Console. * But it may cause problems when working Migrate.exe of EF. ABP works either way. * */ public virtual IDbSet<COAUnit> COAUnit { get; set; }

    // public virtual IDbSet&lt;AddressUnit&gt; AddressUnit { get; set; }
    
   

<span style="color:#BF00BF">// nameOrConnectionString - How to set this connection string as I am unable to access ABPSession or any session </span> public TesTrans (string nameOrConnectionString) : base(nameOrConnectionString) {

    }

    /* This constructor is used by ABP to pass connection string defined in CORPACCOUNTINGDataModule.PreInitialize.
     * Notice that, actually you will not directly create an instance of TesTrans since ABP automatically handles it.
     */
    

}

7 Answer(s)
  • User Avatar
    0
    maharatha created

    Also I was trying to get the TenantID inside my DBContext constructor and it was coming as Null. But as far as I know you should be able to get AbpSession.TenantID values if you are using AspnetZero which I am currently using.

    Any help is really appreciated.

  • User Avatar
    0
    maharatha created

    I am now using Claims to store the connection string and using it this way :

    public TesTrans(string nameOrConnectionString): base(GetDynamicConnectionString()) { }

        public static string GetDynamicConnectionString()
        {
            var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
            if (claimsPrincipal == null)
            {
                return null;
            }
    
    
            var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;
    
            if (claimsIdentity == null)
            {
                return null;
            }
    
            var ConnStringClaim = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ConnString");
            if (ConnStringClaim == null || string.IsNullOrEmpty(ConnStringClaim.Value))
            {
                return null;
            }
    
            return ConnStringClaim.Value;
        }
    

    in my DBContext.

    Is this the right way of approach and is there a cleaner and better approach ?

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    Your approach is conceptually true, but implementation would be better. Instead of storing connection string to claims, you can store connection strings in a static dictionary<int, string>, where key is tenantid and value is conn string. Then you get get it from the dictionary.

    A side note: if you want to access session in the constructor, you can try constructor inject IAbpSession.

  • User Avatar
    0
    maharatha created

    Thank You Hikalkan. You are a savior 8-) . I will be storing the connection string in a DB table and will be getting the connection string from the table and storing in claims,. As the connection string will keep getting added so not sure if I can store that in a static class using dictionary and it would be one connection string per tenant or user. Correct me if I am wrong in my approach ? I am learning a lot of Aspnet Boiler and wants to keep learning.

    Secondly I could access the Abpsession using constructor injection but couldn't use it as I wanted the TenantID to be passed as a parameter to GetDynamicConnectionString() which i couldn't. Also I couldn't use the AbpSession.TenantID inside GetDynamicConnectionString() as it's called before the contructor

    Lastly, I am unable to find out one common place where I should add my claims because the connection string would change when the host will impersonate. I was trying in AccountController.cs in SaveImpersonationTokenAndGetTargetUrl method for adding and updating claims but when an user impersonates he impersonates as another user. So the claims for impersonated user is null. Basically where should I set claims for impersonated user.

    Thanks in advance.

  • User Avatar
    0
    maharatha created

    Finally I did the implementation without adding any new claims. Below is what I did :

    Created a Static class containing a static dictionary storing the tenant ID and the DB Connection .

    static class ConnectionStrings { static Dictionary<int, string> _dict = new Dictionary<int, string> { {1, @"Server =.\xxxxxx; Database = xxxxxxxx; Trusted_Connection = True;"}, {2, @"Server =.\xxxx; Database = xxxxxxxyyy; Trusted_Connection = True;"},

    };
    
        /// &lt;summary&gt;
        /// Access the Dictionary from external sources
        /// &lt;/summary&gt;
        public static string GetConnectionString(int intTenantID)
        {
            // Try to get the result in the static Dictionary
            string result;
            if (_dict.TryGetValue(intTenantID, out result))
            {
                return result;
            }
            else
            {
                return @"Server =.\xxxxx; Database = cxxxxxxxxx; Trusted_Connection = True;";
            }
        }
    }
    

    Then Created a Method called GetDynamicConnectionString()

    Was able to access the TenantID using :

    var ConnStringClaim = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "http://www.aspnetboilerplate.com/identity/claims/tenantId");

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    Congratulations. In addition, you can use the const on AbpClaims class (<a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/master/src/Abp/Runtime/Security/AbpClaims.cs">https://github.com/aspnetboilerplate/as ... pClaims.cs</a>) instead of "http://www.aspnetboilerplate.com/identity/claims/tenantId" string.

  • User Avatar
    0
    maharatha created

    Thank You for pointing to the right direction.