-2

I am trying to write an universal search to use for all objects. I have this code, which is working fine to search in just one object's properties, but I would also like to search also in properties in related objects.

Eg. I have these Models/Objects

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Address{ get; set; }

    public ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
    public int Id { get; set; }
    public DateTime From{ get; set; }
    public DateTime To{ get; set; }
    public string Comment{ get; set; }
    public int CustomerId { get; set; }

    [ForeignKey("CustomerId")]
    public Customer Customer { get; set; }
}

and I want to search if any of properties contains some a string eg. "Peter", I will call it this way:

string searchString = "Peter";
var customers = db.Customers
    .Include(x => x.Contracts)
    .WhereAnyPropertiesOfSimilarTypeContains(searchString);

this code will check if any properties of 'Customer' contains string "Peter". But I would also need to check if the related model 'Contract' contains "Peter.

public static class EntityHelper
    {
        public static IQueryable<TEntity> WhereAnyPropertiesOfSimilarTypeContains<TEntity, TProperty>(this IQueryable<TEntity> query, TProperty value)
        {
            var param = Expression.Parameter(typeof(TEntity));

            var predicate = PredicateBuilder.False<TEntity>(); //--- True to equal
            var entityFields = GetEntityFieldsToCompareTo<TEntity, TProperty>();
            foreach (var fieldName in entityFields)
            {
                MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });

                var predicateToAdd = Expression.Lambda<Func<TEntity, bool>>(
                    Expression.Call(
                        Expression.PropertyOrField(param, fieldName), method,
                        Expression.Constant(value)), param);

                predicate = predicate.Or(predicateToAdd);  //--- And to equal
            }

            return query.Where(predicate);
        }

        // TODO: You'll need to find out what fields are actually ones you would want to compare on.
        //       This might involve stripping out properties marked with [NotMapped] attributes, for
        //       for example.
        public static IEnumerable<string> GetEntityFieldsToCompareTo<TEntity, TProperty>()
        {
            Type entityType = typeof(TEntity);
            Type propertyType = typeof(TProperty);

            var fields = entityType.GetFields()
                                .Where(f => f.FieldType == propertyType)
                                .Select(f => f.Name);

            var properties = entityType.GetProperties()
                                    .Where(p => p.PropertyType == propertyType)
                                    .Select(p => p.Name);

            return fields.Concat(properties);
        }
    }

Thanks.

Martin Nohejl
  • 81
  • 1
  • 4

1 Answers1

0

After reread the question. I don't know what are you trying, but here I put the idea I have what are you looking for.

public class Customer : AbstractEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }

    public ICollection<Contract> Contracts { get; set; }
}
public class Contract : AbstractEntity
{

    //what property here can be string "Peter"? Comments?
    //what are you trying?

    public int Id { get; set; }
    public DateTime From { get; set; }
    public DateTime To { get; set; }
    public string Comment { get; set; }
    public int CustomerId { get; set; }

    [ForeignKey("CustomerId")]
    public Customer Customer { get; set; }
}

public abstract class AbstractEntity
{

    //this method can be used to preselect properties you want
    protected virtual Tuple<bool, ICollection<PropertyInfo>> PropertyCollector()
    {
        return new Tuple<bool, ICollection<PropertyInfo>>(false, null);
    }

    public IEnumerable<Tuple<Type, object>> GetRowValues()
    {
        foreach (var prop in GetRows())
        {
            yield return new Tuple<Type, object>(prop.PropertyType, prop.GetValue(this));
        }
    }

    public ICollection<PropertyInfo> GetRows()
    {

        var tuple = PropertyCollector();

        ISet<PropertyInfo> pInfo;

        if (tuple.Item1)
        {
            pInfo = new HashSet<PropertyInfo>(tuple.Item2);
        }
        else //search all non virtual, private, protected properties, "following POCO scheme"
        {
            pInfo = new HashSet<PropertyInfo>();

            foreach (var prop in GetType().GetProperties())
            {
                foreach (var access in prop.GetAccessors())
                {
                    if ((!access.IsVirtual && !access.IsPrivate) && (prop.CanWrite && prop.CanRead))
                    {
                        pInfo.Add(prop);
                    }
                }
            }
        }
        return pInfo;
    }
}


public static class Searchs
{

    public static ICollection<object> ObjectsWithStringFound(ICollection<Customer> customers, string toBeFound)
    {

        var objs = new List<object>();

        foreach (var cust in customers)
        {
            var strings = cust.GetRowValues().Where(tpl => tpl.Item1 == typeof(string)).Select(tpl => tpl.Item2);
            var contracts = cust.GetRowValues().Where(tpl => tpl.Item2 is IEnumerable<Contract>).Select(tpl => tpl.Item2);

            if (strings.Any(str => str == toBeFound))
            {
                objs.Add(cust);
            }
            else if (contracts.Any(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound))
            { //What I suppose I must "match" with "Peter"??!?!
                objs.Add(contracts.First(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound));
            }

        }

        return objs;

    }
}

I think we aren't understanding each other.

DrkDeveloper
  • 939
  • 7
  • 17
  • What is the 'p'? In this row: if ((!access.IsVirtual && !access.IsPrivate) && !p.Contains(prop) && (prop.CanWrite && prop.CanRead)) – Martin Nohejl Mar 19 '20 at 02:11
  • @MartinNohejl Ups typo. pInfo, avoid repeat properties. You can use ISet<> classes. I was young and stupid. – DrkDeveloper Mar 19 '20 at 02:15
  • @MartinNohejl The important part of all of this is the last part of my answer. Search in collections inside collections – DrkDeveloper Mar 19 '20 at 02:21
  • I can see. But I don't know why but I get an error: Error CS1061 'IIncludableQueryable>' does not contain a definition for 'GetRowValues' and no accessible extension .... – Martin Nohejl Mar 19 '20 at 02:23
  • Mmm, what does intellisense say? – DrkDeveloper Mar 19 '20 at 02:24
  • @MartinNohejl Man, you have to make The entities classes child of AbstractEntity class `public class Customer : AbstractEntity`. And more, you can't do what you want that way. You can't use object methods in linq to sql queries. Get the result of the query, then search. – DrkDeveloper Mar 19 '20 at 02:25
  • I did this: `public class Customer : AbstractEntity` ite says this: `'List' does not contain a definition for 'GetRowValues' and no accessible extension method 'GetRowValues' accepting a first argument of type 'List' could be found (are you missing a using directive or an assembly reference?)` – Martin Nohejl Mar 19 '20 at 02:35
  • I think there is also error in this part of your code: `return new Tuple>(true, list); } }` because the the begenning bracket '{' is missing – Martin Nohejl Mar 19 '20 at 02:37
  • @MartinNohejl I fully edited my answer with my thoughts. Because I think you're only copying my code without trying to understand it. Like if it was a homework. – DrkDeveloper Mar 19 '20 at 02:54