none
Bug when GetHashCode overrided while using EntityCollection RRS feed

  • Question

  • We are using entities with identity columns for PK. We override the GetHashCode and Equals on the child entity to compare them by id (instead of reference).

    But, when we use the ParentEntity.ChildEntityCollection.Contains (or any other operation using the gethashcode, like Parent.ChildEntityCollection.Remove), we don't have the expected behavior. The contains can't find the child entity. Let's check this exemple (it will better show the problem):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace TestBugGetHashCode
    {
      class Program
      {
       static void Main(string[] args)
       {
         // Clean-up the store
         using (MyEntities context = new MyEntities())
         {
          foreach (var tmp in context.Results)
            context.DeleteObject(tmp);
          context.SaveChanges();
         }
    
         // Create a new entity with a child (with auto-increment columns)
         Result Result = new Result();
         Detail detail = new Detail();
    
         using (MyEntities context = new MyEntities())
         {
          context.AddToResults(Result);
          Result.Details.Add(detail);
          context.SaveChanges();
         }
    
         // Bug if GetHashCode is overrided
         bool exist = Result.Details.Any(d => d.Equals(detail));
         bool contains = Result.Details.Contains(detail);
         if (exist != contains)
          throw new InvalidOperationException("Bug?, contains is different than exist");
    
         // Bug if GetHashCode is not overrided (must comment override Detail.GetHashCode to reproduce)
         HashSet<Detail> details = new HashSet<Detail>();
         using (MyEntities context = new MyEntities())
         {
          details.Add (context.Details.Single());
         } 
         Detail detailRead2;
         using (MyEntities context = new MyEntities())
         {
          // Could have got detailRead2 from caching or from deserialization
          detailRead2 = context.Details.Single();
         }
         if (!details.Contains(detailRead2))
          throw new InvalidOperationException("Bug because details and detailRead2 is not the same instance, but with same id. (GetHashCode depend on instance by default)");
       }
      }
    
      partial class Detail : IEquatable<Detail>
      {
       public override int GetHashCode()
       {
         return Id.GetHashCode();
       }
    
       public override bool Equals(object obj)
       {
         return Equals(obj as Detail);
       }
    
       public bool Equals(Detail other)
       {
         if (other == null)
          return false;
         if (Id == 0)
          return base.Equals(other);
         return Id.Equals(other.Id);
       }
      }
    }
    
    

    With this entity schema :

    <?xml version="1.0" encoding="utf-8"?>
    <edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
     <!-- EF Runtime content -->
     <edmx:Runtime>
      <!-- SSDL content -->
      <edmx:StorageModels>
      <Schema Namespace="MyModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
     <EntityContainer Name="MyModelStoreContainer">
      <EntitySet Name="Details" EntityType="MyModel.Store.Details" store:Type="Tables" Schema="dbo" />
      <EntitySet Name="Results" EntityType="MyModel.Store.Results" store:Type="Tables" Schema="dbo" />
      <AssociationSet Name="FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" Association="MyModel.Store.FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat">
       <End Role="Resultat" EntitySet="Results" />
       <End Role="DetailResultat" EntitySet="Details" />
      </AssociationSet>
     </EntityContainer>
     <EntityType Name="Details">
      <Key>
       <PropertyRef Name="Id" />
      </Key>
      <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
      <Property Name="Result_Id" Type="int" Nullable="false" />
     </EntityType>
     <EntityType Name="Results">
      <Key>
       <PropertyRef Name="Id" />
      </Key>
      <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
     </EntityType>
     <Association Name="FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat">
      <End Role="Resultat" Type="MyModel.Store.Results" Multiplicity="1">
       <OnDelete Action="Cascade" />
      </End>
      <End Role="DetailResultat" Type="MyModel.Store.Details" Multiplicity="*" />
      <ReferentialConstraint>
       <Principal Role="Resultat">
        <PropertyRef Name="Id" />
       </Principal>
       <Dependent Role="DetailResultat">
        <PropertyRef Name="Result_Id" />
       </Dependent>
      </ReferentialConstraint>
     </Association>
    </Schema></edmx:StorageModels>
      <!-- CSDL content -->
      <edmx:ConceptualModels>
       <Schema Namespace="MyModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
        <EntityContainer Name="MyEntities" annotation:LazyLoadingEnabled="false">
         <EntitySet Name="Details" EntityType="MyModel.Detail" />
         <EntitySet Name="Results" EntityType="MyModel.Result" />
         <AssociationSet Name="FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" Association="MyModel.FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat">
          <End Role="Resultat" EntitySet="Results" />
          <End Role="DetailResultat" EntitySet="Details" />
         </AssociationSet>
         </EntityContainer>
        <EntityType Name="Detail">
         <Key>
          <PropertyRef Name="Id" />
         </Key>
         <Property Name="Id" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
         <NavigationProperty Name="Result" Relationship="MyModel.FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" FromRole="DetailResultat" ToRole="Resultat" />
        </EntityType>
        <EntityType Name="Result">
         <Key>
          <PropertyRef Name="Id" />
         </Key>
         <Property Name="Id" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
         <NavigationProperty Name="Details" Relationship="MyModel.FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" FromRole="Resultat" ToRole="DetailResultat" />
        </EntityType>
        <Association Name="FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat">
         <End Role="Resultat" Type="MyModel.Result" Multiplicity="1">
          <OnDelete Action="Cascade" />
         </End>
         <End Role="DetailResultat" Type="MyModel.Detail" Multiplicity="*" />
        </Association>
       </Schema>
      </edmx:ConceptualModels>
      <!-- C-S mapping content -->
      <edmx:Mappings>
      <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
     <EntityContainerMapping StorageEntityContainer="MyModelStoreContainer" CdmEntityContainer="MyEntities">
      <EntitySetMapping Name="Details">
       <EntityTypeMapping TypeName="IsTypeOf(MyModel.Detail)">
        <MappingFragment StoreEntitySet="Details">
         <ScalarProperty Name="Id" ColumnName="Id" />
        </MappingFragment>
       </EntityTypeMapping>
      </EntitySetMapping>
      <EntitySetMapping Name="Results">
       <EntityTypeMapping TypeName="IsTypeOf(MyModel.Result)">
        <MappingFragment StoreEntitySet="Results">
         <ScalarProperty Name="Id" ColumnName="Id" />
        </MappingFragment>
       </EntityTypeMapping>
      </EntitySetMapping>
      <AssociationSetMapping Name="FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" TypeName="MyModel.FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" StoreEntitySet="Details">
       <EndProperty Name="Resultat">
        <ScalarProperty Name="Id" ColumnName="Result_Id" />
       </EndProperty>
       <EndProperty Name="DetailResultat">
        <ScalarProperty Name="Id" ColumnName="Id" />
       </EndProperty>
      </AssociationSetMapping>
     </EntityContainerMapping>
    </Mapping></edmx:Mappings>
     </edmx:Runtime>
     <!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
     <Designer xmlns="http://schemas.microsoft.com/ado/2008/10/edmx">
      <Connection>
       <DesignerInfoPropertySet>
        <DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />
       </DesignerInfoPropertySet>
      </Connection>
      <Options>
       <DesignerInfoPropertySet>
        <DesignerProperty Name="ValidateOnBuild" Value="true" />
        <DesignerProperty Name="EnablePluralization" Value="False" />
        <DesignerProperty Name="IncludeForeignKeysInModel" Value="True" />
       </DesignerInfoPropertySet>
      </Options>
      <!-- Diagram content (shape and connector positions) -->
      <Diagrams>
       <Diagram Name="Model1">
        <EntityTypeShape EntityType="MyModel.Detail" Width="1.5" PointX="3.875" PointY="4" Height="1.4033821614583335" IsExpanded="true" />
        <EntityTypeShape EntityType="MyModel.Result" Width="1.5" PointX="3.875" PointY="1.5" Height="1.4033821614583335" IsExpanded="true" />
        <AssociationConnector Association="MyModel.FK_TestApp_DetailResultat_IdResultat_TestApp_Resultat" ManuallyRouted="false">
         <ConnectorPoint PointX="4.625" PointY="2.9033821614583335" />
         <ConnectorPoint PointX="4.625" PointY="4" />
        </AssociationConnector>
       </Diagram>
      </Diagrams>
     </Designer>
    </edmx:Edmx>
    

    So is there any workaround? Or what I'm doing wrong?

    Tuesday, November 30, 2010 7:34 PM

All replies

  • Please try the following code snippet:

    for

    bool exist = Result.Details.Any(d => d.Equals(detail));

    use

    bool exist = Result.Details.Any(d => d.ID.Equals(detail.ID));

    as for

    bool contains = Result.Details.Contains(detail);

    please try:

    bool contains = false;
    foreach(var v in Result.Details.AsEnumerable())
    {
        if(v.ID == detail.ID)
            contains = true;
    }


    Welcome to Microsoft All-In-One Code Framework to download or request code samples from Microsoft Community Team!
    Tuesday, December 7, 2010 6:09 AM
  • Yes I know if I use the Equals directly instead of the EntityCollection methods, I have the right values. But the problem is I don't wan't to do it manually. The real problem is when I use the EntityCollection.Remove method. It call the EntityCollection.ContainsEntity (which do the same thing than EntityCollection.Contains), and I have no control on the call. So the Remove just doesn't work. In reallity, It's this code I want to work :

     using (MyEntities context = new MyEntities ())
         {
          // Create a new entity with a child (with auto-increment columns)
          Result result = new Result ();
          Detail detail = new Detail ();
    
          context.AddToResults (result);
          result.Details.Add (detail);
          context.SaveChanges ();      
    
          // Check the number of details
          int beforeRemoveDetailsCount = context.Details.Count ();
    
          // Delete the entity
          result.Details.Remove (detail);
          context.SaveChanges ();
    
          // The number of details should have drop by one.
          int afterRemoveDetailsCount = context.Details.Count ();
          if (afterRemoveDetailsCount == beforeRemoveDetailsCount)
          {
            throw new InvalidOperationException ("Bug : Didn't remove the detail from store");
          }
         }
    

    So I was talking about EntityCollection.Contains because after some investigations, the problem seems to come from the EntityCollection.Contains which doesn't give the right value.

    Tuesday, December 7, 2010 2:16 PM
  • Is the core of the problem, the Any method respond that the element is in collection but if I try Remove method the Any still repond that the element is in collection. How can I remove the element from the collection? 
    Monday, December 20, 2010 6:50 PM
  • We still waiting for a response...!

    How can use the Remove method for a item inserted into the collection. After the select method is ok but otherwise?

    The problem seem to be the Remove method use an HashCode to find the element, but when I insert it into the collection his hashcode numer return 0. When the Remove method ask for the hashcode, the element has now new number (because his EntityKey have now a value). Result Remove method didn't find the element into container returned by the hashcode because is still in the container 0.

    Tuesday, January 11, 2011 1:30 PM