.NET Data Community Standup - Complex types as value objects in EF8

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
>> Entity framework friendly. Azure Cosmos DB. >> Count down commencing. T minus two minutes. [MUSIC] >> 10, 9, 8, 7, 6, 5, 4, 3, 2, 1. Lift off. We are live for.NET Data Community Standup. >> We're back. It's been a long few months where we haven't had one of these. I would say we took a summer break to have fun, but it was more, took a summer break to do other things, including trying to get EF Core 8 and.NET 8 out the door which has been quite challenging. Anyway, we think we have something we can ship. It's got a few limitations, but we're back here now and we're going to talk a little bit about some of the stuff today. As is always the case for these things, we're completely unprepared. I made a mistake of decided to have a nap this afternoon and then didn't wake up, and so I was going to prepare all this a lot better, including the state of the unicorn, but I didn't, so it is what it is. Anyway. Most of the time we're just going to spend walking through code and talking about complex types. Hopefully that will be interesting and educational. Anyway, without a lot of prep. Without any further ado. Then let us do the state of the unicorn, which is very hastily cobble together today and apparently has nothing on it that will animate. Good to start there. What I was going to say was this EF Core 8 is out now let's see if we find it. >> Wow also good your showmanship is impending. >> Here we go. EF Core 8. It's not out now, I missed RC1. EF8 RC1 is out now. It's on you get. It basically feature complete, meaning it has all of the features that we're going to ship in it. It does have some missing bits and quite a few bugs in it, Yes. Sure. Try LC1, it's better than nothing, but it's actually really easy to use the daily builds. All you need to do is find a file. Put this file, a NuGet.config. Drop it down in the same directory as your application, and put in the path, it still says.NET 7 here, put in the path for.NET 8. All of this is on the EF Core repo,.NET EF repo front page. There's a link that says daily builds that tells you how to do this. It's literally copy this file, put it in this place, go to, you get packages and select the latest daily build of EF Core, and then you'll get all of the bug fixes and everything that we've done since we shipped or we built RC1. This is also going to be true for RC2. What I'm going to use today is actually the daily build rather than the RC1 release, just because it works generally better and got some bug fixes in it. >> What you're mentioning, so some people get cold feet when they hear the word daily build. There's no reason to be stressed, especially now the daily builds basically represent a more stable version compared to RC1. All we've done since RC1 came out was to fix bugs and stabilize, so there's nothing experimental or untested, or unsure about the daily builds quite at the current library. They really do approximate at this point. They are basically the same thing that's going to be released as RC2 in a few weeks. You're really better off. RC1 I think is actually very usable as well even though there's bugs. But why would you want to use less current and less stable version? >> Exactly. What we're going to talk about specifically today is complex types and complex types used as value objects. What do I mean by that? Firstly, what we're talking about here are types with structure, so types that have multiple properties that you actually want to map to the database with multiple- >> We'll come to Jason later. But if we're not using Jason, then you want to map them to multiple columns. We're not talking about a simple property with a simple value, in which case it just is a simple property, maybe with a value converter that goes to a single column in the database, and conceptually on both sides is just a single value. What we're talking about here are types that have a structure. Like typically an address, for example, where it has two lines, a postcode, a city, country, whatever. It's got structure inside and you want that structure to be mapped to the database. >> But Arthur, we already have entity types, which are types that have multiple properties, which get mapped to multiple columns, so what's new here? >> The difference between entity types, including owned entity types, and complex types, is that entity types have a defined identity, a defined key. You typically have a property called ID, or something else, or maybe two properties, but those properties define that object's identity. Whereas in a complex type, it doesn't have any identity. It doesn't have any identity other than the thing that it is. Let's take an address, for example. If I change the city in an address, that's not the same address in a different city, that doesn't make any sense, it's a different address now. It's entirely different address and that's what I mean. It's like you change some part of the address and it's a different address. But if Shay gives me an address with all the same elements, the same street number, same house, same zip code, or whatever, everything exactly the same as mine, those are the same address. It wouldn't make any sense most of the time. Now we'll come back to when this does to give this one an ID of 10 and this one an ID of 13, even though they're the same. Basically, that's what we're talking about here. >> Does it make sense to make the comparison with our.Net structs and classes now just to drive the point home? >> We will come back to that, but that is an important thing because that very much ties in to all of these concepts. >> Conceptually, I'm going to say two words about that because I think it might help drive this point home. In.Net as most of you probably know. You've got a class, a class is a reference type, and so on. When you have a class in your code in C Sharp, now we're not in the database anymore, we're just in C Sharp code. So when you have a class, it's a reference type. Which means that if you have a variable, that is like a class, that means it actually points to something. The class has a location, it has a pointer, it has a reference somewhere in memory and you can have multiple variables pointing to the same place in memory. We can say that the identity in the case of.Net objects or classes is basically the reference. There's something that identifies that thing and you can refer to it. You can reference it from another class. You can have pointers and so on. Whereas structs or value types in.Net do not have that thing in general. They can be embedded in a class, for example, and they can be on the stack. Of course, they are somewhere in memory, everything is somewhere in memory. That's always the case, but conceptually, structs don't have an identity. This is also why, by default, if you compare two classes to each other, you're going to get true only if they refer to the same thing. We don't look at the contents of what's inside the class by default, what we do by default is reference comparison, whereas with structs in.Net, if you compare those two things for equality, what you'll get by default, once again, is a member wise comparison. Only if the address field is the same as here and there in the city, and so on and so forth. I gloss over a lot there I think. >> It's a good analogy, but bear in mind that you can use.Net reference types and.Net value types, both for complex types, both for these types that don't have any identity in F. We'll get to that. >> Right. >> Let's look at some code, stop talking, and just look at some code because we've given an overview. What we're going to do is we're actually going to take address and we're going to model address into a small entity model. I have an entity model here, customer orders, and it's, why is that flashing though? Is that supposed to be doing that in presentation mode now? >> The cursor? >> Up here, there's dots flashing across the top here. Anyway. >> Oh, yeah, I see. Yeah, that's really weird. >> Anyway, okay, so we have a customer in order and we're going to put an address into this model. Let's get our address here. Immediately we can see that a customer, put in order both have an ID. If we go back to our analogy, if the customer calls and says, I've got married and my name's changed, that doesn't make them a new customer. We can mutate this and give it a new name and they still retain the identity of being that same customer, and that's another way of conceptualizing this stuff. Where if somebody says, I've moved to a new address, well it's a new address, it's not the same address. Now so we're going to give, as we just said, a customer an address. We're also going to say when a customer makes an order, we want to put two addresses into the order. We're going to put in a shipping address and a billing address. We've now got three addresses in the model. Let's go and get our context type. I may have already prepared from earlier. Let me see here, explorer. A simple DB context type for customers and orders. Now we want to map these things as complex types. If I run this thing right now, it's going to tell me you can't do that because we don't ever detect complex types by convention. The reason being is that from a convention perspective, we can't determine the difference between this and an entity type other than the fact it doesn't have a key that we can find, oh it finished, interesting. >> The whole time. >> This is a great comedy stand up. >> What was I saying? We can't distinguish it, by convention, from an entity type, other than the fact that it doesn't have a key, and not having a property that we can find as a key is a common error. If we were suddenly to discover all these as complex types, it would become very confusing because you'd never get any warning out of it. We chose to make it so that you have to discover them by convention, although it appears that doesn't work because it was supposed to crash and it didn't, which I'm now very interested in understanding but I probably should just move on and put in the configuration here. Actually it didn't run right. Oh, you know why? >> Why? >> It just says appear console right line. >> Of course. >> I wasn't going to run it initially, that's not in my script. See, I'm going off script. Now I'll run it and it will give an error. >> That looks better. >> It's saying, here if I can get it, requires a prior entity type address. It's discovered as a dress like I was describing and saying it right private. We probably should change that actually. We should let that message, could you make a note of that, Shay to update message to say it might be a complex type? >> Sure. >> You can configure them as you might expect inside your dB context our model creating, and it's going to look like this. You do entity, and then you say that entity has a complex property and you put it to address. Complex property think of it as property but for complex types. Likewise, we can do two calls for it in order. Now when we run this, we should see it creating a table for the customer in the order. Let's look at what's in those tables. You can see that what we've done by default, and this is the same as what happens for own types by default, we've split out the complex type into columns in the entity types, main table. City county, and so far have a column. Where we have two of the same type. This one is prefixed by the property name, in this case it's also prefixed by the property name, and that distinguishes the billing address from the shipping address. Of course, we could choose not to do this when it's only one but then if you add another one, it causes a migration to change these names and everything so we just be consistent from the beginning and name them like this. We want to make it possible to map these two complex type columns like you can with own types as well, but that's a limitation in EF core eight. For now, they only map to additional columns in the main entity type table. There is an attribute, we'll get to that in a second. There's a comment here. Yes, we're going to look at that. You can not own types, you can split off into another table so you don't have to have their own type share a table with its parent entity type that's not something we plan to support for complex types because we'd end up with a table with rows that had no identification in them, so it doesn't really work. >> Should we discuss a little bit complex versus own types and what's the difference? >> We'll get to it. Coming back to the question, if we don't want to put all of this in here, we can just go to customer and put complex type attribute on that and that will produce the same thing. >> Not on the customer though I think. >> Not on the customer, you're right. That won't produce the same thing at all. That will produce a crash as we see down below. Two things about the complex type attribute. This is the old complex type attributes in data annotations, namespace. There's a bug. I believe it's not in the daily build but it's still in even RC2, I think, where if you apply this to a nested complex type it doesn't work. For nested complex types until GA, you'll have to use the fluent API. Also, you can't put this on a struct because in our wisdom when we created this thing back in 2012, 11, whenever we said, well, it's only ever going to go on an F6 complex type which could not be a struct. Currently, we're not planning to do anything about that. If you think we should, there is an issue you can vote for. There is finally one other way. If you don't want to use the attribute, but you don't want to configure every property usage as a complex type, then you can override configure conventions, this is where you do both configuration before the EF conventions of run. You can say complex properties address. In that case, anytime we find a user address, it will be used as a complex property. If we run that again, we should see that that now creates the same tables as we had to start with. Indeed it does. Any questions we should answer Shay? >> There is a question here, why is the attribute on the class level? Which I wrote an answer to but you probably can give a better one. >> I don't know, we created complex type attribute a long time ago before EF 41 was released. Old EF 41. This was like 2012 or something like that. This was before that, 2010. >> My answer would be so typically, when you have a.net type, and you want to use it as a complex type in one place, you probably you want to use it as a complex type in another place as well. Like imagine this address type. It's very contrived to say I want address to be a complex to be treated as a complex type here, but not there. There I wanted to have an ID, like be an actual entity type with its own table. That's very strange in general. That's not how the attribute works. We just made it much simpler. I think it's the right thing, that you slap it on the type once and then you can use that type anywhere and it's going to be recognized as a complex type. I think makes a lot of sense. >> It's interesting. I'm wondering whether I could map it as a different type in other places where I probably could but I'm not going to for now anyway. We have our model with complex types. Let's switch it back to this just because it's a little cleaner about later. Let's save a customer with an address. We'll create a database and we're going to create a customer with a name, and it's got an address, so we're just creating a new address. We add that customer, we call say changes. There should be nothing particularly unusual there. Indeed, we can see that we're inserting into customers with the address. Let's say we now want to create an order. We've got a customer and we're going to create an order from that customer. Now, we'll forget about everything here associated with this other than the stuff around the address. Well soon I can just create a new order. When we create the order, I'm going to copy the customer address into the billing address and the shipping address by default. Obviously, we would let the customer change those things, say ship to my grandma, for example, and bill to my mother or whatever it is, but by default, we'll copy the customer's address into those things and we will save changes. Let's see what happens here when we do that. You can see that we now have insert into the orders with the shipping address values and the customer address values copied from the original customer. At this point, you probably could have been saying, we could have done all of this with own types. Actually that last step there would have failed with own types. It'll be interesting to see if you've read the novel, I'm not going to say that because this is in the blog post. Has anybody read the blog post or the watch new and can say why this fails with own types? Anyway, it does fail with own types. Read the blog post or the watch new to find out why. In a nutshell, it's because you can't share the same complex type instance between three different usage of it. That's because they're entity types and they have identity. What you're saying is this complex type here, this has the same identity as all of these three other places when you're doing own types. Now you could give them all different ID's, and you could change the ID or generate a new ID when you copied it over and that will be fine. You could model address as an entity type and then share that address in multiple places and we would track the references like Shy was saying before because it's a C# reference type and everything, and it would be fine. But you have to give it identity if you're going to do that. If you don't give it identity, then it's just an object with values. We don't care whether it's a copy or not when it's a complex type but with an own type, we definitely do care whether it's the same instance or not. That's one of the big difference between complex type. There's a lot of other things like this which are nuanced and which appear to be small semantic differences but in reality have a big impact on the experience you have when you're trying to use these scenes because it just doesn't meet the principle of expectation where it behaves like you expect it to to given the semantics you think it should have. We're hoping that complex types have a lot more closer semantics to what you expect for this kind of object. >> It's maybe worth mentioning so obviously we introduced on types a long time ago relatively speaking, and complex types are new way. There's quite a bit of overlap and we expect a lot of people to get a bit confused. Which one should I use and why and so on. Which is fair enough and we need to document this a little bit and explain it. It's a little bit subtle. But it is worth saying, I think the way I see it in my head, I hope Arthur agrees. We've had a lot of discussions about this in the team. Basically, if what you want to do is represent something that's inside your entity, it is inside, it's contained inside your entity, then that is exactly what a complex type is for. We made, I think I can say this, we made a mistake basically by saying we're going to take the owned entity concept, which has its uses, and we're going to apply that, we're going to use that for this containment idea. Containment by the way, you can think about it in many ways, like Arthur just showed one type of containment, which is to flatten that thing out into your table so you basically add more columns into your table, prefixed by the name. That's one way of something being contained. It's not somewhere else and we're pointing to it via a foreign key. There's no identity, so there's no foreign key. It really is inside that thing, inside that same role. Another conceptual way to contain something is via J-SON. You can have a single column, doesn't even matter if it's J-SON or not. You have a column in the database and it somehow contains that complex thing inside it. It could be J-SON inside it, It could be some other format, It could be [inaudible] conceptually, even though nobody likes to do that anymore. All these concepts of containment, basically, this is what we're targeting here with this new feature called complex types. It doesn't have an independent existence at all. It is really inside that JSON document. It is inside that table, whatever you want to say. Up to now, a recommendation. The way to do this with EF Core has been to use own types and that is not the ideal way. There are various, as Arthur said, there are various mismatches there. It doesn't work the right way because conceptually, own entity types still have the idea of an identity and an ID behind the scenes, even if you don't necessarily see it. Yeah, that was the thing I wanted to say. >> Absolutely. Let's go and look at what happens when we try and change these things and use that to look at different kinds of objects that can be used as complex types. By different kinds of objects, I'm going back to the different kinds of CSharp.net objects, value types and reference types and then we'll also look at records as part of that. The first thing I'm going to do is make what looks like an innocuous change. Now, conceptually you might be thinking this doesn't change, nobody just moves down the street and then updates just the line one address or whatever. That's fine, you normally would create a complete new instance of an address when somebody gives you address, they'll type in all the bits and you'll create a new instance, you wouldn't mutate certain parts of it. But for the sake of demoing the different kinds of objects and how to behave, we're going to be mutating here and then show how not to. I'm going to change the address line one which was Barking Gate and will now be Peacock Lodge. Then I'm going to call say, changes. This is tracked, should detect the change and save it to the database. We indeed do do that, but we actually update customers address line 1, orders billing address line 1, and shipping address line 1, all to Peacock Lodge. Which may certainly not be what we intended here. When we copied the shipping address and the billing address, that was a starting point for people to edit. We weren't expecting that if then the customer says, oh, I've now moved, that we should then update the address automatically. We might want to do that, and that would be a different scenario. That's when using address as an entity type might make more sense because you want to keep track of that particular address as identity. But in this case, when we change line 1, we don't want them all to change but, address is a reference type. When I did billing address, customer address, and shipping addresses, customer address, I wasn't actually making copies of these things. I was just assigning a reference to that complex type in the customer address, to these things which is fine because EF doesn't care about its identity, which is different from own types like we talked about. and it will save it, but it's problematic if you then mutate it and you're sharing it. Now, this isn't really an EF problem as such. This is really a problem with mutable types and reference types in general. >> I think Shay I would agree with this, the best way to deal with this when you're using complex types, because of the semantics and their nature, is to make them immutable. Let's do that. We're going to do it in a really trivial way first and then we're going to look a record and show how records make it look like nicer. I'm going to change the address type here. We don't need that anymore. What I've done here is actually let me just do it in line so you can see, I'm just going to change all the setters init. Init can only be set when the object is being created. This is now immutable. It's not immutable if you use reflection, you can mutate it with reflection, but if you're using the C-Sharp language, this is immutable. Once I've created an instance of address, I can't change any of its properties. All of its properties are immutable too, which is important for immutability remember, because if I could mutate a string here, then I could mutate this, but I can't. This whole thing is now immutable. That of course means that I can't do this anymore. I can't now say, mutate that line because that's what we just did. I can't change it. What I can do is do this. Conceptually I can create a copy of it. I'll create a new address. I'll put in all the values from the old address as they were before but with just this line change now I've set a customer to a new address instance with these lines change. Notice here that I'm still sharing the instance where I can. It's fine to share an instance because it's now immutable, so that instance will never change where it's been shared to. It's never going to have a different value and if it wants to change somewhere, it has to create a new instance as leaving the other ones fine. That's the real like advantage of your immutable types here. There's other advantage in other scenarios, but in terms of what we're talking about here, that's the advantage. When I run this, it should only update the address on the customer and not on the order now. You can see it does. Interesting thing here is notice that we actually set all the values and a completely new object. This is a new instance of address. But the EF change tracker still understands the structure of this and it still detected changes between these two things and said the only thing that's actually changed that the database cares about is Line 1. All I'm going to do is send an update for Line 1 and change it and leave the rest the same. You are now using these immutable objects in your entity types, but the change tracker is still doing fully optimized partial updates to the database based on what's actually changed. Now some of you hopefully will be yelling, use a record for disks and that is indeed what we are going to do. C-sharp record is a class, in this case you can have a struct as well, but basically with additional semantics over it. It's not really anything particularly special. From a conceptual standpoint, I have an address and I've given it a record type. I can be explicit about this, call it class record or is it record class? I don't know. Record class I can be explicit about that now to imply that this definitely is a reference type, but I don't need that. It's got a constructor that takes all the values and then it's got the property getters and setters like we had before, but it's conceptually very similar. Now the thing is we can actually make this into a really concise syntax with primary constructors. If I say convert this thing to primary scripts, we get this single line of code which defines an address object. This is exactly the same. When I just did that "Alt" enter there and refactored it. I didn't change the semantics of this at all. This has all of those properties. It's got that constructor, it also has a deconstruct as well, but that's not super important for this. We can now use that in our types. Now we have to start calling the constructor so we're going to need to change our calling code here. Let me just do that real quick. We'll call that null for line 2, city, country, and the postcode. We have to use the constructor here. But rather than using the constructor and doing all this manually, we actually have this really nice simple syntax called with, which means that we don't have to do all this manually. We can have the compiler basically generate this for us. >> Now this is getting really nice. Everybody pay attention. >> Now when I want to update line 1, But this is immutable, I can just say the customer address but with this one property or multiple properties, you can add multiple properties in here but with this property changed and it's doing the same thing that we were doing with the class under the covers. It's extracting the values out of the existing instance and then it's calling the primary constructor on the new instance to create a new instance and setting it. But it's just all nicely sugared underneath so that all you have to do, do that. We should be able to run that will work the same as the class example and we don't have a sharing issue anymore. I have to say, I don't know if you agree with me on this, but I would say record types are the best fit for complex types. If you're doing complex types, then pretty much use a record type for it. It's pretty much perfect fit. >> I'm sure there are scenarios where a class is a better fit. But I think, by default it's definitely a good option to start with. I have to say again, because I like to speak and say a lot of long things. But this question of EF and immutability has existed, I think since EF has existed and we have a big issue where people tell us, please support immutability better and records have appeared at some point and people have asked how do I use records with EF Core? And the truth is that there wasn't necessarily a good story for that and by the way, we still have not resolved this completely. This is definitely one step. It's one area where suddenly records fit extremely well in the EF ecosystem. It's really nice. I personally absolutely love what Arthur just showed, where you can just,use records like this and everything works precisely as it should. Everything aligns conceptually like complex types and records and so on. That works really nicely. We still, of course, need to think about how to do immutability outside of complex types. For example, some people want entities to be immutable as well. Especially if you're coming, say, from F sharp, from a more functional background than you want this. That's a whole different problem, which we don't have a good answer for right now because it involves change tracking. How do you change track something that's immutable in one that's difficult. But I love that in EF eight, we could at least do this pretty major step I think for stuff that's contained in matching that as an immutable record thing. Which is I think super cool. I'm really looking forward to seeing people doing stuff with this. >> Let's talk about struct now, I was talking earlier about C-Sharp, reference types and value types. With records we can create a record struct. I guess it's a struct record record. >> I just added the keyword struct in there, but everything else is the same. If I run this, we should see that it works exactly the same as before. This is a key aspect of complex types that is again different from own types. Own types because we need to be able to track their reference as Shay was describing at the beginning have identity and they have to be reference types. It doesn't make sense to try and map a value type, a C Sharp value type, as a entity type. However, complex types don't have that characteristic, we actually don't care about whether it's an instance or it's the same instance or not. Actually, structs or value types are a reasonable fit for this. Records being immutable and having that nice syntax have the same advantages they do here as they have when using them with classes. In addition, you end up using a read only struct and we'll look in a minute why it's important to use read only struct as opposed to mutable structs. The bottom line when it comes to whether or not you should use a record struct or a record class as a complex type is purely the same as anything else in.NET. That is whether or not you think it's better to allocate this thing on the heap or whether you think it's better to allocate on the stack. For something like a coordinate, for example, which just has x and y. A struct seems like an obvious choice. There's a lot of overhead allocating that on the heap when it's just two simple numeric values. On the other hand, if you had a very complex, no pun intended, complex type with a lot of structure in it, a lot of values and a lot of properties, it might actually make more sense to have that still be a immutable reference type share instances and not just copy it around all over the place. But those are the same questions that you would tackle when dealing with value types versus reference types elsewhere in C Sharp. Let's look at non-record value types real quickly. What I have here is a value type and it's mutable. This is a mutable struct. Let me find the code there that I had. Go back to initializing it because it doesn't have a constructor anymore. I could add a constructor, but for the sake of this demo I'm not going to. Initialize it that way. Now when we get down to updating it, we can actually still use with here. With actually doesn't only work with value types, with record types, it will pattern match anything that has the appropriate stuff it will do. What I actually wanted to show here is that we've actually created an immutable struct. What I can do here is say this. It's immutable struct, why can't I do that? Well, that's part of the issue with why mutable structs. Eric Lipper always said, don't use mutable structs. Make them immutable. It's because when I do this access this calls the getter for address, calls it getter, the getter for address will return a copy of this address object. Because it's a struct, it's creating a copy of it. It's not returning a reference to it that then you can change, it's creating a copy of it. C# in this case actually is trying to help us. It's trying to stop us from shooting ourselves in the foot. Because what I've actually said here is, take me a copy of the address object, on that copy change Line 1 to be Peacock Lodge. Well, that's a no, that does nothing, because the address on customer hasn't been changed because I created a copy of it here. C# is basically saying you can't do that. We're not going to allow you to mutate the thing on that side because we know it's a copy and it's just getting thrown away. That's invalid, but it doesn't always. What you'd actually have to do, by the way, is you actually have to do something like this if you did want to do this. Let's extract this into a variable. We'll just replace one instance and we'll say it. That's our current address. We can then update the line on the current address. Then we can say address equals customer address. It's looking very similar to the immutable type, in fact. It's one of the reasons why it doesn't make sense we would not be immutable. This works, and the compiler is saying this is okay as well. We've created a copy, we change the address on the copy, then we copy the copy back into the original. Now this one is changed and if we run this, this should be working. But it's not intuitive. Also the compiler doesn't always give you that nice red line saying what you're doing is stupid. You can do lots of things that the compiler will say, that's fine, go ahead and do that. Then you end up saying, the value is not what I expected it and it's because you copied the struct somewhere, but you didn't realize that you were saving it on a copy and it gets very confusing. Basically, don't use mutable struct is the upshot of that. Just make them immutable. You can make an immutable struct that is not a record type, but you may as well basically go to a record type once you've got there. Again, we end up going with either immutable struct records or immutable struct glasses and that's the good pattern for complex types. I'm going to go to nested complex types next. Is there any questions we should answer? >> There's lots of lively conversation, I'm typing my fingers off to be honest. I will say one thing because it came up several times. First of all, for those who missed it, nested complex types work very well. You can use that. So inside your address you can have yet another thing that's nested, that works fine. EF will handle that. That's not a problem regarding JSON because that came up several times. This wonderful complex type feature for eight, this is the first iteration. In eight, we will not be supporting JSON mapping via complex types. You can still do that with own entities, which is a feature we introduced in seven. That's the right way with EF to do JSON at the moment. However, I think it's a very high priority item for us, I think in nine and of course nine to also allow mapping JSON via complex types. At that point I think complex types becomes the preferred way to do JSON mapping, basically. You're going to have to hang tight until we do that, but for now, JSON is still via owned entities. I think that was the main thing that I saw in the repetitive questions. >> Sounds good. What we're going to do is look at, as Shay said, nest complex types. What do I mean by nested complex types? Well, before we had an order which just had addresses. But let's say I actually want to expand that concept and give the customer a contact information, which is both the customer's address but then also the different phone numbers. I've got three phone numbers here. We can do that by creating actually three complex types. We can create the address complex type that we already had, a phone number complex types with a country code and then the number, and then a complex type which has all of the nested complex types in it. This is a complex type containing only complex type properties itself. An entity type or a complex type can create any combination of complex properties or just normal properties. You'll notice I didn't create a primary constructor on this. That's because EF doesn't yet support injecting complex type properties into a outer complex type property when materializing these entities. That's actually, I think, relatively easy to do with complex types compared to own types. We hope to put that in, but it didn't make it past eight. Once we have that, then you'll be able to use the nice compact syntax with the primary constructor for all of these. As it is, we'll put a contact on the customer, and we'll put two addresses and a contact phone on the order. You can see that we're using these at different levels at different places and that's fine. At this point, phone number is at the top level in order, but it's also contained in contact itself. You can mix them at different levels. We do need to configure them. Let's go to the configuration here. I'm just going to say, wherever phone number is used and wherever contact is used, that's always going to be a complex type. Let's delete some of this code that was for the old stuff and let's create that database and look at what the structure of it is. >> Structure of the database as you might expect. Why did orders not get created? Did delete the orders in that promp? Customer references orders. That book. >> Sorry. I've been doing questions on the chat, I've not been following closely. >> Well, so I read it and my orders table is not being created and I'm not sure why. Great table customers and then we just ended. That's so weird. Let me try again. Just run it again. It will be different. It is different. Go figure. I'm not sure what happened there, but we have a customer's table and you can see that we've again split it out by the property so a contact_address city, so on home phone. When you have a nested complex tape, then by default we flatten it all out as we were just talking about. As I said we would like this to be a single column with adjacent document in that's something we will implement after eight and then the same here for orders table you can see it's spread out. Let's do some queries. Should we do some queries? >> Of course. There's so much to see. >> Yes. Probably need to add some data in. Let's see if I can ignore the person behind the screen for the moment because I'm not fully prepared. This is all on Github, by the way. This is what's new in F8 and in here we have one called nested complex type sample and it has a seed method that will at least create some data on the database so that we can see some queries. Let's put that into our context here. Go back here and seed. Now we've got some data in the database. Let's just be clean and clear the change check out at the end of it. Lazy, really should create in context instance. But now that that's there, I'm just lazy. Simple query. Copy. I guess we need a customer ID is one. What we're doing here is a query for the entity type. There's nothing specific to complex types in this, and I want to show you what happens when we run this. Because complex properties, just like any other property, any other normal non-navigation property are always loaded when you load the entity. We have an issue on the backlog for lazy or deferred loading of property values which can be useful if you've got a property value like a blob which has a lot of data in it and you want to not load it straight away, but at the moment, EF doesn't support that for any property and that's also included for complex properties. I'm avoiding saying you don't need to use include here because as we all know include works on entity types and through navigations, and the complex properties aren't navigations so include doesn't even make sense here to use. But it's a similar. Again, we try to simulate this with own types by doing auto-include on them and that try to make it look semantically the same as it being a property. But yeah, we wouldn't even be having this conversation if we hadn't done that. Their properties, they get loaded as properties and you can see here that the select we did on the database is bringing back all of the different columns as we would expect there. That's not that interesting. What happens if we want to do something like project? Objection is a bit more interesting. Create order ID. >> I know which query I'm waiting for. There's a very exciting query coming up. >> There is, okay. I'll try to get through it. As you might expect, let me actually put a break point on here so that we can. What was that trick that somebody showed us to do that? >> Yeah. >> I used to use console right line and now I just do that. That's nice. >> Yeah. Nice trick. >> Yeah. We're projecting out just the shipping address here. Very common thing that you would want to do so get an order, give me the shipping address. We'll run this again as you can expect it's not particularly interesting translation. >> Much interesting, but was not easy to implement. I will just say that. >> Yes. >> The amount of magic that's going on behind the hood here, that's why we're here and you guys are the users seriously, so that you don't have to contend with all this complexity. >> We've got an exception, contains the stains no element. You know why? >> You forgot to see. >> There's no orders in the database. Because I copied the c-thing and then the code was supposed to add the order afterwards. Bear with me. We'll find the chunk of code here that creates an order. It's going to be in here somewhere. You know what, I'll just go back to my right opening. Not there. Here. Let's go to the top here. You will create an order, see changes. Maybe I should show this. This is actually interesting, isn't it? Update. Basically, rather than showing it, and you see what's on the screen here, that you can update, you can modify bits of a complex type even if you're doing the immutable mechanism and we'll still do the change tracking down into the nested structure. If the only thing you change is work phone country, which would be weird, then we'll only update that. I forgot to mention that, but I think it's fairly straightforward. Let's add an order here. Do it my seed method here. Sure. If this is needed, but I'm going to do it anyway. In the wrong place. It together here. That's why we needed address but we can just do this. No contact. >> Now we've got some data. Let's try. Let's see if we can run this non-interesting query finally. We're projecting the shipping address and as you can see, we're selecting only the shipping address that we need from there and projecting it out as you would expect. Not a particularly interesting query, but we got there eventually. What's next? How about we query into the complex types? Let's use it in a predicate. This isn't the interesting query yet. Here, what we're doing is find me all things that are shipping to this city. Again, I'm drilling down into the complex type and finding everything that's nested, which we should see is fairly, again, self-explanatory query. Select all of this stuff where the shipping address city is that. Maybe this is the interesting one. You can also use the complex type instance itself in predicates. >> Now it's getting interesting. >> Let's see. Somebody gave me their phone number. It actually really doesn't make sense to search for the individual parts of the phone number separately. You rarely want to find all the phone numbers in any country of a certain number. I suppose you could want to find all of the phone numbers in a country, so that part makes sense, but usually, they've given me a phone number, let me find all of the customers that have that phone number and I want you to find all the customers that have that phone number, either as their mobile work or home phone. I'll run it and then I guess I'll let Shay talk about the translation and why that makes sense especially when we're using record types for this >> Should I? >> Yeah, go for it. >> Once again, this is equality between the whole type and another whole type which is different from a specific property or field on that type, that's very different. The behavior here, what you're seeing is that EF expands that very simple equality. It interprets it to mean. Every single column, in this case, must be exactly the same on both sides and what's interesting here is on the left side of the equality, we have something in the database that's mapped to our columns in the database. On the right side, we have a parameter in the link query above, if you look at it, which means that when we expand this equality, if you look at the workclass that Arthur just highlighted, each time, on the left, we see a property on the table that contains that thing, and on the right side we have a parameter. We're going to basically send a parameter for each and every property of that parameter instance, so we took this.NET instance and we actually converted it to a bunch of parameters that we now send to SQL server or whatever database you use and we just construct comparisons between each and every one of them, which is pretty cool. This is the first time you can do something like this in EF and it's important to compare this with what happens if you did the exact same thing with something that isn't a complex type. If you had an own entity or just a normal entity, where this equals that, then you get something very different in general because entity types are defined by identity. Remember, again, this discussion about.NET types and classes and all that kind of stuff. When you do equality between two things, what that should translate to is equality between their identities. You wouldn't, like in this case, compare their actual values inside structural equality, but rather their references. That doesn't really work in the same way at all. If you have, let's say, two addresses and you're asking, give me all the roles where the shipping address is the same as the billing address, if it's an entity type and it's going to actually check that it is the exact same instance so that the IDs are the same rather than the contents are the same, which is usually not what you want with addresses. Once again, this is another great example in my mind why complex types are the more correct way to model this. What is important about a complex type, about an address, is its contents and when you want to compare, what you want is value symatics, you basically want to compare the contents and not the identity, not the reference or the ID. Complex types allow us to do this. People have been asking, by the way, in the chat, what is the exact advantage of complex types compared to, say, own entity types? This is yet another small corner where identity interferes. It gets in the way when you're using own entity types whereas complex types are the correct way to model this, in my opinion. >> We should write something about this in the docs, like you said earlier. But you really need to ask yourself, the thing that I'm modeling, does it have identity? If it has identity that lasts longer than its characteristics so when I change my name, my identity doesn't change. I'm an entity type. Or is the identity made up by its characteristics? Coordinate 23 is different from the Coordinate 56. You can't mutate 2, 3, and 5, 6 and say, that's the same coordinate just with a different value. It's a different coordinate. >> It also doesn't make sense to have the same coordinate twice and then distinguish between the two. That doesn't make any sense. >> That's entity types and complex types. Then you throw own types in there. When should I use own types? Well, you should only use own types when you want it to have a key, but you want that key to be hidden and it treated as a entity type with a hidden key, which is basically pretty much never. In essence, you never want to use own types. That's not strictly true if you think of own types in terms of aggregates where you could define a key but still have it be an own type and have the advantage of being auto included and those advantages of an aggregate, but it being an entity type in the aggregate, but that's not what most people do with own types right now. In doing that, you're still very much modeling it as an entity type, but we don't think it really makes sense to model something without a key as an entity type. Own types with keys in shadow state, like they exist today, will probably not be recommended for anything going forward. We may automatically make the default to be complex types in some situations when we do certain things, we haven't really decided that yet, but at the same time we're not going to deprecate own types immediately, so even if we change the defaults, you'll still be able to go back to all of that behavior and will still exist. Years down the road, we have dreams of being able to deprecate it entirely, but that rarely happens as you guys are well aware. For now they're both going to exist, but you should basically use complex types pretty much in every case where you think there's even a question essentially. >> I'll maybe add one last note, if we go back to this equality property by property equality, going back to our analogy from the beginning with.net classes and structs that once again aligns with the struct behavior and record behavior in.net so when you compare two records, the idea of a record, by the way, is basically value semantics. That's what records are, some people think records are about immutability. That's not quite right, records can be just as mutable as classes are. That's not the point, the point it really is about value semantics so if you compare two records, what you get is a comparison of their properties. Similarly with structures or value types, if you compare them, what you get is a comparison of their property so we're very much mapping concepts now in the same way and interpreting equality in the same way, which I think is great, it's the right thing. >> There's a comment here that I want to bring up because it's important and I've been meaning to mention it throughout. Own types are not, despite what this comment says are not great for collections. Own types are absolutely horrendous for collections. This whole problem with keys is massively greater when you have a collection of these things. If you have a single instance of a complex of our own type on an entity, then the own type inherits the key from the entity, that's how it gets its hidden key. If you have a collection, you can't obviously inherit the key value because you've got lots of things so we have to synthesize key values for that. Those key values, in the case of JSON, aren't even stored in the database like actually in relational. They're not stored in the database either so you just end up with this huge mess of when you use collections of own types and unfortunately, we couldn't do collections of complex types for eight, we just ran out of time basically. It's unfortunate that that's not shipping in eight, but ultimately that is probably the best usage is going to be for collections. >> First, just a shout out to Victor who says, I'm using it all wrong so, don't feel bad at all. >> There's no other way to do it right now. It's just a bad design. >> Also, this is a subject of very heated discussions inside the team that we used to have about. This is all very complex and has a history, but I would like to ask people. When you say own types are great for collections, that can mean two things, you can use them as collections for JSON collections, that's one thing. >> Absolutely. That's fine. >> That's the only way, as Arthur said you cannot do JSON it any other way. Again, if nine, we'll fix this, we'll make this work. But there's also the maybe lesser known option of doing owned types for normal collections which are not JSON mapped to a different table. Anybody who's using it I'd really like them to think and then tell me what exactly it is that they're getting out of it, as opposed to not using own collection. It could just be a normal collection so I'm just interested genuinely asking, and interested. What does that owed give you? It does give you, for example, the fact that it's automatically load it when you do. >> That why I think most people use it. >> Absolutely. But we also have something called auto include. We have a feature that does the same thing and so this own concept again, which has a long history and a lot of hidden discussions around it. There's this question of what it really is good for modeling and I don't think, by the way, that we should kill the own concept in general. Everywhere it does have its uses. But this is not an easy question to answer let's say it like this, It's a very complex topic. >> Indeed. I think that's all we had on the querying side so let's show this really quickly. You can access, track complex properties. In this case, we've queried for customer with number, so we can do context thoughts entry here. Let's just take first one, there is one and this is going to give me an entity entry for the tract entry. I can then go into this and I can use complex property in the same way I can use property to say, let me manipulate something on this complex property. Contact being the context property and I can now drill in further so I could say drill down and go to the mobile phone. Then I could just say find a property on that so let's find the country code. Country code and now I can do all the normal things I can do on here so I could say, I could mark the country code as modified. Then if I call context save changes here that yes, now when we run this, we should see that it's going to mark the country code as modified and it will save that to the database. All of this the normal API here that you can expect to change the current value, original values. That all works as you would expect and indeed, we have updated the country code because we marked it as modified as we'd expect. It's a little interesting as well in that remember how these are immutable? Well, they're not really. For example, let's say if I go on to address here and go to line one. I can now say value equals hello and this will actually work and it will actually mutate it. We are using M-class struck here, even though they're immutable from a perspective of the language, when you actually go and mess with their internals, you can still mutate them. EF does that as it normally does when dealing with these things because it's writing to and from fields and all of that stuff, not saying that a recommended way of doing it. But it does mean that you can in many ways treat complex types similarly to entity types in auditing and things like that. Where you can just read the current values, compare with you do updates, whatever you need to do there.. >> I think we should talk about current limitations and then I think that's about all we have. Right Shay? >> Yes. >> I'm looking here on the what's new here. We've listed out here some of the important current limitation of complex types, which are basically because we just didn't get time to do it in this release. Some of these things are things that we intend to support. Let me just go through each one of them. Support collections of complex types, we talked about that, so that's important. One of the things we haven't mentioned is that complex type properties have to be non null for now, we don't allow you to have an optional. This is a little bit like the optional versus required owned entities. We will fix this past eight and it's a difficult area to get right from a query perspective, because things that are not null, then not appearing on one side of a query can end up null and then you end up trying to deal with a not null thing, with a null thing, for a non null thing, and vice versa and so the whole nullability thing inquiry is complicated, no pun intended again, but we'll get to it. I don't know if you want to say any more about that Shay. It's more your area than mine, I think. >> Specifically, nullability. Sorry. >> Yeah. No, that's fine. >> Yeah. There's a whole complex thing about what happens when a complex type is optional or nullable. For example, just to give people like a nice riddle to think about, how do we represent that in the database? If something is optional and all its properties are optional as well, then what is our way of distinguishing whether the complex type is there, but all its properties are null. >> Exactly. >> Or it's not there at all? That's quite a riddle. That's also something we've struggled with in the past with own entities. I'm not sure we ended up in the best place in the world and I hope we'll do this correctly, but it gives you an idea of the type of problems. Again, distinguish between null and an empty instance. >> Yeah. >> Basically, it's a big problem. We need some other column in the database just to tell us whether that thing is there or not. That gives you a taste of the problems that you have to deal with if you want to do this. For now, we just don't support it, unfortunately. >> There are issues for each of these things. Go vote on the issues. Thumbs up on the issue if it's important to you. I know a lot of people are going to yell at us about the null stuff because they did before about [inaudible] types, but it's really is difficult. JSON columns, we talked about, we want to support mapping them in JSON columns. Constructor injection, I also mentioned, so you should be able to create an instance of a phone number type, a constructor that takes a phone number type and we should be able to call that constructor when we create the instance. If you're using C data, think carefully about whether it's a good idea, but if you are using it then that's not supported currently in complex types yet either. We don't support complex type properties in Cosmos, but we definitely intend to do that and probably make them the default for the adjacent documents in Cosmos. But that's not done yet and they're not implemented in memory provider. Well, parts of them are, the part for model building and things like that, that we needed them to be in the memory provider. Those queries do not work. If you try and run a query against complex type in memory database, it won't work. This we're very unlikely to implement because we're not really promoting the in memory provider as something that people should be using for their own work as opposed to us using it when we're developing EF core for which is very useful. But there's an issue for it, so go vote for it if you want. I doubt it will change anything in this case, but it's always good to get the feedback. I think that's everything we had. Just very quickly. Also new in the EF Core 8 that you might want to try. Primitive collections, we had a stand up on that before, although things have changed quite a lot. We've made a lot of improvements there and I think it's working pretty well now. That is, primitive collections mapped into adjacent column, and primitive collections in embedded in adjacent document. >> Even non primitive collections in adjacent column. What's that? Exactly. If you have a JSON document, there own entities and you have a collection in there, you can now query them. You can add arbitrary, super cool. Everything is queryable. >> Adjacent column, they work on SQL light. I think there's some other stuff in there. We've got hierarchy ID, we just stand up on that. Unmapped types, you can write raw SQL queries for unmapped types in more cases now. Lazy loading for disconnected entities and lazy loading can be switched on or off per property and maybe a couple of other things there. Changes some improvements to the API for tracking entities and how you can look things up by key. Easier, for example, some model building and then a whole bunch of new math translations. This isn't finished yet, by the way, we're still working on adding to this thing. I think price has a PR already yesterday for that. More documentation is coming. But grab the daily build, give it a try, give us your feedback. Because if you report a serious book in the next couple of weeks, probably then we can fix it and it will be in the GA release after that, December at the earliest is when you're going to get it. It could end up being February like it was last year. It's really important to try it now and give us the feedback as soon as you can. Try the daily bills if you can really helps us if you're on the latest daily bill. Otherwise, the first thing we have to do when you file a book is to see whether it repos on the daily bill. If you file it on RC1, for example. I think that's it. Anything else, Shay? >> I don't think so. There's been, again, a ton of engagement, which really is great. I think this has been talking to a lot of people. This feature, which is really nice to see. I think a lot of people dealt with the problems around this, so it's nice to see. But yeah, I'm actually super excited about this release. We're delivering quite a lot of stuff. We just have to document it all so that people are aware of it. It's a pre cool release. >> Yeah. Well, we will let you-all go then and hopefully it won't be three months before the next one. We will see you-all again soon. Of course, I have to find the thing to say. Here we go. Thanks everyone. Bye. [MUSIC]
Info
Channel: dotnet
Views: 6,487
Rating: undefined out of 5
Keywords: dotnet, entityframework, efcore
Id: H-soJYqWSds
Channel Id: undefined
Length: 84min 2sec (5042 seconds)
Published: Thu Sep 21 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.