Clear        


                
                    using APP.Domain;
using APP.Models;
using CORE.Models;
using CORE.Services;
using Microsoft.EntityFrameworkCore;

namespace APP.Services
{
    // UserService inherits the DbService class for User entity type,
    // therefore User entity database operations of the DbService
    // class can be used in this class.
    // UserService also implements the IService interface for
    // UserRequest and UserResponse types, therefore IService method
    // definitions can be implemented for types UserRequest and
    // UserResponse in this class, which will be used by
    // the controller actions.
    public class UserService : DbService<User>,
        IService<UserRequest, UserResponse>
    {
        // Constructor that passes the injected DbContext instance
        // to the DbService base class constructor.
        public UserService(DbContext db) : base(db)
        {
        }

        // Overrides the base virtual DbQuery method to include
        // Status, UserRole and Role entities data to the query
        // to be used in the Query, Edit, Update and Remove
        // methods and orders the query by User entity Score
        // property descending first, then the ordered records
        // are ordered descending by User entity RegistrationDate
        // property. Finally the ordered records are ordered
        // ascending by User entity UserName property.
        // The overridden DbQuery method can be used in any method
        // below by invoking the DbQuery method.
        protected override IQueryable<User> DbQuery()
        {
            // Relationships:
            // User <-> Status.
            // User <-> UserRoles <-> Roles.
            return base.DbQuery()
                .Include(u => u.Status) // Include Status entities
                                        // from User entities.
                .Include(u => u.UserRoles) // Include UserRole entities
                                           // from User entities.
                .ThenInclude(ur => ur.Role) // Then include Role entities
                                            // from UserRole entities.
                .OrderByDescending(u => u.Score)
                .ThenByDescending(u => u.RegistrationDate)
                .ThenBy(u => u.UserName);
                // Only one OrderBy method must be called.
                // The other called ordering methods must be ThenBy.
            // u: User entity delegate, ur: UserRole entity delegate.
        }

        // Gets the updated query from the DbQuery method then projects
        // the User entities to user responses and returns the
        // user response query.
        // The returned query can be executed by invoking
        // ToList, SingleOrDefault, FirstOrDefault, Any, etc. LINQ
        // (Language Integrated Query) methods and then the returned result
        // can be used.
        // This projected query by the Select LINQ method will be used by
        // the List and Single methods of the IService interface.
        public IQueryable<UserResponse> Query()
        {
            return DbQuery().Select(u => new UserResponse
            {
                // Map each User entity property to
                // UserResponse property.
                Id = u.Id,
                UserName = u.UserName,
                Password = u.Password,
                FirstName = u.FirstName,
                LastName = u.LastName,
                RegistrationDate = u.RegistrationDate,
                BirthDate = u.BirthDate,
                IsOnline = u.IsOnline,
                Gender = u.Gender,
                Score = u.Score,
                StatusId = u.StatusId,
                // Way 1: Map primitive type data for basic information.
                StatusTitleR = u.Status.Title,
                // Way 2: Map complex type object for more information.
                // Either Way 1, Way 2 or both can be used.
                StatusR = new StatusResponse
                {
                    // Map each Status entity property to
                    // UserResponse's StatusResponse property.
                    Id = u.Status.Id,
                    Title = u.Status.Title
                },
                // Way 1: Map primitive type data for basic information.
                RoleNamesR = string.Join("<br>", u.UserRoles
                    .OrderBy(ur => ur.Role.Name)
                    .Select(ur => ur.Role.Name)),
                // Way 2: Map complex type object for more information.
                // Either Way 1, Way 2 or both can be used.
                RolesR = u.UserRoles.OrderBy(ur => ur.Role.Name)
                    .Select(ur => new RoleResponse
                {
                    // Map each Role entity property to
                    // UserResponse's RoleResponse property through UserRoles.
                    Id = ur.Role.Id,
                    Name = ur.Role.Name
                }).ToList(),
                // Hidden password with * character.
                PasswordR = new string('*', u.Password.Length),
                // Concatenated first name and list name with white space character.
                FullNameR = u.FirstName + " " + u.LastName,
                // Formatted date and time in month/day/year hour:minute:second
                // format. No need to use the CultureInfo instance for the second
                // parameter of the ToString method since CultureInfo has been
                // assigned in the base abstract Service class by the Culture
                // property assignment. If Culture property of the base abstract
                // Service class is changed within this class constructor,
                // the changed value will be used by the CultureInfo instance and
                // then ToString method.
                RegistrationDateR = u.RegistrationDate
                    .ToString("MM/dd/yyyy HH:mm.ss"),
                // Ternary operator:
                BirthDateR = u.BirthDate.HasValue ? // if (u.BirthDate != null).
                    u.BirthDate.Value.ToShortDateString() : // Assign u.BirthDate's
                                                            // value since u.BirthDate
                                                            // is nullable.
                    string.Empty, // "" string.
                // ToShortDateString returns the date in month/day/year format.
                IsOnlineR = u.IsOnline ? "Yes" : "No",
                GenderR = u.Gender.ToString(), // Assigns "Man" or "Woman".
                ScoreR = u.Score.ToString("N1") // N: Number format.
                                                // 1: 1 decimal.
                                                // C can also be used for currency.
                                                // No need to define the second
                                                // CultureInfo parameter since Culture
                                                // property has been assigned in the
                                                // base abstract Service class.
            });
            // u: User entity delegate, ur: UserRole entity delegate.
        }

        // Gets the User entity by ID then returns a mapped
        // UserRequest instance from the found User entity.
        public UserRequest Edit(int id)
        {
            var userEntity = DbSingle(id);
            // Example SQL: "select * from Users where Id = 7".

            // userEntity may be null since the OrDefault version
            // of the Single method is used. If Single method
            // was used, an exception would be thrown.
            if (userEntity is null) // if (userEntity == null) can
                                    // also be written.
                return null;

            // Map each User entity property to UserRequest property
            // and return the UserRequest instance.
            return new UserRequest
            {
                Id = userEntity.Id,
                UserName = userEntity.UserName,
                Password = userEntity.Password,
                FirstName = userEntity.FirstName,
                LastName = userEntity.LastName,
                BirthDate = userEntity.BirthDate,
                Gender = userEntity.Gender,
                Score = userEntity.Score,
                StatusId = userEntity.StatusId,
                RoleIds = userEntity.RoleIds
            };
        }

        // Checks whether the user with the same trimmed and
        // case sensitive user name exists in the Users database table
        // first. If it doesn't exist, inserts the User entity
        // mapped from the UserRequest instance to the Users
        // database table.
        public CommandResponse Add(UserRequest request)
        {
            if (DbQuery().Any(u => 
                u.UserName == request.UserName.Trim()
                //&& u.BirthDate == request.BirthDate
                // One or many conditions can be used by && (and), || (or)
                // and ! (not) operators.
            ))
                return Error("User with the same user name exists!");
            // u: User entity delegate.
            // Trim method removes white space characters in the
            // beginning and at the end.

            // Create a new User entity instance from the
            // UserRequest instance then insert the entity in the
            // Users database table (second save default parameter
            // of the DbAdd method is true therefore changes of the
            // DbSets are commited to the related database tables).
            var userEntity = new User
            {
                UserName = request.UserName.Trim(),
                Password = request.Password.Trim(),
                FirstName = request.FirstName?.Trim(),
                // Since request.FirstName may be null, ? is used after
                // meaning that if request.FirstName is null, assign
                // null to the User entity's FirstName property else
                // assign the trimmed value of request.FirstName.
                LastName = request.LastName?.Trim(),
                // Since request.LastName may be null, ? is used after
                // meaning that if request.LastName is null, assign
                // null to the User entity's LastName property else
                // assign the trimmed value of request.LastName.
                RegistrationDate = DateTime.Now,
                BirthDate = request.BirthDate,
                Gender = request.Gender,
                Score = request.Score ?? 0,
                StatusId = request.StatusId ?? 0,
                // ??: Null coalescing operator: If request.StatusId is null
                // assign 0 else assign request.StatusId value to the User entity
                // StatusId property. request.StatusId.Value may also be used
                // since request.StatusId is required.
                // Same for request.Score.
                RoleIds = request.RoleIds
            };
            DbAdd(userEntity);

            // Entity's Id value will be updated by the database
            // after the insert operation.
            return Success(userEntity.Id, "User created successfully.");
        }

        // Checks whether the user with the same trimmed and
        // case sensitive user name other than the user request record
        // exists in the Users database table first.
        // If the entity doesn't exist, gets the User entity by ID
        // from the table, deletes the relational UserRole entities,
        // then updates the User entity properties from user request
        // properties and finally updates the User entity in the
        // Users database table.
        public CommandResponse Update(UserRequest request)
        {
            if (DbQuery().Any(u => u.Id != request.Id
                && u.UserName == request.UserName.Trim()))
                return Error("User with the same user name exists!");

            // Get the User entity by ID from the Users database
            // table and check whether it is null or not.
            var userEntity = DbSingle(request.Id);
            if (userEntity is null)
                return Error("User not found!");

            // Delete the relational UserRole entities first.
            if (request.RoleIds is not null)
                DbRemove(userEntity.UserRoles);

            // Then update User entity properties from user request properties.
            // Then update the entity in the Users database table (second save
            // default parameter of the DbUpdate method is true
            // therefore changes of the DbSets are commited to the related
            // database tables).
            userEntity.UserName = request.UserName.Trim();
            userEntity.Password = request.Password.Trim();
            userEntity.FirstName = request.FirstName?.Trim();
            // Since request.FirstName may be null, ? is used after
            // meaning that if request.FirstName is null, assign
            // null to the User entity's FirstName property else
            // assign the trimmed value of request.FirstName.
            userEntity.LastName = request.LastName?.Trim();
            // Since request.LastName may be null, ? is used after
            // meaning that if request.LastName is null, assign
            // null to the User entity's LastName property else
            // assign the trimmed value of request.LastName.
            userEntity.BirthDate = request.BirthDate;
            userEntity.Gender = request.Gender;
            if (request.Score.HasValue)
                userEntity.Score = request.Score.Value;
            if (request.StatusId.HasValue)
                userEntity.StatusId = request.StatusId.Value;
            if (request.RoleIds is not null)
                userEntity.RoleIds = request.RoleIds;
            DbUpdate(userEntity);

            return Success(userEntity.Id, "User updated successfully.");
        }

        // Gets the User entity by ID from the Users database table.
        // If the entity exists, deletes the relational UserRole entities.
        // Then deletes the entity from the Users database table.
        public CommandResponse Remove(int id)
        {
            // Get the User entity by ID from the Users database table
            // and check whether it is null or not.
            var userEntity = DbSingle(id);
            if (userEntity is null)
                return Error("User not found!");

            // Delete the relational UserRole entities first even Cascade
            // delete rule is defined in the database (recommended).
            // This way would also be suitable if the delete rule was
            // defined as No Action in the database.
            DbRemove(userEntity.UserRoles);

            // Then delete the User entity from the Users database table
            // (second save default parameter of the DbRemove method is
            // true therefore changes of the DbSets are commited to
            // the related database tables).
            DbRemove(userEntity);

            return Success(userEntity.Id, "User deleted successfully.");
        }
    }
}