ユーザー関係 (Relation) は、ビジネス システム内の人々間の関係 (署名、フォロー、友人関係など) を表します。
以前にアイデンティティ管理モジュールを拡張したときに、ユーザー関係管理を実装しました。この一連のブログ投稿の以前のコンテンツを表示できます。適切に追加、削除、確認、変更する方法 (2): ID 管理モジュールの拡張
原理
クエリベース
ユーザー間の関係は、関係テーブルを通じて保存されます。モデルを次の図に示します。
-
関係タイプはタイプによって定義されます
-
関係ポイントは UserId と AssociatedUserId によって記述されます
人事間の関係は単一項目です。つまり、A は B の友人になることができますが、B は必ずしも A の友人であるとは限りません
肯定的な関係: ユーザー -> 関連ユーザー
逆の関係: 関連ユーザー -> ユーザー
クエリ対象のビジネス オブジェクト HealthAlarm はビジネス ユーザー HealthClient に関連付けられています。ビジネス ユーザーと認証ユーザー IdentityUser は同じ Id を共有しているため、ユーザー関係に関連付けられた User をクエリすることでビジネス オブジェクトを見つけることができます。
達成
ポジティブなユーザー関係
フォワード ユーザー リレーションシップ (IRelationToOrientedFilter) インターフェイスによるクエリを定義します。
public interface IRelationToOrientedFilter
{
Guid? RelationToUserId { get; set; }
public string EntityUserIdIdiom { get; }
string RelationType { get; set; }
}
- EntityUserIdIdiom: セマンティック UserId。ビジネス エンティティの「UserId」フィールドを記述するために使用される名前を指定するために使用されます。指定しない場合、デフォルトは「UserId」です。
- RelationToUserId: 転送関係ユーザー ID。Guid.Empty の場合は、現在ログインしているユーザーの ID を使用します。
- RelationType: 関係のタイプ。「attach」は署名を意味し、「follow」はフォローを意味し、カスタマイズ可能です。
Relation サービスの場合、その依存関係はアプリケーション層にあり、指定されたユーザーを検索するリレーショナル ユーザーは CurdAppServiceBase のサブクラスに実装されます。抽象メソッド GetUserIdsByManyToAsync を作成する
protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType);
フィルター条件を適用するメソッドを作成します:ApplyRelationToOrientedFiltered。LINQ 式の結合が実現されます。
ICurrentUser は、現在ログインしているユーザーの情報を取得するために使用される Abp のサービスです。
コードは以下のように表示されます。
protected virtual async Task<IQueryable<TEntity>> ApplyRelationToOrientedFiltered(IQueryable<TEntity> query, TGetListInput input)
{
if (input is IRelationToOrientedFilter)
{
var filteredInput = input as IRelationToOrientedFilter;
var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
if (string.IsNullOrEmpty(entityUserIdIdiom))
{
entityUserIdIdiom = "UserId";
}
if (HasProperty<TEntity>(entityUserIdIdiom))
{
var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
if (filteredInput != null && filteredInput.RelationToUserId.HasValue && !string.IsNullOrEmpty(filteredInput.RelationType))
{
Guid userId = default;
if (filteredInput.RelationToUserId.Value == Guid.Empty)
{
using (var scope = ServiceProvider.CreateScope())
{
var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
if (currentUser != null)
{
userId = currentUser.GetId();
}
}
}
else
{
userId = filteredInput.RelationToUserId.Value;
}
var ids = await GetUserIdsByRelatedToAsync(userId, filteredInput.RelationType);
Expression originalExpression = null;
var parameter = Expression.Parameter(typeof(TEntity), "p");
foreach (var id in ids)
{
var keyConstantExpression = Expression.Constant(id, typeof(Guid));
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);
if (originalExpression == null)
{
originalExpression = expressionSegment;
}
else
{
originalExpression = Expression.Or(originalExpression, expressionSegment);
}
}
var equalExpression = originalExpression != null ?
Expression.Lambda<Func<TEntity, bool>>(originalExpression, parameter)
: p => false;
query = query.Where(equalExpression);
}
}
}
return query;
}
逆ユーザー関係
逆ユーザー関係 (IRelationFromOrientedFilter) インターフェイスによるクエリの定義
public interface IRelationFromOrientedFilter
{
Guid? RelationFromUserId { get; set; }
public string EntityUserIdIdiom { get; }
string RelationType { get; set; }
}
- EntityUserIdIdiom: セマンティック UserId。ビジネス エンティティの「UserId」フィールドを記述するために使用される名前を指定するために使用されます。指定しない場合、デフォルトは「UserId」です。
- RelationFromUserId: 逆関係ユーザー ID。Guid.Empty の場合は、現在ログインしているユーザーの ID を使用します。
- RelationType: 関係のタイプ。「attach」は署名を意味し、「follow」はフォローを意味し、カスタマイズ可能です。
Relation サービスの場合、その依存関係はアプリケーション層にあり、指定されたユーザーを検索するリレーショナル ユーザーは CurdAppServiceBase のサブクラスに実装されます。抽象メソッド GetUserIdsByManyFromAsync を作成する
protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType);
アプリケーション フィルター メソッドを作成します:ApplyRelationFromOrientedFiltered。ここで LINQ 式のスプライシングを実現します。
ICurrentUser は、現在ログインしているユーザーの情報を取得するために使用される Abp のサービスです。
コードは以下のように表示されます。
protected virtual async Task<IQueryable<TEntity>> ApplyRelationFromOrientedFiltered(IQueryable<TEntity> query, TGetListInput input)
{
if (input is IRelationFromOrientedFilter)
{
var filteredInput = input as IRelationFromOrientedFilter;
var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
if (string.IsNullOrEmpty(entityUserIdIdiom))
{
entityUserIdIdiom = "UserId";
}
if (HasProperty<TEntity>(entityUserIdIdiom))
{
var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
if (filteredInput != null && filteredInput.RelationFromUserId.HasValue && !string.IsNullOrEmpty(filteredInput.RelationType))
{
Guid userId = default;
if (filteredInput.RelationFromUserId.Value == Guid.Empty)
{
using (var scope = ServiceProvider.CreateScope())
{
var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
if (currentUser != null)
{
userId = currentUser.GetId();
}
}
}
else
{
userId = filteredInput.RelationFromUserId.Value;
}
var ids = await GetUserIdsByRelatedFromAsync(userId, filteredInput.RelationType);
Expression originalExpression = null;
var parameter = Expression.Parameter(typeof(TEntity), "p");
foreach (var id in ids)
{
var keyConstantExpression = Expression.Constant(id, typeof(Guid));
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);
if (originalExpression == null)
{
originalExpression = expressionSegment;
}
else
{
originalExpression = Expression.Or(originalExpression, expressionSegment);
}
}
var equalExpression = originalExpression != null ?
Expression.Lambda<Func<TEntity, bool>>(originalExpression, parameter)
: p => false;
query = query.Where(equalExpression);
}
}
}
return query;
}
IRelationToOrientedFilter と IRelationFromOrientedFilter は、実装において相互に排他的ではありません。
フィルタリングを適用できる条件は次のとおりです。
- 入力は IRelationToOrientedFilter インターフェイスを実装する必要があります。
- エンティティはユーザーに関連付ける必要があります。
それ以外の場合、IQueryable オブジェクトは変更されずに返されます。
使用
アプリケーション層で、GetUserIdsByManyToAsync を実装します。
protected override async Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType)
{
var ids = await relationAppService.GetRelatedToUserIdsAsync(new GetRelatedUsersInput()
{
UserId = userId,
Type = relationType
});
return ids;
}
またはGetUserIdsBy AssociatedFromAsync
protected override async Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType)
{
var ids = await relationAppService.GetRelatedFromUserIdsAsync(new GetRelatedUsersInput()
{
UserId = userId,
Type = relationType
});
return ids;
}
GetAllAlarmInput に IRelationToOrientedFilter または GetUserIdsByManyFromAsync インターフェイスを実装します。コードは次のとおりです。
public class GetAllAlarmInput : PagedAndSortedResultRequestDto, IRelationToOrientedFilter
{
public Guid? RelationToUserId { get ; set ; }
public string RelationType { get; set; }
public string EntityUserIdIdiom { get; }
...
}
テスト
いくつかのクライアントを作成する (クライアント)
顧客管理に入り、右側の顧客リストの「詳細を見る」をクリック
顧客詳細ページを開き、「管理 - 契約社員の設定」をクリックします。
ユーザーを選択すると、そのユーザー アカウントで顧客が署名されます。ここでは、現在のアカウント管理者で顧客 1 と顧客 3 に署名します。
契約ユーザー(管理者)のアカウントにログインし、「マイ」-「顧客」-「ご契約のお客さま」をクリックします。
顧客リストでは、顧客 1 と顧客 3 が当座預金口座と契約を結んでいることがわかります。
結合されたクエリのメッセージ ペイロードは次のとおりです。