Вопрос

I've solved a problem I was having but although I've found out how something works (or doesn't) I'm not clear on why.

As I'm the type of person who likes to know the "why" I'm hoping someone can explain:

I have list of items and associated comments, and I wanted to differentiate between admin comments and user comments, so I tried the following code:

User commentUser = userRepository.GetUserById(comment.userId);
Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");
if(commentUser.Roles.Contains(commentUserRole)
 {
   //do stuff
 }
else
{
 // do other stuff
}

Stepping through the code showed that although it had the correct Role object, it didn't recognise the role in the commentUser.Roles

The code that eventually worked is:

if(commentUser.Roles.Any(x=>x.Name == "admin"))
{
  //do stuff
}

I'm happy with this because it's less code and in my opinion cleaner, but I don't understand how contains didn't work.

Hoping someone can clear that up for me.

Это было полезно?

Решение

This is probably because you didn't override the equality comparisons (Equals, GetHashCode, operator==) on your Role class. Therefore, it was doing reference comparison, which really isn't the best idea, as if they're not the same object, it makes it think it's a different. You need to override those equality operators to provide value equality.

Другие советы

You have to override Equals (and always also GetHashCode then) if you want to use Contains. Otherwise Equals will just compare references.

So for example:

public class Role
{
    public string RoleName{ get; set; }
    public int RoleID{ get; set; }
    // ...

    public override bool Equals(object obj)
    {
        Role r2 = obj as Role;
        if (r2 == null) return false;
        return RoleID == r2.RoleID;
    }
    public override int GetHashCode()
    {
        return RoleID;
    }
    public override string ToString()
    {
        return RoleName;
    } 
}

Another option is to implement a custom IEqualityComparer<Role> for the overload of Enumerable.Contains:

public class RoleComparer : IEqualityComparer<Role>
{
    public bool Equals(Role x, Role y)
    {
        return x.RoleID.Equals(y.RoleID);
    }

    public int GetHashCode(Role obj)
    {
        return obj.RoleID;
    }
}

Use it in this way:

var comparer = new RoleComparer();
User commentUser = userRepository.GetUserById(comment.userId);
Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");
if(commentUser.Roles.Contains(commentUserRole, comparer))
{
    // ...
}

When using the Contains-method, you check if the the array Roles of the user-object contains the object you have retrieved from the database beforehand. Though the array contains an object for the role "admin" it does not contain the exact object you fetched before.

When using the Any-method you check if there is any role having the name "admin" - and that delivers the expected result.

To get the same result with the Contains-method implement the IEquatable<Role>-interface on the role-class and compare the name to check whether two instances have actually the same value.

It will be your equality comparison for a Role.

The object in commentUserRole is not the same object as the one you are looking for commentUser.Roles.

Your context object will create a new object when you select from it and populate your Roles property with a collection of new Roles. If your context is not tracking the objects in order to return the same object when a second copy is requested then it will be a different object even though all the properties may be the same. Hence the failure of Contains

Your Any clause is explicitly checking the Name property which is why it works

Try making Role implement IEquatable<Role>

public class Role : IEquatable<Role> {
  public bool Equals(Role compare) {
    return compare != null && this.Name == compare.Name;
  }
}

Whilst MSDN shows you only need this for a List<T> you may actually need to override Equals and GetHashCode to make this work

in which case:

public class Role : IEquatable<Role> {
  public bool Equals(Role compare) {
    return compare != null && this.Name == compare.Name;
  }

  public override bool Equals(object compare) {
     return this.Equals(compare as Role); // this will call the above equals method
  }

  public override int GetHashCode() {
     return this.Name == null ? 0 : this.Name.GetHashCode();
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top