fingers10 / JqueryDataTablesServerSide

Asp.Net Core Server Side for Jquery DataTables Multiple Column Filtering and Sorting with Pagination and Excel Export
MIT License
227 stars 37 forks source link

[QUESTION] Search Nested Model properties with flatten VM hierarchy #26

Closed hosamre94 closed 4 years ago

hosamre94 commented 4 years ago

Hi,

I have this Model

public class Patient
    {
        public virtual string UserId { get; set; }
        public virtual ApplicationUser User { get; set; }
    }
public class ApplicationUser : IdentityUser
    {
        public string PhoneNumber { get; set; }
    }

And My ViewModel looks like this

public class PatientVM
    {
        [Sortable]
        [SearchableString]
        //this property is mapped from User.PhoneNumber
public string PhoneNumber { get; set; }

        public void Mapping(Profile profile)
        {
            profile.CreateMap<Subscriber, SubscribersListDto>()
                .ForMember(destination => destination.PhoneNumber, member => member.MapFrom(source => source.User.PhoneNumber));;
        }

when searching for a PhoneNumber which is mapped from User.PhoneNumber I got this error because the Vm has a flatter hierarchy and my Base Model is nested as shown in the example above

An unhandled exception has occurred while executing the request.
System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found.
   at System.Attribute.GetCustomAttribute(MemberInfo element, Type attributeType, Boolean inherit)
   at System.Reflection.CustomAttributeExtensions.GetCustomAttribute[T](MemberInfo element)
   at JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure.SearchOptionsProcessor`2.GetTermsFromModel(Type parentSortClass, String parentsEntityName, String parentsName, Boolean hasNavigation)+MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure.SearchOptionsProcessor`2.GetValidTerms(IEnumerable`1 columns)+MoveNext()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure.SearchOptionsProcessor`2.Apply(IQueryable`1 query, IEnumerable`1 columns)

Any way to make searching work for this kind of implementation Thanks.

fingers10 commented 4 years ago

Dear @hosamre94 ,

This is not possible right now with this package. Can you try changing your view model to match your model structure and try?

Thanks, Abdul Rahman

fingers10 commented 4 years ago

Closing this as there is no response.

hosamre94 commented 4 years ago

if anyone is interested in a solution for this issue when using AutoMapper

Dto is like this

public class CitiesListDto 
    {
        [Sortable(Default = true)]
        [SearchableInt]
        public string Id { get; set; }

        [Sortable]
        [SearchableString]
        public string Name { get; set; }

        [Sortable]
        [SearchableString]
        public string Country { get; set; }

        [Sortable]
        [SearchableInt]
        public int UsersCount { get; set; }

        public void Mapping(Profile profile)
        {
            profile.CreateMap<City, CitiesListDto>()
                .ForMember(destination => destination.Name, member => member.MapFrom(source => source.Key))
                .ForMember(destination => destination.Country, member => member.MapFrom(source => source.Country.Key))
                .ForMember(destination => destination.UsersCount, member => member.MapFrom(source => source.Users.Count()));
        }
    }

and my model

 public partial class City : AuditableEntity
    {
        public string Key { get; set; }
        public int CountryId { get; set; }
        public virtual Country Country { get; set; }
        public ICollection<ApplicationUser> Users { get; set; }
    }

simply doing the projection first then doing the sorting and ordering with the input and output of both functions is the same as our Dto seems to work fine

       public async static Task<JqueryDataTablesPagedResults<TOut>> ToPagedModel<T, TOut>(this IQueryable<T> source, JqueryDataTablesParameters table, IConfigurationProvider configuration)
        {
            var result = source.ProjectTo<TOut>(configuration);
//Here the input and the output should be the same as our Dto
            result = SearchOptionsProcessor<TOut, TOut>.Apply(result, table.Columns);
            result = SortOptionsProcessor<TOut, TOut>.Apply(result, table);

            var size = await result.CountAsync();
            var items = await result
                .Skip((table.Start / table.Length) * table.Length)
                .Take(table.Length)
                .ToArrayAsync();

            return new JqueryDataTablesPagedResults<TOut>
            {
                Items = items,
                TotalSize = size
            };
        }
  var result = await _context.Cities
             .AsNoTracking()
             .ToPagedModel<City, CitiesListDto>(request.Table, _mapper.ConfigurationProvider);
fingers10 commented 4 years ago

Hey @hosamre94,

Many thanks for the suggestion and solution. I'll try to add this as an example in the demo solution. I'll get back to you on this if I need any assistance.

Cheers, fingers10