I believe the solution to this is actually rather straightforward:
As you mention, entities must have an identity,
Per your (perfectly valid) requirements, the identity of your entities is assigned centrally by the DBMS,
Hence, any object that hasn't yet been assigned an identity is not an entity.
What you're dealing with here is a kind of Data Transfer Object type, which doesn't have an identity. You should think about it as transferring data from whatever input system you use to the domain model via the repository (which you need as an interface here for the identity assignment). I suggest you create another type for these objects (one that doesn't have a key), and pass it to the Add/Create/Insert/New method of your repository.
When the data doesn't need much preprocessing (i.e. doesn't need to be passed around much), some people even omit DTOs and pass the various pieces of data via the method arguments directly. This is really how you should be looking at such DTOs: as convenient argument objects. Again, notice the lack of a "key" or "id" argument.
If you need to manipulate the object as an entity before inserting it in the database, then DBMS sequences are your only option. Note that this is usually relatively rare, the only reason you might need to do this is if the results of these manipulations end up modifying the object state such that you'd have to make a second request to update it in the database, which you'd surely prefer to avoid.
Very often, "creation" and "modification" functionality in applications are distinct enough that you'll always add the records for the entities in the database first before retrieving them again later to modify them.
You'll undoubtedly be worried about code-reuse. Depending on how you construct your objects, you'll probably want to factor-out some validation logic so that the repository can validate the data before inserting it into the database. Note that this is usually unnecessary if you're using DBMS sequences, and might be a reason why some people systematically use them even if they don't strictly need them. Depending on your performance requirements, take the comments above into consideration, as a sequence will generate an additional round trip that you'll often be able to avoid.
- Example: Create a validator object that you use in both the entity and the repository.
Disclaimer: I don't have in-depth knowledge of canonical DDD, I wouldn't know if this was really the recommended approach, but it makes sense to me.
I'll also add that in my opinion, changing the behavior of Equals
(and other methods) based on whether the object represents an entity or a simple data object is simply not ideal. With the technique you use, you also need to ensure that the default value you use for the key is properly excluded from the value domain in all domain logic.
If you still want to use that technique, I suggest using a dedicated type for the key. This type would box/wrap the key with additional state indicating whether or not the key exists. Note that this definition resembles Nullable<T>
so much that I'd consider using it (you can use the type?
syntax in C#). With this design, it's clearer that you allow the object not to have an identity (null key). It should also be more obvious why the design is not ideal (again, in my opinion): You're using the same type to represent both entities and identity-less data transfer objects.