How to add, delete, check and modify gracefully (8): Query by user relationship


User relationship (Relation) describes the relationship between people in the business system, such as: signing, following, or friend relationship.

When we expanded the identity management module before, we have implemented user relationship management, you can view the previous content of this series of blog posts. How to add, delete, check and modify gracefully (2): Extending the identity management module

principle

Query basis

The relationship between users is stored through the Relation table. The model is shown in the figure below:

insert image description here

  • Relationship types are defined by Type

  • Relationship points are described by UserId and RelatedUserId

    The relationship between personnel is single-item, that is to say, A can be a friend of B, but B is not necessarily a friend of A

    Positive relationship: User -> RelatedUser

    Reverse relationship: RelatedUser -> User

The query target business object HealthAlarm is associated with the business user HealthClient. Since the business user and the authentication user IdentityUser share the same Id, the business object can be found by querying the User associated with the user relationship.

insert image description here

accomplish

positive user relationship

Define the query by forward user relationship (IRelationToOrientedFilter) interface

public interface IRelationToOrientedFilter
{
    Guid? RelationToUserId { get; set; }
    
    public string EntityUserIdIdiom { get; }

    string RelationType { get; set; }

}

  • EntityUserIdIdiom: Semantic UserId, used to specify the name used to describe the "UserId" field in the business entity, if not specified, the default is "UserId";
  • RelationToUserId: Forward relationship user Id, if it is Guid.Empty, use the Id of the currently logged in user;
  • RelationType: the type of relationship, such as: "attach" means signing, "follow" means following, which can be customized.

For the Relation service, its dependency is at the application layer, and the relational user to find the specified user will be implemented in the subclass of CurdAppServiceBase. Create an abstract method GetUserIdsByRelatedToAsync

protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType);

Create the method of applying filter conditions: ApplyRelationToOrientedFiltered, where the splicing of LINQ expressions is realized,

ICurrentUser is a service of Abp, which is used to obtain the information of the currently logged in user

code show as below:

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;
}


reverse user relationship

Define the query by reverse user relationship (IRelationFromOrientedFilter) interface

public interface IRelationFromOrientedFilter
{
    Guid? RelationFromUserId { get; set; }
    
    public string EntityUserIdIdiom { get; }

    string RelationType { get; set; }

}

  • EntityUserIdIdiom: Semantic UserId, used to specify the name used to describe the "UserId" field in the business entity, if not specified, the default is "UserId";
  • RelationFromUserId: Reverse relationship user Id, if it is Guid.Empty, use the Id of the currently logged in user;
  • RelationType: the type of relationship, such as: "attach" means signing, "follow" means following, which can be customized.

For the Relation service, its dependency is at the application layer, and the relational user to find the specified user will be implemented in the subclass of CurdAppServiceBase. Create an abstract method GetUserIdsByRelatedFromAsync

protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType);

Create an application filter method: ApplyRelationFromOrientedFiltered, here to realize splicing LINQ expressions,

ICurrentUser is a service of Abp, which is used to obtain the information of the currently logged in user

code show as below:

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 and IRelationFromOrientedFilter are not mutually exclusive in implementation.

Note that the conditions under which filtering can be applied are:

  1. The input needs to implement the IRelationToOrientedFilter interface;
  2. Entities must be associated with users.

Otherwise, the IQueryable object will be returned unchanged.

use

In the application layer, implement GetUserIdsByRelatedToAsync

protected override async Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType)
{
    var ids = await relationAppService.GetRelatedToUserIdsAsync(new GetRelatedUsersInput()
    {
        UserId = userId,
        Type = relationType
    });
    return ids;

}

或GetUserIdsByRelatedFromAsync

protected override async Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType)
{
    var ids = await relationAppService.GetRelatedFromUserIdsAsync(new GetRelatedUsersInput()
    {
        UserId = userId,
        Type = relationType
    });
    return ids;

}

Implement the IRelationToOrientedFilter or GetUserIdsByRelatedFromAsync interface in GetAllAlarmInput, the code is as follows:

public class GetAllAlarmInput : PagedAndSortedResultRequestDto, IRelationToOrientedFilter
{ 
    public Guid? RelationToUserId { get ; set ; }
    public string RelationType { get; set; }
    public string EntityUserIdIdiom { get; }

    ...
}

test

Create some clients (Client)

insert image description here

Enter customer management, click "View Details" in the customer list on the right

Open the customer details page, click Manage - Set Contract Employees

insert image description here

Select a user, and the customer will be signed under the user account. Here we sign customer 1 and customer 3 under the current account admin.

insert image description here

Log in to the account of the contracted user (admin), click "My" - Customers - Contracted Customers

It can be seen in the customer list that customer 1 and customer 3 have signed contracts with the current account.

insert image description here

The message Payload of the combined query is as follows:

insert image description here

Guess you like

Origin blog.csdn.net/jevonsflash/article/details/131787400