Is it safe to implement GetHashCode by returning an unique ID from the object? - gethashcode

Let's say I have a POCO that represents a database row. I have access to the ID of that row.
Is it safe to implement GetHashCode and Equals by leveraging that unique ID ?
public class Project
{
public string ID { get; }
public string Name { get; set; }
public override bool Equals(object obj)
{
var pr = obj as Project;
if (pr == null) return false;
if (pr.ID == null) throw new InvalidOperationException("Attempt to .Equals an entity with null ID");
return pr.ID == ID;
}
public override int GetHashCode()
{
if (ID == null) throw new InvalidOperationException("Attempt to .GetHashCode on an entity with null ID");
return ID.GetHashCode();
}
}
I believe this could work because
ID is not mutable
I handle the case where ID is null
Any thoughts on that ?

Related

EF Core Updating Entity Property with JSON Type

I have a 1 to many relationship of Parent and Child to store some data. I want to store this data in PostgreSQL using Npgsql.EntityFrameworkCore.PostgreSQL package. Parent maps to a table, but Child is stored as a json column of Parent table in the database.
public class Parent
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
When I try to add a Child entity to an existing Parent instance with the code below, SaveChangesAsync doesn't produce an update command on the database.
var child = new Child(){Id = 0, Name = "Name"};
var parent = await DataContext.Parent.SingleOrDefaultAsync(f => f.Id == 1);
parent.Children.Add(child);
await DataContext.SaveChangesAsync();
In order to trigger an update command, I have to set parent entity's State to Modified before calling SaveChangesAsync.
var entry = DataContext.Entry<Parent>(parent);
entry.State = EntityState.Modified;
Is this the expected behavior or am I missing something?
Update:
As #SvyatoslavDanyliv suggested, instead of using ICollection when I use a class EqualityCollection derived from List and override Object.Equals as follows :
public class EqualityCollection<T> : List<T>
{
public override bool Equals(object? obj)
{
if (obj != null && obj.GetType() == typeof(EqualityCollection<T>))
return this.Equals(obj as EqualityCollection<T>);
return false;
}
public bool Equals(EqualityCollection<T> obj)
{
return this.SequenceEqual(obj ?? throw new InvalidOperationException());
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
Change detection doesn't detect the change in the property and update command is not triggered.
You have to define ValueComparer when defining conversion via HasConversion as described in documentation: Value Comparers

EF 4.4 Preventing AutoGen Navigation Properties and/or Relationships

I started a db schema using EF and ran into multiple issues when tring to mannually modify the CLR's and/or db tables. First was a "Employee_ID" column that EF placed in a table. I deleted it, the dbo.EdmMetaData and the dbo.__MigrationHistory tables and fumbled through the run-time errors that insued. Now, I'm grapling with the following error:
A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'EmployeeID'.
My implementation uses a TimeCardEntity CLR that has 3 computed columns. These columns just so happens to map to another table's Primary Key. This other table is EmployeeRecord.
GOAL) I don't WANT EF to auto map thse 3 columns. I intend to fill them myself due to the complications EF offers, but I can't tell EF to stop creating navigation relationships and/or referential constraints.
Point #1) I have a EmployeeRecord table that has a Guid ID primary key, it maps to CLR class EmployeeRecord
Point #2) I have a TimeCardEntity table that has has 3 computed columns called EmployeeID, ManagerID, DivisionManagerID that relate back to EmployeeRecord. All are NULL declared but EmployeeID is required, obviously, because you can't have a time card without declaring the employee. The ManagerID and DivisionManagerID get filled later.
Point #3) Please don't ask me "Why are these computed?", because there is a reason. I alos feel it is illrelevant to the issue. In short, computed EmployeeID's (whether employee, manager or division mananger), are stored in an xml property with the data of approval and signature of employee - which provides non reputiation.
Point #4) I have 3 stored functions called fxGetEmployeeID(xml), fxGetManagerID(xml), and getDivisonManagerID(xml). Each of these are used in the computed columns EmployeeID, ManagerID and DivisionManagerID respectively.
Here is the class declarations simplified for brevity:
public enum TimeCardEmployeeTypeEnum {
Employee,
Manager,
DivisionManager
}
[DataContract]
[Serializable]
[Table("EmployeeRecord", Schema = "TimeCard")]
public class EmployeeRecord {
#region Exposed Propert(y|ies)
[DataMember]
public Guid ID { get; set; }
/// <summary>
/// Customers internal company employee ID. Can be null, SSN, last 4, or what ever...
/// I included it just in case it was part of my pains...
/// </summary>
[CustomValidation(typeof(ModelValidator), "EmployeeRecord_EmployeeID", ErrorMessage = "Employee ID is not valid.")]
public string EmployeeID { get; set; }
#endregion
}
[DataContract]
[Serializable]
[Table("TimeCardEntry", Schema = "TimeCard")]
public class TimeCardEntry {
#region Member Field(s)
[NonSerialized]
XDocument m_TimeEntries;
#endregion
#region Con/Destructor(s)
public TimeCardEntry() {
this.m_TimeEntries = "<root />".ToXDocument();
}
public TimeCardEntry(Guid employeeID) {
if (employeeID == Guid.Empty)
throw new ArgumentNullException("employeeID");
this.m_TimeEntries = "<root />".ToXDocument();
this.EmployeeID = employeeID;
}
#endregion
#region Exposed Propert(y|ies)
[NotMapped]
[IgnoreDataMember]
public XDocument TimeEntries {
get {
if (this.m_TimeEntries == null) {
if (!string.IsNullOrEmpty(this.TimeEntriesXml))
this.m_TimeEntries = this.TimeEntriesXml.ToXDocument();
}
return this.m_TimeEntries;
}
set {
this.m_TimeEntries = value;
if (this.m_TimeEntries != null)
this.TimeEntriesXml = this.m_TimeEntries.ToString();
else
this.TimeEntriesXml = null;
this.OnPropertyChanged("TimeEntriesXml");
this.OnPropertyChanged("TimeEntries");
}
}
[DataMember]
[EditorBrowsable(EditorBrowsableState.Never)]
[Required]
public string TimeEntriesXml {
get {
if (this.m_TimeEntries == null)
return null;
return this.m_TimeEntries.ToString();
}
set {
this.m_TimeEntries = value.ToXDocument();
this.OnPropertyChanged("TimeEntriesXml");
this.OnPropertyChanged("TimeEntries");
}
}
[IgnoreDataMember]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
public Guid? EmployeeID {
get {
var attribute = this.m_TimeEntries.Root.Attribute("EmployeeID");
if (attribute != null)
return (Guid)attribute;
return null;
}
set {
if (this.ValidateSignature(TimeCardEmployeeTypeEnum.Manager))
throw new ArgumentException("Property cannot be changed once the manager signature has been set.", "EmployeeID");
if (value != null && value.Value != Guid.Empty)
this.m_TimeEntries.Root.SetAttributeValue("EmployeeID", value);
else {
var attribute = this.m_TimeEntries.Root.Attribute("EmployeeID");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("EmployeeID");
}
}
public virtual EmployeeRecord Employee { get; set; }
[NotMapped]
[IgnoreDataMember]
public DateTime? EmployeeApprovalDate {
get {
var attribute = this.m_TimeEntries.Root.Attribute("EmployeeApprovalDate");
if (attribute != null)
return (DateTime)attribute;
return null;
}
set {
if (this.ValidateSignature(TimeCardEmployeeTypeEnum.Manager))
throw new ArgumentException("Property cannot be changed once the manager signature has been set.", "EmployeeApprovalDate");
if (value.HasValue)
this.m_TimeEntries.Root.SetAttributeValue("EmployeeApprovalDate", value);
else {
var attribute = this.m_TimeEntries.Root.Attribute("EmployeeApprovalDate");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("EmployeeApprovalDate");
}
}
[NotMapped]
[IgnoreDataMember]
public byte[] EmployeeSignature {
get {
var attribute = this.m_TimeEntries.Root.Attribute("EmployeeSignature");
if (attribute != null)
return Convert.FromBase64String((string)attribute);
return null;
}
set {
if (this.ValidateSignature(TimeCardEmployeeTypeEnum.Manager))
throw new ArgumentException("Property cannot be changed once the manager signature has been set.", "EmployeeSignature");
if (value != null) {
if (value.Length > 1024)
throw new ArgumentException("Signature cannot be larger than 1KB.", "EmployeeSignature");
this.m_TimeEntries.Root.SetAttributeValue("EmployeeSignature", Convert.ToBase64String(value));
} else {
var attribute = this.m_TimeEntries.Root.Attribute("EmployeeApprovalDate");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("EmployeeSignature");
}
}
[IgnoreDataMember]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
public Guid? ManagerID {
get {
var attribute = this.m_TimeEntries.Root.Attribute("ManagerID");
if (attribute != null)
return (Guid)attribute;
return null;
}
set {
if (this.ValidateSignature(TimeCardEmployeeTypeEnum.DivisionManager))
throw new ArgumentException("Property cannot be changed once the division manager signature has been set.", "ManagerID");
if (value.HasValue) {
if (value.Value == Guid.Empty)
throw new ArgumentNullException("ManagerID");
this.m_TimeEntries.Root.SetAttributeValue("ManagerID", value);
} else {
var attribute = this.m_TimeEntries.Root.Attribute("ManagerID");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("ManagerID");
}
}
public virtual EmployeeRecord Manager { get; set; }
[NotMapped]
[IgnoreDataMember]
public DateTime? ManagerApprovalDate {
get {
var attribute = this.m_TimeEntries.Root.Attribute("ManagerApprovalDate");
if (attribute != null)
return (DateTime)attribute;
return null;
}
set {
if (this.ValidateSignature(TimeCardEmployeeTypeEnum.DivisionManager))
throw new ArgumentException("Property cannot be changed once the division manager signature has been set.", "ManagerApprovalDate");
if (value.HasValue)
this.m_TimeEntries.Root.SetAttributeValue("ManagerApprovalDate", value);
else {
var attribute = this.m_TimeEntries.Root.Attribute("ManagerApprovalDate");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("ManagerApprovalDate");
}
}
[NotMapped]
[IgnoreDataMember]
public byte[] ManagerSignature {
get {
var attribute = this.m_TimeEntries.Root.Attribute("ManagerSignature");
if (attribute != null)
return Convert.FromBase64String((string)attribute);
return null;
}
set {
if (this.ValidateSignature(TimeCardEmployeeTypeEnum.DivisionManager))
throw new ArgumentException("Property cannot be changed once the division manager signature has been set.", "ManagerSignature");
if (value != null) {
if (value.Length > 1024)
throw new ArgumentException("Signature cannot be larger than 1KB.", "ManagerSignature");
this.m_TimeEntries.Root.SetAttributeValue("ManagerSignature", Convert.ToBase64String(value));
} else {
var attribute = this.m_TimeEntries.Root.Attribute("ManagerSignature");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("ManagerSignature");
}
}
[IgnoreDataMember]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
public Guid? DivisionManagerID {
get {
var attribute = this.m_TimeEntries.Root.Attribute("DivisionManagerID");
if (attribute != null)
return (Guid)attribute;
return null;
}
set {
if (value.HasValue) {
if (value.Value == Guid.Empty)
throw new ArgumentNullException("DivisionManagerID");
this.m_TimeEntries.Root.SetAttributeValue("DivisionManagerID", value);
} else {
var attribute = this.m_TimeEntries.Root.Attribute("DivisionManagerID");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("DivisionManagerID");
}
}
public virtual EmployeeRecord DivisionManager { get; set; }
[NotMapped]
[IgnoreDataMember]
public DateTime? DivisionManagerApprovalDate {
get {
var attribute = this.m_TimeEntries.Root.Attribute("DivisionManagerApprovalDate");
if (attribute != null)
return (DateTime)attribute;
return null;
}
set {
if (value.HasValue)
this.m_TimeEntries.Root.SetAttributeValue("DivisionManagerApprovalDate", value);
else {
var attribute = this.m_TimeEntries.Root.Attribute("DivisionManagerApprovalDate");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("DivisionManagerApprovalDate");
}
}
[NotMapped]
[IgnoreDataMember]
public byte[] DivisionManagerSignature {
get {
var attribute = this.m_TimeEntries.Root.Attribute("DivisionManagerSignature");
if (attribute != null)
return Convert.FromBase64String((string)attribute);
return null;
}
set {
if (value != null) {
if (value.Length > 1024)
throw new ArgumentException("Signature cannot be larger than 1KB.", "DivisionManagerSignature");
this.m_TimeEntries.Root.SetAttributeValue("DivisionManagerSignature", Convert.ToBase64String(value));
} else {
var attribute = this.m_TimeEntries.Root.Attribute("DivisionManagerSignature");
if (attribute != null)
attribute.Remove();
}
this.OnPropertyChanged("DivisionManagerSignature");
}
}
#endregion
}
This is the db context declaration
public sealed class DatabaseContext : DbContext {
public DatabaseContext(bool autoDetectChangesEnabled = false, bool lazyLoadingEnabled = false, bool proxyCreationEnabled = false, bool validateOnSaveEnabled = false) {
this.Configuration.AutoDetectChangesEnabled = autoDetectChangesEnabled;
this.Configuration.LazyLoadingEnabled = lazyLoadingEnabled;
this.Configuration.ProxyCreationEnabled = proxyCreationEnabled;
this.Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled;
}
public DbSet<EmployeeRecord> EmployeeRecords { get; set; }
public DbSet<TimeCardEntry> TimeCards { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<System.Data.Entity.Infrastructure.IncludeMetadataConvention>();
}
}
UPDATE
I have to add another observed behavior of EF. When I add the "NotMappedAttribute" to the EmployeeID column of TimeCardEntry, I get another issue. The EF addes a "Employee_ID" column back to the auto-gen schema. See the TSQL Profile Trace below:
exec sp_executesql N'SELECT
[Limit1].[C1] AS [C1],
[Limit1].[ID] AS [ID],
[Limit1].[TimeEntriesXml] AS [TimeEntriesXml],
[Limit1].[ManagerID] AS [ManagerID],
[Limit1].[DivisionManagerID] AS [DivisionManagerID],
[Limit1].[CreatedBy] AS [CreatedBy],
[Limit1].[Created] AS [Created],
[Limit1].[UpdatedBy] AS [UpdatedBy],
[Limit1].[Updated] AS [Updated],
[Limit1].[Employee_ID] AS [Employee_ID]
FROM ( SELECT TOP (2)
[Extent1].[ID] AS [ID],
[Extent1].[TimeEntriesXml] AS [TimeEntriesXml],
[Extent1].[ManagerID] AS [ManagerID],
[Extent1].[DivisionManagerID] AS [DivisionManagerID],
[Extent1].[CreatedBy] AS [CreatedBy],
[Extent1].[Created] AS [Created],
[Extent1].[UpdatedBy] AS [UpdatedBy],
[Extent1].[Updated] AS [Updated],
[Extent1].[Employee_ID] AS [Employee_ID],
1 AS [C1]
FROM [TimeCard].[TimeCardEntry] AS [Extent1]
WHERE [Extent1].[ID] = #p0
) AS [Limit1]',N'#p0 uniqueidentifier',#p0='10F3E723-4E12-48CD-8750-5922A1E42AA3'
EF is trying to declare Employee_ID in the database because it needs column for foreign key to Employee table. It cannot use your EmployeeID property and its columns as foreign key because it is declared as computed - foreign keys in EF must not be declared as computed or identity (it is not supported).
Solution for your model either requires abandoning navigation properties and work with IDs only (and loading related employees manually) or abandoning those computed columns - I can imagine that both options may be quite annoying.

How do I patch enumerables with System.Web.Http.OData.Delta?

Trying to make use of System.Web.Http.OData.Delta to implement PATCH methods in ASP.NET Web API services, but it seems unable to apply changes to properties of type IEnumerable<T>. I'm using the latest Git revision of Delta (2012.2-rc-76-g8a73abe). Has anyone been able to make this work?
Consider this data type, which it should be possible to update in a PATCH request to the Web API service:
public class Person
{
HashSet<int> _friends = new HashSet<int>();
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<int> Friends
{
get { return _friends; }
set
{
_friends = value != null ? new HashSet<int>(value) : new HashSet<int>();
}
}
public Person(int id, string firstName, string lastName)
{
Id = id;
FirstName = firstName;
LastName = lastName;
}
public Person()
{
}
}
This Web API method implements patching of a Person through Delta<Person>:
public void Patch(int id, Delta<Person> delta)
{
var person = _persons.Single(p => p.Id == id);
delta.Patch(person);
}
If I send a PATCH request with the following JSON to the service, the person's Friends property should be updated, but alas it doesn't happen:
{"Friends": [1]}
The crux of the matter is really how to make Delta update Friends with this data. See also the discussion at CodePlex.
The problem likely is that Deta will try to assign JSON's JArray to your Hashset<int>
If you are using it against JsonMEdiaTypeFormatter and you internalized the Delta code (meaning you can modify it), you'd have to do something like this (this is rough, but works):
Inside, bool TrySetPropertyValue(string name, object value) of Delta<T>, where it returns false:
if (value != null && !cacheHit.Property.PropertyType.IsPrimitive && !isGuid && !cacheHit.Property.PropertyType.IsAssignableFrom(value.GetType()))
{
return false;
}
Change to:
var valueType = value.GetType();
var propertyType = cacheHit.Property.PropertyType;
if (value != null && !propertyType.IsPrimitive && !propertyType.IsAssignableFrom(valueType))
{
var array = value as JArray;
if (array == null)
return false;
var underlyingType = propertyType.GetGenericArguments().FirstOrDefault() ??
propertyType.GetElementType();
if (underlyingType == typeof(string))
{
var a = array.ToObject<IEnumerable<string>>();
value = Activator.CreateInstance(propertyType, a);
}
else if (underlyingType == typeof(int))
{
var a = array.ToObject<IEnumerable<int>>();
value = Activator.CreateInstance(propertyType, a);
}
else
return false;
}
This will only work with collections of int or string but hopefully nudges you into a good direction.
For example, now your model can have:
public class Team {
public HashSet<string> PlayerIds { get; set; }
public List<int> CoachIds { get; set; }
}
And you'd be able to successfully update them.
You could override the TrySetPropertyValue method of the Delta class and make use of JArray class:
public sealed class DeltaWithCollectionsSupport<T> : Delta<T> where T : class
{
public override bool TrySetPropertyValue(string name, object value)
{
var propertyInfo = typeof(T).GetProperty(name);
return propertyInfo != null && value is JArray array
? base.TrySetPropertyValue(name, array.ToObject(propertyInfo.PropertyType))
: base.TrySetPropertyValue(name, value);
}
}
If you are using the ODataMediaTypeFormatter, this should be working. There are a couple of caveats to mention though.
1) your collections have to be settable.
2) the entire collection is replaced. you cannot remove/add individual elements.
Also, there is an issue tracking item 1 - '670 -Delta should support non-settable collections.'

Implementing GetHashCode on a value class

I have a class Money and I want to know what the best way of implementing GetHashCode on this value class would be give that $1 != €1. Having a weighted value against the currency * value is not going to work.
public class Money : System.IEquatable<Money>
{
public Money(Currency c, decimal val)
{
this.Currency = c;
this.Value = val;
}
public Currency Currency
{
get;
protected set;
}
public decimal Value
{
get;
protected set;
}
public override bool Equals(object obj)
{
Money m = obj as Money;
if (m == null){throw new System.ArgumentNullException("m");}
if(m.Currency.Id == this.Currency.Id)
{
if(m.Value == this.Value)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
public override int GetHashCode()
{
// What would be the best way of implementing this as €1 != $1
// Currency object contains 2 members, (int) Id and (string) symbol
}
}
As the Currency.Id seems unique, provided it's a non-zero integer I'd go with
public override int GetHashCode()
{
unchecked
{
return (Currency.Id*397) ^ Value.GetHashCode();
}
}
Would Currency.Id be a not-empty string or a Guid, the following would do the trick
public override int GetHashCode()
{
unchecked
{
return (Currency.Id.GetHashCode()*397) ^ Value.GetHashCode();
}
}

Equals and GetHashCode

What do you think about this Person class? Is it a bad idea or best practise to override Equals and GetHashCode like that?
public class Person {
public int PersonId { get; set; }
public string Name { get; set; }
public override bool Equals(object obj) {
var person = obj as Person;
return PersonId == person.PersonId;
}
public override int GetHashCode() {
return PersonId;
}
}
Usage :
static void Main(string[] args) {
var list = new List<Person>();
list.Add(new Person(){ PersonId = 1, Name = "Mike"});
list.Add(new Person() { PersonId = 2, Name = "Michael Sync" });
list.Add(new Person(){ PersonId = 1, Name = "Mike"});
var list1 = new List<Person>();
list1.Add(new Person() { PersonId = 1, Name = "Mike" });
list1.Add(new Person() { PersonId = 3, Name = "Julia" });
var except = list.Except(list1);
foreach (var item in except) {
Console.WriteLine(item.Name);
}
Console.ReadKey();
}
A few points:
It's not null safe or "different type" safe. Try this:
new Person().Equals(new Object());
or
new Person().Equals(null);
Bang.
Classes defining equality operations should usually be immutable IMO. Changing the contents of an object after using it as a dictionary key is a Bad Thing, for example.
Consider implementing IEquatable<Person>
A quick reimplementation, which still assumes you want equality based solely on ID.
public sealed class Person : IEquatable<Person> {
private readonly int personId;
public int PersonId { get { return personId; }
private readonly string name;
public string Name { get { return name; } }
public Person(int personId, string name) {
// Is a null name valid? If not, throw here.
this.personId = personId;
this.name = name;
}
public override bool Equals(object obj) {
return Equals(obj as Person);
}
public Equals(Person other) {
return other != null && other.personId == personId;
}
public override int GetHashCode() {
return personId;
}
}
Yes this is wrong. You should never use a mutable property as part of the calculation for GetHashCode. Doing so opens you up to numerous hard to track down bugs.
One problem I can see is that you'll get lots of collisions for new (unsaved) records (lots of zeros) - unless you do something like have consecutive -ve ids for those... But do you really need to use Person as a key? Personally I don't think I'd bother...