Search code examples
c#.netlistcontains

Why does the "contains" method returns an element that is in the list as if it is not?


I have a Product table in my DB. Also, I have Brand and Category tables in my DB which are not related to each other. I want to relate these. In the form UI when I click the one of the Categories, should come the Brands which they have products in the related category.

I tried this way to do this. First, I get my products by categoryID with GetList method then I get these products' brands and I added these brands to pblist list(Brand type). However, some products have the same brands and pblist have repeated brand names. I tried to fix this with contains method but it does not work. Also, I have the same problem in the other part which I try to remove brands not included in pblist from blist(all brands' list). I tried removing item from blist by taking its index with this code: blist.RemoveAt(blist.IndexOf(item)); but this one also not working.It returns -1. But item is in the blist.

 public class BrandVM : BaseVM
{
    public int ProductCount { get; set; }

}

 public class BaseVM
{
    public int ID { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return this.Name;
    }

 public class BrandService : ServiceBase, IBrandService
{


    public List<BrandVM> GetList(int Count)
    {
        try
        {
            var result = GetQuery();
            result = Count > 0 ? result.Take(Count) : result;

            return result.ToList();
        }
        catch (Exception ex)
        {

            return null;
        }
    }


public List<BrandVM> GetListByCatID(int pCatID)
    {
        var plist = productService.GetListByCatID(pCatID);
        List<BrandVM> pblist = new List<BrandVM>();
        foreach (var item in plist)
        {
            if (!pblist.Contains(item.Brand))
            {
                pblist.Add(item.Brand);
            }
        };

        var blist = GetList(0);
        var blistBackup = GetList(0);

        foreach (BrandVM item in blistBackup)
        {
            if (!pblist.Contains(item))
            {
                blist.Remove(item);
            }       
        };

        return blist;
    } 

These are my classes related to Brand. In BrandService I shared the filled methods there are more methods to fill.

This is method is in my ProductService: I use that method to pull product list by CategoryID (plist)

public List<ProductVM> GetListByCatID(int EntityID)
    {
        try
        {
            var result = GetQuery().Where(x => x.Category.ID==EntityID);

            return result.ToList();
        }
        catch (Exception ex)
        {

            return null;
        }
    }

This GetQuery method for ProductService, in other services there are some differences but there are similar

  private IQueryable<ProductVM> GetQuery()
    {
        return from p in DB.Products
               select new ProductVM
               {
                   ID = p.ProductID,
                   Name = p.ProductName,
                   UnitPrice = (decimal)p.UnitPrice,
                   Category =p.CategoryID==null?null:new CategoryVM()
                   {
                       ID = (int)p.CategoryID,
                       Name = p.Category.CategoryName
                   },
                   Brand = p.BrandID == null ? null :
                   new BrandVM
                   {
                           ID=(int)p.BrandID,
                           Name=p.Brand.BrandName,

                   }


               };
    }

Solution

  • Entity framework will translate Linq queries into SQL statements, which means that Equals (and GetHashCode) will not be used for comparison of database objects. However, if you're comparing local instances of these objects, then these methods will be used for comparisons.

    The default Equals does a reference comparison to determine equality, which literally means that two instances of a type are only considered equal if they both refer to the exact same object in memory.

    Instead, we want to use the ID property for equality comparison, which means we need to override the Equals (and GetHashCode) methods for the class.

    Here's an example of how you could do this:

    public class BaseVM
    {
        public int ID { get; set; }
        public string Name { get; set; }
    
        public override string ToString()
        {
            return Name;
        }
    
        public override bool Equals(object obj)
        {
            return obj is BaseVM &&
                   ((BaseVM) obj).ID == ID;
        }
    
        public override int GetHashCode()
        {
            return ID;
        }
    }
    

    Alternatively, if you don't want to modify the class (which I would recommend since it solves this problem everywhere), you can modify your code to filter out any brands that have the same id (or name):

    foreach (var item in plist)
    {
        // Note: you could potentially use 'Name' instead of 'Id'
        if (!pblist.Any(productBrand => productBrand.Id == item.Brand.Id))
        {
            pblist.Add(item.Brand);
        }
    }