@4:30 - Not a book, but I have a "user" entity (keeping it simple), and a vote that the user made. So there can be a Dictionary, where that's a lookup of the vote made by the user (though the vote is likely more complex than a string, so would have its own object). The user has identity stuff, but it would most likely have the username as the Id, not a Guid. Probably. And then you can have a HashSet to contain a list of all users who voted as you process the votes. Is the User who made a vote in post 10 the same as the user who made a vote in post 20? If you compare by user name, then yes, so you can deprecate the old vote and replace it with the new vote. The main difference, I guess, is that I'm using some content of the entity to identify it, and not some abstract guid or whatever. It would be a bit like using the book title as the ID instead of the guid (except book titles aren't guaranteed to be unique, while usernames on a forum are). Or, going the other direction, saying that the User object needed to have an ID, which would be different for the vote made in post 10 vs the one made in post 20. But having (multiple) equality comparers built in that handle different types of comparisons is a great idea. Maybe you want to track both votes because you may need to reference either one of them as you work through the dataset, so you want to compare by username+post number when adding to the dictionary (though you only use the last post by each username when you generate the final results). So have an equality comparer for just the username (used by the HashSet), and an equality comparer for username+post (used by the Dictionary). Doesn't lend itself to an abstract base class, though. Further, you have to be able to invert the relationship, where you have a Dictionary, to figure out who voted for what, which also lends itself to equality comparers on the Vote which may need to be done in multiple different ways (such as whether or not it should be case sensitive). And in this case, a guid ID for each vote that was found would be meaningless for comparison purposes. Anyway, I think I basically agree with your point in the video, I just objected to the idea that there is no reason for those types of data structures to need some type of equality comparison. Or perhaps it's solely about having a guid as the basis for a comparison, which implicitly links it to database work. That assumption seems to carry the baggage that the ID _is_ the object, at a certain level, so if you get rid of the database association, that kind of falls away as well.
Why dictionary of user and not dictionary of user ID? I mean, that is what you are doing. You can find an excellent example of how Microsoft handled the same situation in the Entity Framework. EF uses what they call identity resolution to identify entities returned from subsequent queries as same (same, not equal!). Here is the specification: learn.microsoft.com/en-us/ef/core/change-tracking/identity-resolution
Hi Zoran. Are you using the Book example together with NHibernate or EF? If so, isn't there a problematic corner case where you could compare a Book and a BookProxy in the x.GetType() == y.GetType() part of IdEqualityComparer?
I don't use NHibernate. EF Core uses comparison on keys to determine which entities it is already tracking, so it doesn't depend on any custom comparisons. However, your note on type comparison and proxies is valid. There is a coding pattern that solves it, and you can see it in generated code for records, where the compiler adds a virtual method which returns the comparison type. It would be GetType in any class, but a proxy would only forward the call and hence the comparison type would remain unchanged, preserving the equivalence relationship. That is a very interesting solution.
@@AndersBaumann No, EF Core doesn't call any of these, not does it use proxies. When registering an entity type with EF Core, you must specify one or more properties that comprise the key. EF Core then applies its custom logic to index the entities by the keys, so there is nothing for you to do to make it work. EF Core calls that process identity resolution and it is entirely implemented inside of EF.
You misunderstand me. I am talking about this, maybe a bit contrived example, but I hope you get my point: var guid = Guid.NewGuid(); var bookId = new BookId(guid); var book = new Book(bookId); using var dbContext = new YourDbContext(); dbContext.Books.Add(book); dbContext.SaveChanges(); var theSameBook = dbContext.Books.FirstOrDefault(book => book.Id == guid); var isItTheSameBook = book == theSameBook; // -> false because book is of type Book and theSameBook is of type BookProxy.
@@AndersBaumann I see your point. You are right, EF Core is using proxies in lazy loading and in that case the comparison would fail. My solution should be augmented so that the entity exposes a virtual method, say EqualityContext, which just calls GetType. Since the process would not modify the behavior of this method, the type used in comparison would be the same in the entity and its proxy, and so the equality composter would then support proxies as well. Thank you for this analysis. I will keep it for some later video!
At first I didn't really like your videos because of how you talk 😅, but the content is so damn good, that I've watched them anyways and now I'm a fan. Keep it up!
@@zoran-horvatI don't know the english term for it. But its the variety in tone and speed that bothered me at first. It just didn't sound "right / authentic" to me. Variety is good thing. But to me it sounded like a bit too much. I guess it's just personal preference. Anyhow. The content is just great stuff!
I have opposite experience. When I first heard Zoran and his manner of speaking, to me it was like storytelling about c# - totally different from academic-kind materials :)
@@zoran-horvatHey, just another opinion about that - In my case I think the way you talk and deliver information is good for educational videos. I obviously don't know how you talk in real life 😅, but in videos that speech speed, clarity and tones greatly helps to place accents on some stuff and to catch and understand information better) P.S. Thanks for your work and great content! I always recommend my students to watch your channel(unfortunately I doubt they do it...)
@@timur2887 And so I ask what makes you read entities by key, how often do you for that and why? On top of my head I cannot remember such a case, so it must have been years ago when I did it the last time, or it was an exceptional corner case, whatever.
No, you don't. How come that database synchronization of entities is the responsibility of the entity? That doesn't make any sense. Now that I said it, I expect you to come up with a solution that better separates those two responsibilities. So, what is your solution?
@@zoran-horvat I work with dynamics and i have a service that retrieves records periodically from one dynamics environment into another environment. How can I tell that the record was already synced and therefore skipping this record? With overwriting equals on the class in combination with a record property with the attributes to compare, I have saved tons of code since dynamics itself overwrites the equals method for its own classes like optionset and entityreference. Therefore I overwrite the equals method on the class to compare the record property (and not the class) so I can use methods like ienumerable1_class.except(ienumerable2_class). A solution is to automatically push a change instead of pull but this is much worse in dynamics since it would need to execute code on every attribute.
It’s fascinating to think about how entity equality can actually undermine the core principles of Domain-Driven Design. What do you think about applying an immutable approach to entities to avoid these pitfalls altogether?
DDD is such a confusing topic. And Eric's book is fundamental, but a book which one has to read at least three times to get a grip of. Great video though, at least this one I understand clearly :) Could you recommend any good Udemy DDD course by any chance? Can't find the one you recommended there and Pluralsight is not an option for me :(
I've never done anything related to equality comparison but to compare two instance of that entity type to find out, for example, what the best "score" is (if we take game scores as an example). Being equal, greater or lower should only be applied with something that make sence. Clearly, if you ever get twice the same entity within the same comparison, the issue is a bug. Thanks for the tips on the Strongly typed ID though. I will implement that so make it more clear and avoid bugs i could cause on a friday's night lol. I like to use uint or ulong for primary keys, since why on earth would it be negative. But for some reason EFCore migration cannot infer the type convertion (even with int). I need to do something like this: public readonly record struct JobID(uint Id) { public static readonly JobID None = new(0); } } p_ModelBuilder.Entity() .Property(p_E => p_E.Id) .HasConversion( p_JobID => p_JobID.Id, p_Id => new Job.JobID(p_Id)); Any idea why? Since you never mentionned it in your past video, i thought there was never the need to do this.
I am preparing a video that explains the use of strongly typed IDs with the Entity Framework. That video will explain the need for conversion and a few other things, such as application-generated IDs, clusters indexes, nullable IDs with conversions, and similar. It will be a very detailed demo, and that is why I haven't completed it yet. Your note about uint and ulong is interesting. There is a strong case for negative IDs! It is common to set autoincrement ID to start from 1 and to let 0 indicate an unassigned value. But if the application needs to know some pre-existing objects and to guarantee that they exist, then you insert them with fixed negative IDs and keep those values in the application as named constraints. That is a typical application of Null Object and Special Case patterns, persisted variant.
@@zoran-horvat Oh nice! Yes, in my case the non defined IDs always was 0. It's the default behavior that was defined anyway. So with uint it used to make sence for me and provided more room if there was the need for that much quatity of object. But honestly, i just prefer to use types that makes sence, and negative IDs in the database itself didn't. So undefined objects just ship with 0 and will get auto incremented by the database, no issue with that i assume. But that indeed doesn't allow you to have multiple initial value for your strongly type ids, i don't see the point of it though X)
Hey Zoran, Would like to exchange an idea. What about entities that are the same and have multiple registration by software design. For example: In the Family Tree web site, different users can register the same person: I and my distant relatives have register our great-great-grandmother in the site, so there is lots of Entities that are indeed the same. The website recognizes it and suggest a merge. Great Content!
Then you model it that way. Not that is not the same as comparing the IDs of two entities. The problem you mentioned is present in authentication, too. Many systems allow merging of user accounts, but that operation is complicated, sensitive, and even dangerous from the security standpoint. No wonder developers so often flat out refuse to develop it, and let the company handle their end users by talking to them.
Zoran I just wanted to comment to say that I find your videos very useful and it would be great if you added a Patreon tier for "just saying thanks" for £2/month where you don't get anything additional, but want to show your appreciation anyway :)
I would create those Dictionary and hashset to make my colleges to drive crazy, and either to make their hair gray or loose then all 🙂 Like 10 years ago I run into this problem, I was debugging a mathematician's guy code 🙂 They could not fix in in 2 months 😀
Literally 2 seconds in and you explain strongly typed Ids like it's nothing... yet that was the first lightbulb moment for me haha.
Another excellent video highlighting a common misconception. Nice one.
@4:30 - Not a book, but I have a "user" entity (keeping it simple), and a vote that the user made. So there can be a Dictionary, where that's a lookup of the vote made by the user (though the vote is likely more complex than a string, so would have its own object). The user has identity stuff, but it would most likely have the username as the Id, not a Guid. Probably. And then you can have a HashSet to contain a list of all users who voted as you process the votes.
Is the User who made a vote in post 10 the same as the user who made a vote in post 20? If you compare by user name, then yes, so you can deprecate the old vote and replace it with the new vote. The main difference, I guess, is that I'm using some content of the entity to identify it, and not some abstract guid or whatever. It would be a bit like using the book title as the ID instead of the guid (except book titles aren't guaranteed to be unique, while usernames on a forum are). Or, going the other direction, saying that the User object needed to have an ID, which would be different for the vote made in post 10 vs the one made in post 20.
But having (multiple) equality comparers built in that handle different types of comparisons is a great idea. Maybe you want to track both votes because you may need to reference either one of them as you work through the dataset, so you want to compare by username+post number when adding to the dictionary (though you only use the last post by each username when you generate the final results). So have an equality comparer for just the username (used by the HashSet), and an equality comparer for username+post (used by the Dictionary). Doesn't lend itself to an abstract base class, though.
Further, you have to be able to invert the relationship, where you have a Dictionary, to figure out who voted for what, which also lends itself to equality comparers on the Vote which may need to be done in multiple different ways (such as whether or not it should be case sensitive). And in this case, a guid ID for each vote that was found would be meaningless for comparison purposes.
Anyway, I think I basically agree with your point in the video, I just objected to the idea that there is no reason for those types of data structures to need some type of equality comparison. Or perhaps it's solely about having a guid as the basis for a comparison, which implicitly links it to database work. That assumption seems to carry the baggage that the ID _is_ the object, at a certain level, so if you get rid of the database association, that kind of falls away as well.
Why dictionary of user and not dictionary of user ID? I mean, that is what you are doing.
You can find an excellent example of how Microsoft handled the same situation in the Entity Framework. EF uses what they call identity resolution to identify entities returned from subsequent queries as same (same, not equal!). Here is the specification:
learn.microsoft.com/en-us/ef/core/change-tracking/identity-resolution
just another masterpiece from the maestro.
Hi Zoran.
Are you using the Book example together with NHibernate or EF? If so, isn't there a problematic corner case where you could compare a Book and a BookProxy in the x.GetType() == y.GetType() part of IdEqualityComparer?
I don't use NHibernate. EF Core uses comparison on keys to determine which entities it is already tracking, so it doesn't depend on any custom comparisons.
However, your note on type comparison and proxies is valid. There is a coding pattern that solves it, and you can see it in generated code for records, where the compiler adds a virtual method which returns the comparison type. It would be GetType in any class, but a proxy would only forward the call and hence the comparison type would remain unchanged, preserving the equivalence relationship. That is a very interesting solution.
So don't you need to implement this pattern in your IdEqualityComparer for this to work in a EF Core context? Or do I misunderstand you?
@@AndersBaumann No, EF Core doesn't call any of these, not does it use proxies. When registering an entity type with EF Core, you must specify one or more properties that comprise the key. EF Core then applies its custom logic to index the entities by the keys, so there is nothing for you to do to make it work. EF Core calls that process identity resolution and it is entirely implemented inside of EF.
You misunderstand me. I am talking about this, maybe a bit contrived example, but I hope you get my point:
var guid = Guid.NewGuid();
var bookId = new BookId(guid);
var book = new Book(bookId);
using var dbContext = new YourDbContext();
dbContext.Books.Add(book);
dbContext.SaveChanges();
var theSameBook = dbContext.Books.FirstOrDefault(book => book.Id == guid);
var isItTheSameBook = book == theSameBook; // -> false because book is of type Book and theSameBook is of type BookProxy.
@@AndersBaumann I see your point. You are right, EF Core is using proxies in lazy loading and in that case the comparison would fail. My solution should be augmented so that the entity exposes a virtual method, say EqualityContext, which just calls GetType. Since the process would not modify the behavior of this method, the type used in comparison would be the same in the entity and its proxy, and so the equality composter would then support proxies as well.
Thank you for this analysis. I will keep it for some later video!
Love your videos! I don't get many opportunities to implement DDD techniques as I'm still a junior working on a legacy codebase
As a unity dev, I've been looking into Data Oriented Design, and was wondering if you could do a video on DOD.
That is a narrow topic which I don't plan to get into in the near future.
I have to say: Fantastic content! Thank you, sir and keep it up!
At first I didn't really like your videos because of how you talk 😅, but the content is so damn good, that I've watched them anyways and now I'm a fan.
Keep it up!
Hey, how do I talk? What bothered you?
@@zoran-horvatI don't know the english term for it. But its the variety in tone and speed that bothered me at first. It just didn't sound "right / authentic" to me. Variety is good thing. But to me it sounded like a bit too much. I guess it's just personal preference.
Anyhow. The content is just great stuff!
@@Menicoification Thanks!
I have opposite experience. When I first heard Zoran and his manner of speaking, to me it was like storytelling about c# - totally different from academic-kind materials :)
@@zoran-horvatHey, just another opinion about that - In my case I think the way you talk and deliver information is good for educational videos. I obviously don't know how you talk in real life 😅, but in videos that speech speed, clarity and tones greatly helps to place accents on some stuff and to catch and understand information better)
P.S.
Thanks for your work and great content!
I always recommend my students to watch your channel(unfortunately I doubt they do it...)
You might want to store entities in collections that are read optimized, no?
Define read and define optimized. Both terms are slippery in the sense that there is no single definition of them.
@@zoran-horvat I assume that this was said in defense of using dictionaries to read values by key
@@timur2887 And so I ask what makes you read entities by key, how often do you for that and why?
On top of my head I cannot remember such a case, so it must have been years ago when I did it the last time, or it was an exceptional corner case, whatever.
@@zoran-horvat perhaps there are rare cases when it is useful to lookup dictionary values as an intersection of another set of entities` keys
@@timur2887 I was asking for a case, not for guesswork. Do you have an example from your practice?
You need to override equals when syncing records form one database to another to check if source records need to be updated in the target database.
No, you don't. How come that database synchronization of entities is the responsibility of the entity? That doesn't make any sense.
Now that I said it, I expect you to come up with a solution that better separates those two responsibilities. So, what is your solution?
@@zoran-horvat I work with dynamics and i have a service that retrieves records periodically from one dynamics environment into another environment. How can I tell that the record was already synced and therefore skipping this record? With overwriting equals on the class in combination with a record property with the attributes to compare, I have saved tons of code since dynamics itself overwrites the equals method for its own classes like optionset and entityreference. Therefore I overwrite the equals method on the class to compare the record property (and not the class) so I can use methods like ienumerable1_class.except(ienumerable2_class). A solution is to automatically push a change instead of pull but this is much worse in dynamics since it would need to execute code on every attribute.
It’s fascinating to think about how entity equality can actually undermine the core principles of Domain-Driven Design. What do you think about applying an immutable approach to entities to avoid these pitfalls altogether?
DDD is such a confusing topic. And Eric's book is fundamental, but a book which one has to read at least three times to get a grip of. Great video though, at least this one I understand clearly :) Could you recommend any good Udemy DDD course by any chance? Can't find the one you recommended there and Pluralsight is not an option for me :(
@@dennym9213 I don't know any other video courses on that topic.
I've never done anything related to equality comparison but to compare two instance of that entity type to find out, for example, what the best "score" is (if we take game scores as an example). Being equal, greater or lower should only be applied with something that make sence. Clearly, if you ever get twice the same entity within the same comparison, the issue is a bug.
Thanks for the tips on the Strongly typed ID though. I will implement that so make it more clear and avoid bugs i could cause on a friday's night lol.
I like to use uint or ulong for primary keys, since why on earth would it be negative. But for some reason EFCore migration cannot infer the type convertion (even with int). I need to do something like this:
public readonly record struct JobID(uint Id)
{
public static readonly JobID None = new(0);
}
}
p_ModelBuilder.Entity()
.Property(p_E => p_E.Id)
.HasConversion(
p_JobID => p_JobID.Id,
p_Id => new Job.JobID(p_Id));
Any idea why? Since you never mentionned it in your past video, i thought there was never the need to do this.
I am preparing a video that explains the use of strongly typed IDs with the Entity Framework. That video will explain the need for conversion and a few other things, such as application-generated IDs, clusters indexes, nullable IDs with conversions, and similar. It will be a very detailed demo, and that is why I haven't completed it yet.
Your note about uint and ulong is interesting. There is a strong case for negative IDs! It is common to set autoincrement ID to start from 1 and to let 0 indicate an unassigned value. But if the application needs to know some pre-existing objects and to guarantee that they exist, then you insert them with fixed negative IDs and keep those values in the application as named constraints. That is a typical application of Null Object and Special Case patterns, persisted variant.
@@zoran-horvat Oh nice! Yes, in my case the non defined IDs always was 0. It's the default behavior that was defined anyway. So with uint it used to make sence for me and provided more room if there was the need for that much quatity of object. But honestly, i just prefer to use types that makes sence, and negative IDs in the database itself didn't.
So undefined objects just ship with 0 and will get auto incremented by the database, no issue with that i assume.
But that indeed doesn't allow you to have multiple initial value for your strongly type ids, i don't see the point of it though X)
Hey Zoran, Would like to exchange an idea.
What about entities that are the same and have multiple registration by software design. For example: In the Family Tree web site, different users can register the same person: I and my distant relatives have register our great-great-grandmother in the site, so there is lots of Entities that are indeed the same. The website recognizes it and suggest a merge.
Great Content!
Then you model it that way. Not that is not the same as comparing the IDs of two entities.
The problem you mentioned is present in authentication, too. Many systems allow merging of user accounts, but that operation is complicated, sensitive, and even dangerous from the security standpoint. No wonder developers so often flat out refuse to develop it, and let the company handle their end users by talking to them.
Zoran I just wanted to comment to say that I find your videos very useful and it would be great if you added a Patreon tier for "just saying thanks" for £2/month where you don't get anything additional, but want to show your appreciation anyway :)
Nice idea.
only thing i got from this video, some codes are useless until you know the use at given point of time.
I would create those Dictionary and hashset to make my colleges to drive crazy, and either to make their hair gray or loose then all 🙂 Like 10 years ago I run into this problem, I was debugging a mathematician's guy code 🙂 They could not fix in in 2 months 😀
🍺🍻🍺