Introduction
I spent time migrating XAF existing security system to new much improved policy permission system and I would like to share my experience and code for anyone else who might decide for this endevour.
My application is still in development and uses security system quite heavily which is whay I decided to go to improved security system.
Since this scripts will turn your database upside down and back (hopefully) I strongly advise you not to run this in production, but if you really must - then I will state the obvious - make sure you have backups. Several of them. On different media in different buildings preferrably on different continents.
Also, all scripts are Microsoft SQL Server specific, so if you are using another database you will need to rework all metadata queries.
With that said, let us dig in.
Using the code
All code can be implemented using existing XAF update mechanisam:
UpdateDatabaseBeforeUpdateSchema
- which executes before XAF tries to push class changes into database and UpdateDatabaseAfterUpdateSchema
which executes after XAF is done pusing class changes and we get a chance to perform additional post-update tasks.
I had two different kind of columns that I had to deal with which will provide as a good example if you will have similar scenarios:
- In my inital version of application I created a common parent class which had
UserCreated
and UserModified uniqueidentifier
properties connected to SecuritySystemUser
. I decided to change those properties to nvarchar
and to keep usernames instead - User properties that I wanted to keep and migrate to
PermissionPolicyUser
(for instance - Employee.SystemUser
column)
Entire process can be divided into following steps:
- In
UpdateDatabaseBeforeUpdateSchema
change all uniqueidentifier
columns to nvarchar
and update new columns with username - Also, rename all existing columns (and their foreign keys and indices) which will allow XAF to create new columns properly connected to
PermissionPolicyUser
- Allow XAF to do it's own thing
- In
UpdateDatabaseAfterUpdateSchema
- Copy users, roles and permission from old tables to new - And finally run script that will update new columns with previous data and delete old columns/relationships/indices
I wrapped all scripts into stored procedures which makes it easier and safer to call from XAF (rather than multi SQL statements). Also, each stored procedure is saved as separate file and added to my agnostic module as embedded resources so I can access them during XAF update procedure.
Step 1 - prepare scripts and execute them in UpdateDatabaseBeforeUpdateSchema
Following code will load embedded script
if (CurrentDBVersion < new Version("3.5.3.105"))
{
UpdateStatus("InitSecurity", "Migrate users", "This action could take several minutes ...");
batchSql = Func.GetSqlScript(this.GetType(), "Link.Module.DatabaseUpdate.Script.{0}.sql", "sp_sys_ConvertUsersToString", "");
foreach (string sql in Func.GetNonQueryFromBatch(batchSql))
{
if (!string.IsNullOrWhiteSpace(sql)) ExecuteNonQueryCommand(sql, false);
}
using (var conn = DB.DBCommon.NewMSSqlConnection)
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(@"EXEC dbo.sp_sys_ConvertUsersToString", conn))
{
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
We are using helper functions GetSqlScript
(which loads embedded resource script) and GetNonQueryFromBatch
(which parses individual blocks between GO statements), so here they are:
public static string GetSqlScript(Type type, string ns, string scriptName, string version)
{
Assembly _assembly = type.Assembly;
StreamReader _textStreamReader;
string text = "";
try
{
string resourceName = "";
if (version == "") resourceName = string.Format(ns, scriptName);
else resourceName = string.Format(ns, version, scriptName);
if (resourceName != "")
{
using (_textStreamReader = new StreamReader(_assembly.GetManifestResourceStream(resourceName)))
{
text = _textStreamReader.ReadToEnd();
_textStreamReader.Close();
}
}
}
catch
{
throw new Exception("Error accessing resources!");
}
return text;
}
public static List<string> GetNonQueryFromBatch(string batchSql)
{
List<string> result = new List<string>();
string sql = string.Empty;
batchSql += "\nGO";
foreach (string line in batchSql.Split(new string[2] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries))
{
if (line.ToUpperInvariant().Trim() == "GO")
{
result.Add(sql);
sql = string.Empty;
}
else
{
sql += line + "\n";
}
}
return result;
}
And here is the code for sp_sys_ConvertUsersToString
stored procedure (see comments for more details):
ALTER PROCEDURE [dbo].[sp_sys_ConvertUsersToString]
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRAN T1;
DECLARE @sql nvarchar(MAX)
DECLARE @sch nvarchar(50)
DECLARE @tbl nvarchar(200)
DECLARE @col nvarchar(200)
DECLARE @fk nvarchar(200)
select @sql = (
select 'DROP INDEX ' + o.name + '.' + i.name + char(10) as [text()]
from sys.indexes i join sys.objects o on i.object_id=o.object_id
where i.name LIKE '%LinkID%'
for xml path('')
)
EXEC sp_executesql @sql
DECLARE cur_tbl CURSOR FOR
select
tab1.name AS [table],
sch.name AS [schema_name],
col1.name AS [column],
obj.name AS FK_NAME
FROM sys.foreign_key_columns fkc
INNER JOIN sys.objects obj
ON obj.object_id = fkc.constraint_object_id
INNER JOIN sys.tables tab1
ON tab1.object_id = fkc.parent_object_id
INNER JOIN sys.schemas sch
ON tab1.schema_id = sch.schema_id
INNER JOIN sys.columns col1
ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
INNER JOIN sys.tables tab2
ON tab2.object_id = fkc.referenced_object_id
INNER JOIN sys.columns col2
ON col2.column_id = referenced_column_id AND col2.object_id = tab2.object_id
WHERE
tab2.name = 'SecuritySystemUser'
and not tab1.name like '%SecuritySystem%'
DECLARE @idxtbl TABLE (
index_name nvarchar(200),
index_description nvarchar(200),
index_keys nvarchar(200)
)
DECLARE @sql2 nvarchar(max)
OPEN cur_tbl
FETCH NEXT FROM cur_tbl INTO @tbl, @sch, @col, @fk
WHILE @@FETCH_STATUS = 0
BEGIN
IF @col in ('UserCreated', 'UserModified')
BEGIN
SET @sql = 'ALTER TABLE ' + @sch + '.' + @tbl + ' ADD ' + @col + 'Str nvarchar(100) '
EXEC sp_executesql @sql
SET @sql = ' UPDATE q SET q.' + @col + 'Str = u.UserName FROM ' + @sch + '.' + @tbl + ' q INNER JOIN dbo.SecuritySystemUser u on u.Oid = q.' + @col
EXEC sp_executesql @sql
SET @sql = 'ALTER TABLE ' + @sch + '.' + @tbl + ' DROP CONSTRAINT ' + @fk
EXEC sp_executesql @sql
DELETE FROM @idxtbl
INSERT INTO @idxtbl
EXEC sys.sp_helpindex @objname = @tbl
SELECT
@sql = (
select
' DROP INDEX ' + @sch + '.' + @tbl + '.' + index_name + CHAR(10)
from @idxtbl
where index_keys = @col
for xml path('')
)
EXEC sp_executesql @sql
SET @sql = ' ALTER TABLE ' + @sch + '.' + @tbl + ' DROP COLUMN ' + @col
EXEC sp_executesql @sql
SET @sql = ' EXEC sp_rename ''' + @sch + '.' + @tbl + '.' + @col + 'Str'', ''' + @col + ''', ''COLUMN'''
EXEC sp_executesql @sql
END
ELSE
BEGIN
SET @sql =
'EXEC sp_rename ''' + @sch + '.' + @tbl + '.' + @col + ''', ''' + @col + 'Old'', ''COLUMN''; ' + CHAR(10) +
' EXEC sp_rename ''' + @fk + ''',''' + @fk + 'Old'', ''OBJECT'';' + CHAR(10)
DELETE FROM @idxtbl
INSERT INTO @idxtbl
EXEC sys.sp_helpindex @objname = @tbl
SELECT
@sql2 = (
select
' EXEC sp_rename ''' + @sch + '.' + @tbl + '.' + index_name + ''', ''' + index_name + 'Old'', ''INDEX''; ' + CHAR(10)
from @idxtbl
where index_keys = @col
for xml path('')
)
SET @sql = @sql + @sql2
EXEC sp_executesql @sql
END
FETCH NEXT FROM cur_tbl INTO @tbl, @sch, @col, @fk
END
CLOSE cur_tbl
DEALLOCATE cur_tbl
COMMIT TRAN T1
END
GO
So, in UpdateDatabaseBeforeUpdateSchema
we both update stored procedure and execute it. You might notice I use ALTER PROCEDUR
E instead of CREATE PROCEDURE
.
This is because I have a script which executes on beginning of UpdateDatabaseBeforeUpdateSchema
and creates any procedures, functions and views which does not exists with empty bodies.
This makes "dependency hell" hell of a less of a hell :-) and ensures every procedure / function / view exists which allows me to always use ALTER scripts and not bother too much.
Here is a snippet from this script:
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_sys_ConvertUsersToString]') AND type = N'P')
EXEC sp_executesql N'CREATE PROCEDURE [dbo].[sp_sys_ConvertUsersToString] AS BEGIN SET NOCOUNT ON END'
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_sys_MigrateUsersToPermissionPolicy]') AND type = N'P')
EXEC sp_executesql N'CREATE PROCEDURE [dbo].[sp_sys_MigrateUsersToPermissionPolicy] AS BEGIN SET NOCOUNT ON END'
GO
Of course, there are other ways to do this, but I found this to be most convenient and reduces the need to worry about dependencies to bare minimum.
Step 2 - XAF doing it's own magic
After above code executes and I handled columns I wanted to convert to nvarchar
and keep user names instead of user oids, I need to allow XAF to push all class changes and actually creates new permission policy tables and also creates columns and relationship in classes where I used user which I wanted to migrate (like Employee.SystemUser
)
This is handled by framework itself and starts automatically after UpdateDatabaseBeforeUpdateSchema
completes it's execution.
Step 3 - Post update migration
Ok, we are now in UpdateDatabaseAfterUpdateSchema.
It is important to note that you need to switch to new security system. You can do that either using module designer on your application (WinApplication.cs) setting PermissionPolicyUser
to UserType
and PermissionPolicyRole
to RoleType
(see XAF documentation for more details) or you can do it in code like this:
public static WindowsFormsApplication CreateApplication()
{
WindowsFormsApplication winApplication = new WindowsFormsApplication();
winApplication.Security =
new SecurityStrategyComplex(
typeof(DevExpress.Persistent.BaseImpl.PermissionPolicy.PermissionPolicyUser),
typeof(DevExpress.Persistent.BaseImpl.PermissionPolicy.PermissionPolicyRole),
new WinChangeDatabaseStandardAuthentication());
}
New tables and columns are created and we are left to migrate data from old renamed columns.
To do that, I am using another stored procedure named sp_sys_MigrateUsersToPermissionPolicy.
I use same system - procedure in a file addes as embedded resource which I then load and execute.
Here is UpdateDatabaseAfterUpdateSchema
code.
if (CurrentDBVersion < new Version("3.5.3.107"))
{
UpdateStatus("InitSecurity", "Migrate users", "This action might take several minutes ...");
ExecuteNonQueryCommand(@"
INSERT INTO PermissionPolicyUser (Oid, UserName, StoredPassword, ChangePasswordOnFirstLogon, IsActive)
SELECT NEWID(), UserName, StoredPassword, ChangePasswordOnFirstLogon, IsActive
FROM SecuritySystemUser
WHERE GCRecord IS NULL", false);
MySecurity.ConvertToNewSecurityModel(this, ObjectSpace);
batchSql = Func.GetSqlScript(this.GetType(), "Link.Module.DatabaseUpdate.Script.{0}.sql", "sp_sys_MigrateUsersToPermissionPolicy", "");
foreach (string sql in Func.GetNonQueryFromBatch(batchSql))
{
if (!string.IsNullOrWhiteSpace(sql)) ExecuteNonQueryCommand(sql, false);
}
using (var conn = DB.DBCommon.NewMSSqlConnection)
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(@"EXEC dbo.sp_sys_MigrateUsersToPermissionPolicy", conn))
{
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
So, again we update procedure code and then executing, but first we migrate users using SQL because C# code can not move passwords.
Here is sp_sys_MigrateUsersToPermissionPolicy
procedure (see comments for more info):
ALTER PROCEDURE [dbo].[sp_sys_MigrateUsersToPermissionPolicy]
AS
BEGIN
DECLARE @sql nvarchar(MAX)
DECLARE @sch nvarchar(50)
DECLARE @tbl nvarchar(200)
DECLARE @fk nvarchar(200)
DECLARE @col nvarchar(200)
DECLARE cur_tbl2 CURSOR FOR
select
tab1.name AS [table],
sch.name AS [schema_name],
obj.name AS FK_NAME,
col1.name AS [column]
FROM sys.foreign_key_columns fkc
INNER JOIN sys.objects obj
ON obj.object_id = fkc.constraint_object_id
INNER JOIN sys.tables tab1
ON tab1.object_id = fkc.parent_object_id
INNER JOIN sys.schemas sch
ON tab1.schema_id = sch.schema_id
INNER JOIN sys.columns col1
ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
INNER JOIN sys.tables tab2
ON tab2.object_id = fkc.referenced_object_id
INNER JOIN sys.columns col2
ON col2.column_id = referenced_column_id AND col2.object_id = tab2.object_id
WHERE
tab2.name = 'SecuritySystemUser'
and not tab1.name like '%SecuritySystem%'
and (
col1.name not in ('UserCreated', 'UserModified')
)
DECLARE @idxtbl TABLE (
index_name nvarchar(200),
index_description nvarchar(200),
index_keys nvarchar(200)
)
DECLARE @sql2 nvarchar(max)
OPEN cur_tbl2
FETCH NEXT FROM cur_tbl2 INTO @tbl, @sch, @fk, @col
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql = ''
IF COL_LENGTH(@tbl, REPLACE(@col, 'Old', '')) IS NOT NULL
SET @sql =
'UPDATE q SET q.' + REPLACE(@col, 'Old', '') + ' = pu.Oid FROM ' + @sch + '.' + @tbl + ' q INNER JOIN dbo.SecuritySystemUser u on u.Oid = q.' + @col + ' INNER JOIN PermissionPolicyUser pu ON pu.UserName = u.UserName; ' + CHAR(10)
SET @sql = @sql + ' ALTER TABLE ' + @sch + '.' + @tbl + ' DROP CONSTRAINT ' + @fk + '; ' + CHAR(10)
DELETE FROM @idxtbl
INSERT INTO @idxtbl
EXEC sys.sp_helpindex @objname = @tbl
SELECT
@sql2 = (
select
' DROP INDEX ' + @tbl + '.' + index_name + '; ' + CHAR(10)
from @idxtbl
where index_keys = @col
for xml path('')
)
SET @sql = @sql + @sql2
SET @sql = @sql +
' ALTER TABLE ' + @sch + '.' + @tbl + ' DROP COLUMN ' + @col + '; ' + CHAR(10)
EXEC sp_executesql @sql
FETCH NEXT FROM cur_tbl2 INTO @tbl, @sch, @fk, @col
END
CLOSE cur_tbl2
DEALLOCATE cur_tbl2
END
GO
And finally - here is the code kindly provided by DevExpress (slightly modified since I am actuall migrating users in SQL which original code did not support) wrapped in ConvertToNewSecurityModel:
public static void ConvertToNewSecurityModel(ModuleUpdater updater, IObjectSpace objectSpace)
{
using (MySecurity mySecurity = new MySecurity(updater, objectSpace, updateAll: false))
{
foreach (SecuritySystemUser securitySystemUser in objectSpace.GetObjects<SecuritySystemUser>())
{
mySecurity.CopyUser(securitySystemUser);
}
foreach (SecuritySystemRole PermissionPolicyRole in objectSpace.GetObjects<SecuritySystemRole>())
{
mySecurity.CopyRole(PermissionPolicyRole, null);
}
objectSpace.CommitChanges();
}
}
private void CopyUser(SecuritySystemUser securitySystemUser)
{
PermissionPolicyUser permissionPolicyUser = ObjectSpace.FindObject<PermissionPolicyUser>(new BinaryOperator("UserName", securitySystemUser.UserName));
if (permissionPolicyUser == null)
{
permissionPolicyUser = ObjectSpace.CreateObject<PermissionPolicyUser>();
permissionPolicyUser.UserName = securitySystemUser.UserName;
permissionPolicyUser.IsActive = securitySystemUser.IsActive;
permissionPolicyUser.SetPassword("123");
permissionPolicyUser.ChangePasswordOnFirstLogon = securitySystemUser.ChangePasswordOnFirstLogon;
}
foreach (SecuritySystemRole securitySystemRole in securitySystemUser.Roles)
{
CopyRole(securitySystemRole, permissionPolicyUser);
}
}
private void CopyRole(SecuritySystemRole securitySystemRole, PermissionPolicyUser permissionPolicyUser)
{
PermissionPolicyRole permissionPolicyRole = ObjectSpace.FindObject<PermissionPolicyRole>(new BinaryOperator("Name", securitySystemRole.Name));
if (permissionPolicyRole == null)
{
permissionPolicyRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
permissionPolicyRole.Name = securitySystemRole.Name;
permissionPolicyRole.PermissionPolicy = SecurityPermissionPolicy.DenyAllByDefault;
permissionPolicyRole.IsAdministrative = securitySystemRole.IsAdministrative;
permissionPolicyRole.CanEditModel = securitySystemRole.CanEditModel;
foreach (SecuritySystemTypePermissionObject securitySystemTypePermissionObject in securitySystemRole.TypePermissions)
{
CopyTypePermissions(securitySystemTypePermissionObject, securitySystemRole, permissionPolicyRole);
}
foreach (SecuritySystemRole parentRole in securitySystemRole.ParentRoles)
{
CopyParentRole(parentRole, permissionPolicyRole);
}
}
if (permissionPolicyUser != null && !permissionPolicyUser.Roles.Any(x => x.Name == permissionPolicyRole.Name))
{
permissionPolicyUser.Roles.Add(permissionPolicyRole);
}
}
private void CopyParentRole(SecuritySystemRole parentRole, PermissionPolicyRole permissionPolicyRole)
{
if (parentRole.IsAdministrative)
{
permissionPolicyRole.IsAdministrative = true;
}
if (parentRole.CanEditModel)
{
permissionPolicyRole.IsAdministrative = true;
}
foreach (SecuritySystemTypePermissionObject securitySystemTypePermissionObject in parentRole.TypePermissions)
{
CopyTypePermissions(securitySystemTypePermissionObject, parentRole, permissionPolicyRole);
}
foreach (SecuritySystemRole subParentRole in parentRole.ParentRoles)
{
CopyParentRole(subParentRole, permissionPolicyRole);
}
}
private void CopyTypePermissions(SecuritySystemTypePermissionObject securitySystemTypePermissionObject, SecuritySystemRole securitySystemRole, PermissionPolicyRole permissionPolicyRole)
{
PermissionPolicyTypePermissionObject permissionPolicyTypePermissionObject = ObjectSpace.FindObject<PermissionPolicyTypePermissionObject>(new BinaryOperator("TargetType", securitySystemTypePermissionObject.TargetType));
permissionPolicyTypePermissionObject = ObjectSpace.CreateObject<PermissionPolicyTypePermissionObject>();
permissionPolicyTypePermissionObject.TargetType = GetTargetType(securitySystemTypePermissionObject.TargetType);
permissionPolicyTypePermissionObject.Role = permissionPolicyRole;
if (securitySystemTypePermissionObject.AllowRead)
{
permissionPolicyTypePermissionObject.ReadState = SecurityPermissionState.Allow;
}
if (securitySystemTypePermissionObject.AllowWrite)
{
permissionPolicyTypePermissionObject.WriteState = SecurityPermissionState.Allow;
}
if (securitySystemTypePermissionObject.AllowCreate)
{
permissionPolicyTypePermissionObject.CreateState = SecurityPermissionState.Allow;
}
if (securitySystemTypePermissionObject.AllowDelete)
{
permissionPolicyTypePermissionObject.DeleteState = SecurityPermissionState.Allow;
}
if (securitySystemTypePermissionObject.AllowNavigate)
{
permissionPolicyTypePermissionObject.NavigateState = SecurityPermissionState.Allow;
}
foreach (SecuritySystemObjectPermissionsObject securitySystemObjectPermissionsObject in securitySystemTypePermissionObject.ObjectPermissions)
{
CopyObjectPermissions(securitySystemObjectPermissionsObject, permissionPolicyTypePermissionObject);
}
foreach (SecuritySystemMemberPermissionsObject securitySystemMemberPermissionsObject in securitySystemTypePermissionObject.MemberPermissions)
{
CopyMemberPermission(securitySystemMemberPermissionsObject, permissionPolicyTypePermissionObject);
}
permissionPolicyRole.TypePermissions.Add(permissionPolicyTypePermissionObject);
}
private void CopyMemberPermission(SecuritySystemMemberPermissionsObject securitySystemMemberPermissionsObject, PermissionPolicyTypePermissionObject permissionPolicyTypePermissionObject)
{
PermissionPolicyMemberPermissionsObject permissionPolicyMemberPermissionsObject = ObjectSpace.CreateObject<PermissionPolicyMemberPermissionsObject>();
permissionPolicyMemberPermissionsObject.TypePermissionObject = permissionPolicyTypePermissionObject;
if (securitySystemMemberPermissionsObject.AllowRead)
{
permissionPolicyMemberPermissionsObject.ReadState = SecurityPermissionState.Allow;
}
if (securitySystemMemberPermissionsObject.AllowWrite)
{
permissionPolicyMemberPermissionsObject.WriteState = SecurityPermissionState.Allow;
}
permissionPolicyMemberPermissionsObject.Members = securitySystemMemberPermissionsObject.Members;
permissionPolicyMemberPermissionsObject.Criteria = securitySystemMemberPermissionsObject.Criteria;
permissionPolicyTypePermissionObject.MemberPermissions.Add(permissionPolicyMemberPermissionsObject);
}
private void CopyObjectPermissions(SecuritySystemObjectPermissionsObject securitySystemObjectPermissionsObject, PermissionPolicyTypePermissionObject permissionPolicyTypePermissionObject)
{
PermissionPolicyObjectPermissionsObject permissionPolicyObjectPermissionsObject = ObjectSpace.CreateObject<PermissionPolicyObjectPermissionsObject>();
permissionPolicyObjectPermissionsObject.TypePermissionObject = permissionPolicyTypePermissionObject;
if (securitySystemObjectPermissionsObject.AllowRead)
{
permissionPolicyObjectPermissionsObject.ReadState = SecurityPermissionState.Allow;
}
if (securitySystemObjectPermissionsObject.AllowWrite)
{
permissionPolicyObjectPermissionsObject.WriteState = SecurityPermissionState.Allow;
}
if (securitySystemObjectPermissionsObject.AllowDelete)
{
permissionPolicyObjectPermissionsObject.DeleteState = SecurityPermissionState.Allow;
}
if (securitySystemObjectPermissionsObject.AllowNavigate)
{
permissionPolicyObjectPermissionsObject.NavigateState = SecurityPermissionState.Allow;
}
permissionPolicyObjectPermissionsObject.Criteria = securitySystemObjectPermissionsObject.Criteria;
permissionPolicyTypePermissionObject.ObjectPermissions.Add(permissionPolicyObjectPermissionsObject);
}
private Type GetTargetType(Type currentType)
{
if (currentType == null) return null;
Type outType;
if (!SecurityAssociationClassDictionary.TryGetValue(currentType, out outType))
{
outType = currentType;
}
return outType;
}
private static Dictionary<Type, Type> SecurityAssociationClassDictionary = new Dictionary<Type, Type>(){
{ typeof(SecuritySystemUser),typeof(PermissionPolicyUser) },
{ typeof(SecuritySystemRole),typeof(PermissionPolicyRole) },
{ typeof(SecuritySystemTypePermissionObject ),typeof(PermissionPolicyTypePermissionObject ) },
{ typeof(SecuritySystemObjectPermissionsObject ),typeof(PermissionPolicyObjectPermissionsObject ) },
{ typeof(SecuritySystemMemberPermissionsObject ),typeof(PermissionPolicyMemberPermissionsObject ) }
};
Gimmicks
I had rather strange situation where above code failed to load old SecuritySystemUser and SecuritySystemRole because system was switched to new permission policy classes, so it seemed old ones were no longer registered by XAF.
Usually, I would fix that by locating those classes in agnostic module designer, find desired type and simply select Use in application.
But for some reason I could not find those classes anywhere, so instead I put following code in Module.cs constructor:
public LinkModule()
{
AdditionalExportedTypes.AddRange(
new Type[] { typeof(SecuritySystemUser), typeof(SecuritySystemRole) });
InitializeComponent();
}
And this did the trick, old classes were populated correctly.
Finish line
And that's it :-)
Your scenario will surely be different than mine, but process is the same - actual SQL scripts will need to be adjusted to cover your spefics.
If some more details are required, don't be shy and drop me a word or two.
Good luck migrating!