Base solution for your next web application

Activities of "JeffMH"

Answer

Thanks for the prompt reply!

I see where we can publish a notification by Entity (using EntityIdentifier), but I don't see a built in way to query by that. Like the list of Notifications built into the app, I need to create a list to see the all notifications for a particular Entity.

Is there a way to do this that I am just missing? It's probably wishful thinking but I thought I would ask.

Yes, I already started to do something like that. I think I will add a feature request on GitHub. It would be nice if this was part of the IUserNotificationManager. It's just cleaner if I don't have to implement my own NotificationManager since that is part of the ABP framework and not part of the template.

We could either create an overload for GetUserNotificationsAsync on IUserNotificatioManager or create an alternate method to pass the additional entity information. Maybe GetUserEntityNotificationsAsync.

Once I am done with what I do, I will post the request.

Thanks!

I followed that viewtopic but it's not working for me. I added this code to the User class:

[MaxLength(AbpUserBase.MaxEmailAddressLength)]
public override string UserName { get; set; }

EF recognized the change and added the migration, but when I try and save the entity, it fails validation. Any ideas?

I laughed out loud when i saw that. That fixed it. Very much appreciated!

This is happening anytime you see a <br /> element in the code. It's happening to me with IE11 on windows 10. I think this is an issue with Open Sans font.

I download the latest version from GitHub just yesterday and ran it today in IE11 and I get the little square anytime you put a <br /> in your code. I took a screenshot on the one that is on the dashboard.

If you inspect that element, nothing stands out but if you go down and take Open Sans out of the font family, it no longer shows up wrong. So after that, I went out to the metronic theme and saw how they were including the google font

<link href="http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700&subset=all" rel="stylesheet" type="text/css" />

I replaced the link to the local open sans font and it is also working. Not sure how you download the font but there could be something wrong with the local Open Sans files.

Let me know if I can help.

Sorry, I saw some of these posts late. Multiple DB's per tenant is up and running for us. It's been live now for about 3 months. Now, we use separate Db's for each tenant, and I also have a separate DB Context for Tenant and Host. I do not want my host tables inside my tenant DB's, and vice versa. So, in order to get all this to work I did have to make a few changes (for azure as well).

  1. The built in create database does not work in Azure. I had to write some code when the CreateTenant is called to execute a SQL script to create the database. I have an App.Setting I set for IsAzure, and if that is set, I run the custom create database code. This way it works locally and in azure.

  2. For Azure, we would get some timeouts during database migration. Since our developers use basic instances of AzureSQL, that instance is pretty slow so as our migrations grew in size, so did the time taken to execute them. Also, creating a database in Azure is a long process, it's not like creating a database in Sql Server. Resources have to be allocated, etc. So I had to add some extended timeouts in a few places.

  3. Migrations didn't always work. I had to modify some of the code in the Migrator app just a smidgen. Now, we fought SetTenantId bug for a while so these changes may not be necessary but I had to make a few changes. Sorry, I can't be more descriptive. I will try and get the changes that we made together for you. This may just work now.

  4. Using multiple context classes, I had to change AbpZeroDbMigrator (in the EntityFramework project) to accept multiple context classes. I basically created a AbpZeroMultipleContextDbMigrator class and instead of inheriting their AbpZeroDbMigrator, I inherit from my own. I had to copy some of their code around a little. Maybe this is something we can get them to integrate into the framework.

Other than that, the multiple database integration is working fine. I get nervous about it a little because the AspNetZero guys recommend using a single schema / context. As with the AbpZeroDbMigrator, they don't run tests on the code with multiple context in mind. So, if you want to use a single schema / context, then you may not have to do all that I have done. I just hate the idea of having host tables in my tenant DB's.

My only other complaint / wish list item is having to put a connection string in. I can't have end users creating tenants if they have to have a connection string. I wish instead it was just Server / Database entry (or just auto created based on TenancyName). We could store a template connection string in App.config and just do a string replace on the server / database inside the Context classes. I have to make these changes in our code anyway because I have to allow users to create tenants. Also, when a tenant signs up, I have to create some code that tries to auto create a tenant database with some given name. This is probably hard to make where everyone is happy.

I will try and get the modifications I did in another follow up post. I am on a deadline so it may be a couple days. Just know, the system so far is working well for us.

Distributed transactions are now supported in AzureSQL.

<a class="postlink" href="https://azure.microsoft.com/en-us/blog/elastic-database-transactions-with-azure-sql-database/">https://azure.microsoft.com/en-us/blog/ ... -database/</a>

That's the first article I found googling but as long as you are on framework 4.6.1, you should be good to go. You may also need to be running on the latest version of AzureSQL as well, in case your instances are not setup to auto migrate to the latest.

But, you will need to modify the CreateTenant code to create the database manually. The built in create database that EF migrations use does not work on AzureSQL. I had to write code in the CreateTenant that executed a T-SQL Create Database command when running on azure.

Below is the code. FYI, I store two app settings inside the web.config file.

&lt;add key=&quot;MyPortal.IsAzureSql&quot; value=&quot;false&quot; /&gt;
&lt;add key=&quot;MyPortal.AzureSqlDbCreateScript&quot; value=&quot;CREATE DATABASE [{0}] ( EDITION = &#39;basic&#39; )&quot; /&gt;

You can customize the script to create for what you need. We have different scripts depending on where we deploy so app settings work well for us since we can use config overrides on build.

AzureDbCreator. This handles all the legwork on creating the database. Modify TenantManager to have this injected into the constructor.

public class AzureDbCreator
{
	public void CreateDatabase(string tenantConnection)
	{
		//Need to parse the connection string in order to create the DB.
		//Workflow:
		//  1.  Get Connection String
		//  2.  Parse the name of the database from the connection string.
		//  3.  Change to connect to the Master Database
		//  4.  Connect and create the database.

		//  1.  Get Connection String
		var builder = new SqlConnectionStringBuilder(tenantConnection);

		//  2. Parse the name
		var dbName = builder.InitialCatalog;

		if (string.IsNullOrEmpty(dbName))
		{
			throw new AzureDbCreateException("Unable to parse the connection string and find the name of the Database", tenantConnection);
		}

		// 3.  Change the connection to Master
		builder.InitialCatalog = "master";

		// 4.  Create Database
		try
		{
			CreateDatabase(builder.ConnectionString, dbName);
		}
		catch (Exception ex)
		{
			throw new AzureDbCreateException("Unable to create the database", tenantConnection, ex);
		}
	}

	/// <summary>
	/// Try to connect to the DB
	/// </summary>
	/// <param name="connectionString"></param>
	/// <returns></returns>
	public bool TryConnect(string connectionString)
	{
		bool retVal;

		try
		{
			using (var connection = new SqlConnection(connectionString))
			{
				connection.Open();

				retVal = true;

				connection.Close();
			}
		}
		catch (Exception)
		{
			retVal = false;
		}

		return retVal;
	}

	private void CreateDatabase(string connectionString, string dbName)
	{
		//First find the template Sql for creating the Database
		using (var connection = new SqlConnection(connectionString))
		{
			var createScript = ConfigurationManager.AppSettings[MyPortalAppSettings.AzureSqlDbCreateScript];

			//Create the command and set its properties.
			var command = new SqlCommand
			{
				Connection = connection,
				CommandText = string.Format(createScript, dbName),
				CommandType = CommandType.Text,
				CommandTimeout = 120 //Seconds
			};

			command.Parameters.AddWithValue("@p1", dbName);

			// Open the connection and execute the reader.
			connection.Open();

			var result = command.ExecuteNonQuery();
		}
	}
}

And inside the tenant manager I modified the CreateWithAdminUserAsync method. All the other code in the method is the same as what you download.

public async Task<int> CreateWithAdminUserAsync(string tenancyName, string name, string adminPassword, string adminEmailAddress, string connectionString, bool isActive, int? editionId, bool shouldChangePasswordOnNextLogin, bool sendActivationEmail)
        {
            int newTenantId;
            long newAdminId;

            //M3 - If this is azure, I need to first make sure the database is created before moving on.  If it isn't, let's create the DB first.  
            //M3 - This cannot be within a transaction with azure.
            //M3 - Need to check if we are running in Azure.  if so, we need to create the database first.
            var isAzureSql = Convert.ToBoolean(ConfigurationManager.AppSettings[MyPortalAppSettings.IsAzureSql]);

            if (isAzureSql)
            {
                if(!_azureDbCreator.TryConnect(connectionString))
                    _azureDbCreator.CreateDatabase(connectionString);
            }

            using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
            {

Hey, no worries, glad I can help a little. Overall, I love what they have done, it's not perfect, but no architecture / system is perfect. Multi-tenancy is not simple and it's great to get a leap forward. So if I can help make it better while helping others, we all win.

Here is my MultiContext migrator. The other post I did on the create tenant and this were the biggest changes. As I pasted this in, I think this is where I had to make another change from what they did.

<a class="postlink" href="https://github.com/aspnetboilerplate/module-zero/blob/dev/src/Abp.Zero.EntityFramework/Zero/EntityFramework/AbpZeroDbMigrator.cs">https://github.com/aspnetboilerplate/mo ... igrator.cs</a>

If you look at the CreateOrMigrate method of that class compared to mine, within the using where it suppresses the unit of work, I am doing the migration slightly different. I had troubles getting migrations working and this solved the problems for me. I don't remember the circumstances enough to tell you why. I was under the gun on a big deployment and had to get that fixed. I wish I would have documented that one better.

Here is my migrator. The biggest difference compared to the one above is it has the generic types for both the host and the tenant, the default class only excepts one context type. To use, inside the AspNetZeroDbMigrator class that comes in the downloaded solution, just change what class that inherits from. Pretty simple.

Hope this helps.

using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Transactions;
using Abp.Data;
using Abp.Dependency;
using Abp.Domain.Uow;
using Abp.Extensions;
using Abp.MultiTenancy;
using Abp.Zero.EntityFramework;

namespace MyPortal.Framework.EntityFramework.Migrator
{
    public abstract class AbpZeroMultiContextDbMigrator<THostDbContext, THostConfiguration, TTenantDbContext, TTenantConfiguration> : IAbpZeroDbMigrator, ITransientDependency
            where THostDbContext : DbContext
            where THostConfiguration : DbMigrationsConfiguration<THostDbContext>, IMultiTenantSeed, new()
            where TTenantDbContext : DbContext
            where TTenantConfiguration : DbMigrationsConfiguration<TTenantDbContext>, IMultiTenantSeed, new()
    {
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IDbPerTenantConnectionStringResolver _connectionStringResolver;
        private readonly IIocResolver _iocResolver;

        protected AbpZeroMultiContextDbMigrator(
            IUnitOfWorkManager unitOfWorkManager,
            IDbPerTenantConnectionStringResolver connectionStringResolver,
            IIocResolver iocResolver)
        {
            _unitOfWorkManager = unitOfWorkManager;
            _connectionStringResolver = connectionStringResolver;
            _iocResolver = iocResolver;
        }

        public virtual void CreateOrMigrateForHost()
        {
            CreateOrMigrate<THostDbContext, THostConfiguration>(null);
        }

        public virtual void CreateOrMigrateForTenant(AbpTenantBase tenant)
        {
            if (tenant.ConnectionString.IsNullOrEmpty())
            {
                return;
            }

            CreateOrMigrate<TTenantDbContext, TTenantConfiguration>(tenant);
        }

        protected virtual void CreateOrMigrate<TDbContext, TConfiguration>(AbpTenantBase tenant)
            where TDbContext : DbContext
            where TConfiguration : DbMigrationsConfiguration<TDbContext>, IMultiTenantSeed, new()
        {
            var args = new DbPerTenantConnectionStringResolveArgs(
                tenant == null ? (int?)null : (int?)tenant.Id,
                tenant == null ? MultiTenancySides.Host : MultiTenancySides.Tenant
                );
            args["DbContextType"] = typeof(TDbContext);

            var nameOrConnectionString = ConnectionStringHelper.GetConnectionString(_connectionStringResolver.GetNameOrConnectionString(args));

            using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.Suppress))
            {
                var dbInitializer = new MigrateDatabaseToLatestVersion<TDbContext, TConfiguration>(
                       true,
                       new TConfiguration
                       {
                           Tenant = tenant
                       });

                Database.SetInitializer<TDbContext>(dbInitializer);

                using (var dbContext = _iocResolver.ResolveAsDisposable<TDbContext>(new { nameOrConnectionString = nameOrConnectionString }))
                {
                    dbInitializer.InitializeDatabase(dbContext.Object);

                    _unitOfWorkManager.Current.SaveChanges();
                    uow.Complete();
                }
            }
        }
    }
}

I have to say I am curious on this one as well.

My first thought is you have some weird dependency graph. I think Castle will fail if you have a circular dependency but it will also give you some other potential configuration problems:

<a class="postlink" href="https://github.com/castleproject/Windsor/blob/master/docs/debugger-views.md">https://github.com/castleproject/Windso ... r-views.md</a>

I loaded up the IocManager in my debugger and there are some potential issues. You could check to see if anything you are dependent on is on of the debug views. If one is in there, you could just comment that one out and see if it gets better.

I am sure you may have already moved on. It may be worth the AspNetZero guys to look over the lists that are generated by Castle. There are 7 Potentially misconfigured, 38 potential lifestyle mismatches, and 20 potentially duplicate dependencies. Not that any of these may be wrong, they are just potential problems.

I may spend some time running a couple perf tests if I can, won't be anytime soon, but this has me really curious.

Showing 11 to 20 of 66 entries