Can you mark the Location entity with a MultitenancySide.Host attribute: [MultiTenancySide(MultiTenancySides.Host)]? Not sure if you use both in one query if it still applies the filter to each table.
You might be pushing the limits of the Filtering technique they are using with EF.
Ok, I am getting back to work now :)
As I said in my other post, you will never avoid changing some code within the Framework code. You can try, but it's impossible not to. Just try and have a good merging strategy in place.
If you look for my post on the github link above (jmhinnen), I explain how I do the branching and merging. Works great for me!
We do this in ours. It would take me a bit of time to document everything but this is very doable. In every project, I have a Module and a CustomDtoMapper class at the root. I took this from the framework's Application project. I like this so I use that same thing when I create my own project.
In our Core project, we implemented our own Authorization provider (inherit from AuthorizationProvider just like theirs does) and in that projects Module, just call Configuration.Authorization.Providers.Add<MyAuthorizationProvider>();
Make your Modules dependent on their module. As an example, in your Core project's Module, add a DependsOn for the frameworks Core module. Make sense?
You will need to wire up the API parsing inside the WebAPI project. This is the big one that converts the AppServices to WebAPI methods. Search for DynamicApiControllerBuilder and you can see where they registered their Application Module. You will need to add a DependsOn your Application module here also.
Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
.ForAll<IApplicationService>(typeof(MyApplicationModule).Assembly, "app")
.Build();
Not sure you can get away with Multiple EF projects. Just keep that as one. Unless you had separate DbContext classes, I don't see how this would help a ton. I move all their Migrations into a sub folder in the Migrations folder to keep their migrations separate from mine. This helps to keep things separate on the EF side. Other than that, never had any issues with this approach.
We also have our own SettingsProvider. We register that in the Web Module. As you go, it's pretty easy to figure out what they did to copy. I will say that we can keep from changing the framework stuff about 90% of the time. Sometimes it's impossible to avoid changing the framework code.
Hope that helps a little. If you just start to create your own Application and Core project, you will figure it out pretty quick.
Just FYI, this no longer works because the AccountController login method no longer call SignInAsync in the account controller.
<a class="postlink" href="https://gist.github.com/hikalkan/67469e05475c2d18cb88">https://gist.github.com/hikalkan/67469e05475c2d18cb88</a>
I updated this with a question. Just making sure no one missed the question.
Thanks!
Rashed is completely correct! There are lots of good CMS systems out there. I use this framework because it is NOT a CMS! I think if you needed CMS functionality for say a blog or something like that, then this is a good place for an aspnetzero_contrib project where users could share different examples of implementations on top of AspNetZero. But keep in mind, there are already 3 different front ends of AspNetZero (Jquery, Angular, Angular2), and hopefully more coming :)
I would rather see many more implementations with different front ends, a React version, React / Redux version, Aurelia version. Give developers more choices on the front end instead of bulking up the back end. Keep the framework just a framework! I think doing this type of stuff and maintaining a CMS type system would be very difficult.
As always, this is my opinion! It's good to be able to openly discuss this stuff.
I don't think this is a problem with the framework.
<a class="postlink" href="http://stackoverflow.com/questions/17127351/introducing-foreign-key-constraint-may-cause-cycles-or-multiple-cascade-paths">http://stackoverflow.com/questions/1712 ... cade-paths</a>
We have seen this using the default cascade option in EF a ton. That article explains the error some.
Hope that helps.
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.
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();
}
}
}
}
}
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.
<add key="MyPortal.IsAzureSql" value="false" />
<add key="MyPortal.AzureSqlDbCreateScript" value="CREATE DATABASE [{0}] ( EDITION = 'basic' )" />
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))
{
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).
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.
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.
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.
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.