Imagine there is an Order aggregate, with two entities: Order, and OrderLine. The Order entity is the Aggregate root. Generally, your application will add, remove and edit OrderLines from an order during its lifecycle.
According to DDD (and general best practices), entities other than the aggregate root should not be directly referenced from outside the aggregate. That means that the OrderLine identifier should always be used in the context of an Order. In practical terms, that means that you should always pass both the OrderId and the OrderLineId, if you want to do something with an OrderLine. The OrderId is used to locate the Aggregate (Order), which is then asked to perform whatever operation on and OrderLine, identified with the OrderLineId.
There are roughly three options for choosing identifiers: random, hash or sequential. The advantage of random and hash identifiers is that the client can generate the identifier, and reference an instance without the need to wait for the result of the creation command.
Never use identifiers generated by the query database to identify instances of entities. Since these identifiers are not part of the command model, you will end up with events that you cannot replay. Whatever you do, make sure the Aggregate knows about the identifiers used.
If you found yourself ending up with a list of entities without their own identifier (for example because you never anticipated a requirement to edit exiting items), you're going to need to generate identifiers for the past events. You're probably best off with a hash based identifier for those entities. Calculate the hash based on immutable information in the entity. When you add the identifier to the event, you can create an upcaster that builds the ID based on information available inside the event.