I have some entities in use in my project, and to make things easier, I would like to have the type of the key for that entity defined via a generic. E.g.:
public abstract class Entity<T>
{
public virtual T Id { get; set; }
}
This way, I can pull the Id field into the base entity and not have to define it every time (There's a few other fields on Entity in the real system, so there is other utility to doing this)
Then, since I'm using Fluent NHibernate, I make a generic mapping class to go along with this generic entity:
internal abstract class EntityMapping<T,TK> : ClassMap<T> where T : Entity<TK>
{
protected EntityMapping()
{
Id(m => m.Id);
}
}
And starting here is when I wonder if there's a better way of handling this. C# doesn't seem to infer the TK
type parameter from the Entity
definition, and having to add to all of my mapping classes the key parameter for the entity they map seems redundant, all the more so since it also extends to some of the higher generic data access classes, e.g.:
public abstract class EntityRepository<T,TK> where T : Entity<TK> { }
Is there a tidier way of handling this, or is this what I have to do if I want to have the key type of my entities passed in?
-
3\$\begingroup\$ I think there is no tidier way, as long as you want to keep everything generic. \$\endgroup\$svick– svick2013年01月30日 10:28:45 +00:00Commented Jan 30, 2013 at 10:28
-
\$\begingroup\$ That's what I was thinking was probably the case. Worth asking though. \$\endgroup\$Matt Sieker– Matt Sieker2013年01月30日 17:53:23 +00:00Commented Jan 30, 2013 at 17:53
-
\$\begingroup\$ I would suggest that you use marker interfaces for generic type constraints (ex. IEntity, IEntity<TId> : IEntity, IRepository, IRepository<T> : IRepository where T: class, IEntity, new()) . A base entity class as shown below should work fine as well, but use marker interfaces instead for constraints to allow using your base class or a custom class. \$\endgroup\$Shelakel– Shelakel2013年02月10日 17:19:10 +00:00Commented Feb 10, 2013 at 17:19
2 Answers 2
There is no way to simplify generic parameters if you want to keep Entity class generic, as noted by @svick.
The only thing I can suggest as alternative here is to remove generic parameter from Entity
. I don't think you need 10 different types for Id
field, most likely you'll have int Id
or Guid Id
, so you can create
public interface IEntity<T>
{
T Id { get; set; }
}
public abstract class IntEntity: IEntity<int>
{
public virtual int Id { get; set; }
}
public abstract class GuidEntity: IEntity<Guid>
{
public virtual Guid Id { get; set; }
}
Not sure if you need IEntity<T>
interface, added it so that you don't loose the possibility to reference both types of entities in generic manner.
Yes, you'll have to declare separate IntEntityRepository and GuidEntityRepository, but I don't think it's that hard given that you can extract common code to base class, and it's a one-time job.
As to mappings - I don't use class hierarchy for them (declaring all the fields on the actual entity mapper), but you can do the same trick as with repositories.
-
\$\begingroup\$ What about a composite key ? \$\endgroup\$Imanez– Imanez2018年10月28日 20:30:27 +00:00Commented Oct 28, 2018 at 20:30
-
\$\begingroup\$ I don't think it's reasonable to create generic base classes for entities with composite keys. In case of a single key, it's ok to assume (or define a convention) that key's name would be "Id". I don't think you would have such a convention for generic composite keys... \$\endgroup\$almaz– almaz2018年10月30日 12:13:42 +00:00Commented Oct 30, 2018 at 12:13
This is quite similar to something I'm doing. If I may give a tiny bit of advice for your Entity
class, it would be to override/define some of the standard object
operations as such:
public abstract class Entity<T> where T: IEquatable<T>
{
protected Entity(T id) => this.Id = id;
public virtual T Id { get; }
public static bool operator ==(Entity<T> entity1, Entity<T> entity2) =>
ReferenceEquals(entity1, entity2) || (((object)entity1 != null) && entity1.Equals(entity2));
public static bool operator !=(Entity<T> entity1, Entity<T> entity2) => !(entity1 == entity2);
public override bool Equals(object obj)
{
Entity<T> entity = obj as Entity<T>;
return (entity != null) && (entity.GetType() == this.GetType()) && entity.Id.Equals(this.Id);
}
public override int GetHashCode() => this.Id?.GetHashCode() ?? 0;
public override string ToString() => this.Id?.ToString() ?? string.Empty;
}
-
1\$\begingroup\$ I had
Equals
andGetHashCode
overridden on the actual class (I just pulled out the relevant properties for this question), soIComparer
/IEquatable
would work. The operator overloading is a good idea though. \$\endgroup\$Matt Sieker– Matt Sieker2013年02月01日 16:47:33 +00:00Commented Feb 1, 2013 at 16:47 -
\$\begingroup\$ @MattSieker rock on, sir! \$\endgroup\$Jesse C. Slicer– Jesse C. Slicer2013年02月01日 16:52:41 +00:00Commented Feb 1, 2013 at 16:52
-
1\$\begingroup\$ @JesseC.Slicer code does not work... missing Entity Base class or must be of type Entity<T> \$\endgroup\$Beachwalker– Beachwalker2015年10月25日 16:07:45 +00:00Commented Oct 25, 2015 at 16:07
-
\$\begingroup\$ @Beachwalker it worked 2 1/2 years ago. \$\endgroup\$Jesse C. Slicer– Jesse C. Slicer2015年10月25日 16:17:43 +00:00Commented Oct 25, 2015 at 16:17