Key comparisons for Linq GroupBy using Default EqualityComparer - linq

I'm trying to do a Linq GroupBy on some objects using an explicit key type. I'm not passing an IEqualityComparer to the GroupBy, so according to the docs:
The default equality comparer Default is used to compare keys.
It explains the EqualityComparer<T>.Default property like this:
The Default property checks whether
type T implements the
System.IEquatable<T> generic interface
and if so returns an
EqualityComparer<T> that uses that
implementation.
In the code below, I'm grouping an array of Fred objects. They have a key type called FredKey, which implements IEquatable<FredKey>.
That should be enough to make the grouping work, but the grouping is not working. In the last line below I should have 2 groups, but I don't, I just have 3 groups containing the 3 input items.
Why is the grouping not working?
class Fred
{
public string A;
public string B;
public FredKey Key
{
get { return new FredKey() { A = this.A }; }
}
}
class FredKey : IEquatable<FredKey>
{
public string A;
public bool Equals(FredKey other)
{
return A == other.A;
}
}
class Program
{
static void Main(string[] args)
{
var f = new Fred[]
{
new Fred {A = "hello", B = "frog"},
new Fred {A = "jim", B = "jog"},
new Fred {A = "hello", B = "bog"},
};
var groups = f.GroupBy(x => x.Key);
Debug.Assert(groups.Count() == 2); // <--- fails
}
}

From MSDN
If you implement IEquatable, you should also override the base class implementations of Object::Equals(Object) and GetHashCode() so that their behavior is consistent with that of the IEquatable::Equals method. If you do override Object::Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals() method return consistent results.
add this to FredKey and it should work
public override int GetHashCode()
{
return A.GetHashCode();
}

Here is a complete example with a Fiddle. Note: the example differs slightly from the question's example.
The following implementation of IEquatable can act as the TKey in GroupBy. Note that it includes both GetHashCode and Equals.
public class CustomKey : IEquatable<CustomKey>
{
public string A { get; set; }
public string B { get; set; }
public bool Equals(CustomKey other)
{
return other.A == A && other.B == B;
}
public override int GetHashCode()
{
return string.Format("{0}{1}", A, B).GetHashCode();
}
}
public class Custom
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
public static void Main()
{
var c = new Custom[]
{
new Custom {A = "hello", B = "frog" },
new Custom {A = "jim", B = "jog" },
new Custom {A = "hello", B = "frog" },
};
var groups = c.GroupBy(x => new CustomKey { A = x.A, B = x.B } );
Console.WriteLine(groups.Count() == 2);
}

Related

Serialize Dictionary<long, VALUE> to BSON documents

I want to serialize Dictionary<long, VALUE> to following JSON in MongoDB.
{
"213" : {},
"63624" : {},
...
}
I don't want other DictionaryRepresentation except DictionaryRepresentation.Document.Document
I am using MongoDB C# Driver (v2.0.1.27), and it is not smart to convert the long type key into string, which causes an exception.
You can do this with the existing serializers but it requires a small amount of configuration.
Assume the following class:
public class C
{
public int Id { get; set; }
public Dictionary<long, long> D { get; set; }
}
You can configure a custom serializer for the D property (the Dictionary) that uses a key serializer that serializes longs to strings. The code would look like this:
BsonClassMap.RegisterClassMap<C>(cm =>
{
cm.AutoMap();
var customDictionarySerializer = new DictionaryInterfaceImplementerSerializer<Dictionary<long, long>>(
dictionaryRepresentation: DictionaryRepresentation.Document,
keySerializer: new Int64Serializer(BsonType.String),
valueSerializer: BsonSerializer.SerializerRegistry.GetSerializer<long>());
cm.GetMemberMap(c => c.D).SetSerializer(customDictionarySerializer);
});
The key idea here is that even though the keys and values are both longs, we are using different serializers for the keys and the values.
If we then run a quick test:
var document = new C { Id = 1, D = new Dictionary<long, long> { { 2, 3 } } };
var json = document.ToJson();
Console.WriteLine(json);
We see that the Dictionary keys are now being serialized as strings:
{ "_id" : 1, "D" : { "2" : NumberLong(3) } }
Also I worked out another solution, hope it helps other people
public class LongDictionarySerializer<K> : DictionarySerializerBase<Dictionary<long, K>>
{
public LongDictionarySerializer() : base(DictionaryRepresentation.Document)
{
}
protected override Dictionary<long, K> CreateInstance()
{
return new Dictionary<long, K>();
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Dictionary<long, K> value)
{
if (value != null)
{
Dictionary<string, K> dic = value.ToDictionary(d => d.Key.ToString(), d => d.Value);
BsonSerializer.Serialize<Dictionary<string, K>>(context.Writer, dic);
}
else
BsonSerializer.Serialize<object>(context.Writer, null);
}
public override Dictionary<long, K> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
Dictionary<string, K> dic = BsonSerializer.Deserialize<Dictionary<string, K>>(context.Reader);
if (dic == null)
return null;
Dictionary<long, K> ret = new Dictionary<long, K>();
foreach( var pair in dic )
{
long key;
if (!long.TryParse(pair.Key, out key))
continue;
ret[key] = pair.Value;
}
return ret;
}
}
Then on the field
[BsonElement(Fields.Markets)]
[BsonSerializer(typeof(LongDictionarySerializer<XXX>))]
public Dictionary<long, XXX> Markets { get; set; }

Xamarin.Android: item previously inserted in ArrayAdapter is not found again

I've inherited this Xamarin.Android app and it has a few issues.
A particular bug involves an ArrayAdapter<ProductListObject>, where ProductListObject is a common POCO that's shared between subprojects (i.e. Android, Windows Phone and iOS); it just has a couple of properties (e.g. an Id) and overrides the (.NET) Equals() method to achieve structural equality:
public class ProductListObject
{
public long Id { get; set; }
public override bool Equals(object obj)
{
if (!(obj is ProductListObject))
{
return false;
}
return Id == (obj as ProductListObject).Id;
}
}
The problem is that whenever I put an instance of this ProductListObject in an ArrayAdapter, I can't find it again, even if they have the same Id:
var p1 = new ProductListObject { Id = 1 };
var p2 = new ProductListObject { Id = 1 };
var areEqual = p1.Equals(p2); // returns True, as expected
var productAdapter = new ArrayAdapter<ProductListObject>(this, 0, new[] { p1 });
var position = productAdapter.GetPosition(p2); // returns -1 >:(
My question is: what do I have to do to make my POCO's work with Xamarin.Android types that rely on the Java equals() method, internally (like ArrayAdapter; which delegates to List.indexOf(Object))?
What I have tried:
verified that the corresponding Java version works as expected (it does)
overrode GetHashCode() (it doesn't matter, as I expected)
googled and checked the Xamarin documentation for information about implementing Equals() (I found nothing particularly relevant)
Thanks,
Jan
I did as Matt R suggested and created a proxy that inherits from Java.Lang.Object and delegates to the actual .NET object:
public class JavaObject<TValue> : Java.Lang.Object
{
public readonly TValue Value;
internal JavaObject(TValue value)
{
Value = value;
}
public override bool Equals(Java.Lang.Object that)
{
if (!(that is JavaObject<TValue>))
{
return false;
}
return Value.Equals((that as JavaObject<TValue>).Value);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}
This doesn't tie my platform-agnostic POCO's to the Android implementation, plus it doesn't force me to lock into some rigid inheritance tree, which is always a plus.
Application is straightforward:
var p1 = new JavaObject<ProductListObject>(new ProductListObject { Id = 1 });
var p2 = new JavaObject<ProductListObject>(new ProductListObject { Id = 1 });
var areEqual = p1.Equals(p2); // returns True, as expected
var productAdapter = new ArrayAdapter<JavaObject<ProductListObject>>(this, 0, new[] { p1 });
var position = productAdapter.GetPosition(p2); // returns 0!
It looks like .NET objects get wrapped within a Java.Lang.Object when used inside a Android.Widget.ArrayAdapter. Therefore, the comparison method that's used in the productAdapter.GetPosition(...) call is actually the java Equals(Java.Lang.Object o) method for the wrapping Java.Lang.Object.
To make a ProductListObject resolve to the same index when two objects have the same Id, make ProductListObject derive from Java.Lang.Object, override the Equals(Java.Lang.Object) and forward it to the .NET Equals(System.Object) method:
public class ProductListObject : Java.Lang.Object
{
public long Id { get; set; }
public override bool Equals(object obj) // Inherited from System.Object.
{
if (!(obj is ProductListObject))
{
return false;
}
return Id == (obj as ProductListObject).Id;
}
public override bool Equals (Java.Lang.Object o) // Inherited from Java.Lang.Object.
{
return this.Equals (o as System.Object);
}
}
If you can't inherit ProductListObject from Java.Lang.Object, another option is to implement your own proxy class:
public class ProductListObject
{
public long Id { get; set; }
public override bool Equals(System.Object obj)
{
if (!(obj is ProductListObject))
{
return false;
}
return Id == (obj as ProductListObject).Id;
}
}
public class JavaProxy: Java.Lang.Object
{
public Object Object { get; private set; }
public JavaProxy(System.Object o)
{
Object = o;
}
public override bool Equals (Java.Lang.Object o)
{
var proxy = o as JavaProxy;
if (o != null) {
return Object.Equals (proxy.Object);
}
return base.Equals (o);
}
}
// ...
var productAdapter = new ArrayAdapter<JavaProxy>(this, 0, new[] { new JavaProxy(p1) });
var position = productAdapter.GetPosition(new JavaProxy(p2));
It's not as clean as the first approach but it also works.

Index (zero based) must be greater than or equal to zero and less than the size of the argument list in ef 5.0 query

I have code-first based context with following entities:
public class City : IEquatable<City>
{
public City()
{
Posts = new List<Post>();
}
public City(string cityName) : this()
{
Name = cityName;
}
public virtual ICollection<Post> Posts { get; private set; }
public int Id { get; set; }
public string Name { get; private set; }
protected string LoweredName
{
get { return Name.ToLower(CultureInfo.CurrentCulture); }
}
public override bool Equals(object obj)
{
bool equals = false;
var city = obj as City;
if (city != null)
equals = Equals(city);
return equals;
}
public override int GetHashCode()
{
int idHash = Id.GetHashCode();
int nameHash = LoweredName.GetHashCode();
var hashCode = idHash ^ nameHash;
return hashCode;
}
public bool Equals(City other)
{
return Id == other.Id && LoweredName == other.Name.ToLower(CultureInfo.CurrentCulture);
}
}
public class Post : IEquatable<Post>
{
public Post()
{
Addresses = new List<PostalAddress>();
}
public virtual ICollection<PostalAddress> Addresses { get; private set; }
public virtual City City { get; set; }
public int Id { get; set; }
public string ZipCode { get; set; }
protected string LoweredZipCode { get { return ZipCode.ToLower(CultureInfo.CurrentCulture); } }
public bool Equals(Post other)
{
return Id == other.Id && City.Equals(other.City) && LoweredZipCode == other.ZipCode.ToLower(CultureInfo.CurrentCulture);
}
}
DbContext has defined those entities in method OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new CityMap());
modelBuilder.Configurations.Add(new PostMap());
}
public class CityMap : EntityTypeConfiguration<City>
{
public CityMap()
{
// Primary Key
HasKey(t => t.Id);
// Properties
// Table & Column Mappings
ToTable("City");
Property(t => t.Id).HasColumnName("Id");
Property(t => t.Name)
.HasColumnName("Name")
.HasMaxLength(450);
}
}
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
// Primary Key
HasKey(t => t.Id);
// Properties
// Table & Column Mappings
ToTable("Post");
Property(t => t.Id)
.HasColumnName("Id");
Property(t => t.ZipCode)
.HasColumnName("ZipCode")
.HasMaxLength(450);
// Relationships
HasRequired(t => t.City)
.WithMany(t => t.Posts)
.Map(map => map.MapKey("CityId"));
}
}
I've readed some data as POCO object and inserted them into List collection
public class PostImportObject : IEquatable<PostImportObject>
{
private string _city;
private string _loweredCity;
public string City
{
get { return _city; }
set
{
_city = value.CapitalizeFirstLetter();
_loweredCity = value.ToLower(CultureInfo.CurrentCulture);
}
}
public string ZipCode
{
get { return _zipValue; }
set { _zipValue = value.ToLower(CultureInfo.CurrentCulture); }
}
protected string LoweredCity
{
get { return _loweredCity; }
}
public override bool Equals(object obj)
{
bool equals = false;
var postImport = obj as PostImportObject;
if (postImport != null)
{
equals = Equals(postImport);
}
return equals;
}
public override int GetHashCode()
{
int ziphash = ZipCode.GetHashCode();
int cityHash = LoweredCity.GetHashCode();
var hashCode = ziphash ^ cityHash;
return hashCode;
}
public bool Equals(PostImportObject other)
{
bool equals = _loweredCity == other.City.ToLower(CultureInfo.CurrentCulture) && ZipCode == other.ZipCode;
return equals;
}
}
If I query data in import list and in database too, my following queries return the same Exception:
using(var db = new DbContext())
{
var query1 = from post2 in db.Posts.Include("City")
join mergedPost in mergedPosts on new PostImportObject() {City = post2.City.Name, ZipCode = post2.ZipCode} equals new PostImportObject() {City = mergedPost.City, ZipCode = mergedPost.ZipCode} into joinedPosts
from joinedPost in joinedPosts.DefaultIfEmpty()
where joinedPosts==null
select post2;
var query2= from city1 in db.Cities
join postImportObject in mergedPosts on city1.Name equals postImportObject.City
join post1 in db.Posts on city1 equals post1.City
select post1;
}
I'll get following exception when querying Any() method of query1 or query2:
Index (zero based) must be greater than or equal to zero and less than the size of the argument list
I'm sorry that I created another topic with same subject, but I didn't find solution for my problem in other topics.
Looking at the stack trace, I'm guessing there's a problem with the translated version of the ELinq_UnsupportedConstant resource. The English version of this error message is: Unable to create a constant value of type '{0}'. Only primitive types ('{1}') are supported in this context.
I think you have two problems:
For a join, the composite key needs to be an anonymous type; you can't use your PostImportObject as the join key in query1;
You can't join a database table to a local list;
I think you'll need to use .AsEnumerable() to pull the entire list into memory before you can join to the local list:
var query = from post in context.Posts.Include(p => p.City).AsEnumerable()
join mergedPost in mergedPosts
on new { City = post.City.Name, post.ZipCode }
equals new { mergedPost.City, mergedPost.ZipCode }
into joinedPosts
from joinedPost in joinedPosts.DefaultIfEmpty()
where joinedPost == null
select post;

Access to a property with Interface cast

ActionBase, ActionA, ActionB and ActionC are Entities (from a database). ActionA, ActionB and ActionC are derived type of ActionBase.
ActionB and ActionC implements ISpecialAction with a SpecialProperty.
ex :
public interface ISpecialAction
{
Guid SpecialProperty { get; }
}
public partial class ActionBase
{
public objectX OnePropertyBase { get; set; }
}
public partial class ActionA : ActionBase
{
public objectY OnePropertyA { get; set; }
}
public partial class ActionB:ActionBase,ISpecialAction
{
public objectZ OnePropertyB { get; set; }
public Guid SpecialProperty
{
get
{
return OnePropertyB.ID;
}
}
}
public partial class ActionC : ActionBase ,ISpecialAction
{
public objectW OnePropertyC { get; set; }
public Guid SpecialProperty
{
get
{
return OnePropertyC.ID;
}
}
}
My problem is that SpecialProperty is build from other Properties of the objects (ActionB or ActionC) and when the cast (to ISpecialAction) is done, OtherProperty and OtherProperty2 are null.
I tried :
GetActionBase().ToList().Where(x=>x is ISpecialAction && ((dynamic) x).SpecialProperty== p_SpecialProperty);
GetActionBase().ToList().Where(x=>x is ISpecialAction && ((ISpecialAction) x).SpecialProperty== p_SpecialProperty);
GetActionBase().ToList().OfType<ISpecialAction>().Where(x => x.SpecialProperty== p_SpecialProperty).Cast<ActionBase>();
return GetActionOnGoing().ToList().OfType<ICityAction>().Cast<ActionBase>().Where(x => ((dynamic)x).CityId == p_CityId);
remark : OfType<> doesn't works with an Interface in Linq to entities but is ok in Linq to object
How do I access my property interface without knowing the type of the object?
I might missed something but this is Ok with the code you provided :
public class objectX
{
}
public class objectY
{
}
public class objectZ
{
public Guid ID { get { return Guid.NewGuid();} }
}
public class objectW
{
public Guid ID { get { return new Guid(); } }
}
class Program
{
private static Guid p_SpecialProperty;
static void Main(string[] args)
{
var result = GetActionBase().ToList().Where(x => x is ISpecialAction && ((dynamic)x).SpecialProperty == p_SpecialProperty).FirstOrDefault();
var result1 = GetActionBase().ToList().Where(x => x is ISpecialAction && ((ISpecialAction)x).SpecialProperty == p_SpecialProperty).FirstOrDefault();
var result2 = GetActionBase().ToList().OfType<ISpecialAction>().Where(x => x.SpecialProperty == p_SpecialProperty).Cast<ActionBase>().FirstOrDefault();
}
private static IEnumerable<ActionBase> GetActionBase()
{
return new List<ActionBase> {new ActionA{OnePropertyA= new objectY()}, new ActionB{OnePropertyB=new objectZ()},new ActionC{OnePropertyC=new objectW()} };
}
}
Not sure if I exactly understand your question, but could you try using an intermediate interface, such as:
public interface ISpecialActionB : ISpecialAction
{
objectZ OnePropertyB { get; set; }
}
public class ActionB : ActionBase, ISpecialActionB
{
//same stuff
}
and casting to that instead.
var b = new ActionB{OnePropertyB = new Whatever()};
var bAsSpecial = b as ISpecialActionB;
var whatever = b.OnePropertyB; // should not be null
It' ok.
Your example run very well without problem so I searched in a other way : AutoMapper.
l_List.Actions = Mapper.Map<List<ActionBase>, Action[]>(l_ActionManagement.GetActionBySpecialId(l_Special.ID).ToList());
The problem was not interfaces or Linq queries but it was that automapper need an empty constructor and in this constructor, I need to initialize OnePropertyB and OnePropertyC to compute SpecialProperty.
Thanks

Using ValueInjecter, is there a way to only inject a given property one time

Given 3 classes,
A, and B which each have an ID property, and then various other properties
and C, which has an ID, and the combined properties of A and B,
I want to
C.InjectFrom(A);
C.InjectFrom(B);
such that the ID from A is preserved and not overwritten by B.
Obviously in this simple case, I could just reverse the order of the two calls, but in my real world example, it is slightly more complicated where I cannot just solve the problem with ordering.
Esentially I want the second injection to ignore anything that the first injection has already handled, and this may be continued down a chain of several injections. Some of these injections may be from the same objects too
C.InjectFrom(A);
C.InjectFrom<SomeInjector>(A);
C.InjectFrom<SomeInjector2>(A);
C.InjectFrom<SomeInjector3>(A);
etc.
here you go:
using System;
using System.Collections.Generic;
using Omu.ValueInjecter;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var a = new { Id = 1, P1 = "p1" };
var b = new { Id = 2, P2 = "p2" };
var c = new C();
var propList = new List<string>();
c.InjectFrom(new HandlePropOnce(propList), a);
c.InjectFrom(new HandlePropOnce(propList), b);
Console.WriteLine("Id = {0} P1 = {1} P2 = {2}", c.Id, c.P1, c.P2);
}
}
public class C
{
public int Id { get; set; }
public string P1 { get; set; }
public string P2 { get; set; }
}
public class HandlePropOnce : ConventionInjection
{
private readonly IList<string> handledProps;
public HandlePropOnce(IList<string> handledProps)
{
this.handledProps = handledProps;
}
protected override bool Match(ConventionInfo c)
{
if (handledProps.Contains(c.SourceProp.Name)) return false;
var isMatch = c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Type == c.TargetProp.Type;
if (isMatch) handledProps.Add(c.SourceProp.Name);
return isMatch;
}
}
}

Resources