7.3 Persistent Object relationship diagram
Persistent order involves operations on multiple entities. Although only order and its details are updated, the products of order's customer and details will also be involved as read-only data. Because multiple entities are involved, we need to talk about the entity graph or the object graph ).
7.3.1 object relationship diagram of the persistent added state
When order is saved, the persistence process is triggered. The following is what you must do:
- Create an instance of order.
- Associate order with a customer.
- Creates an orderdetail instance.
- Associate orderdetail with a product.
- Add orderdetail to order.
- Repeat steps 3 to 5 for each detail.
- Add order to context.
- Persistent modification.
Perform these steps based on whether the foreign key association or independent association is selected.CodeBut the basic concepts are the same. Let's take a look at these two cases in sequence.
Use a foreign key to associate the object relational graph of the persistent added state
It is very easy to associate customerh and order with foreign keys. You only need to set the foreign key attribute as the primary key attribute of the correlated object. For example, if the customerid attribute of order is set to 1, the customer with order and ID is automatically associated.
As mentioned above, EF does not support partial object relationship graphs. When order is in the added state, details must also be in the added state. See the following list:
Listing 7.7 create an order using a foreign key Association
VaR Order = New Order {Customerid = 1, orderdate = Datetime . Now. Date}; order. shippingaddress = New Addressinfo () {Address = "2th Street" , City = "New York" , Country = "USA" , Zipcode ="0000001" }; VaR Detail1 = New Orderdetail () {Productid = 2, quantity = 3, unitprice = 10 }; VaR Detail2 = New Orderdetail () {Productid = 1, quantity = 5, unitprice = 10}; Order. orderdetails. add (detail1); Order. orderdetails. add (detail2); CTX. orders. addobject (order); CTX. savechanges ();
Foreign keys are easy to use, but they are not the only way to maintain the relationship between entities. During model design, you may not use foreign keys, but instead use independent associations. Or yourProgramIs upgraded from ef1.0 to ef4.0, and ef1.0 does not have a foreign key concept.
Use the object relationship diagram of the independent association persistent added state
When an independent association is used, the customerid column of the Order table is mapped to the companyid attribute of the customer class. The Order class does not have the mermerid attribute. As a result, a customer instance and an order must be created and associated with the order. The same applies to associated details and their products.
Because we only need the customer ID and pruduct ID to retrieve them from the database, you can create an instance for each of them, and then associate the instance with order and details. See the following list:
Listing 7.8 creating an order using an independent association
VaR Cust = New Customer () {Companyid = 1 }; VaR Product1 = New Product () {Productid = 1 }; VaR Product2 = New Product () {Productid = 2 }; VaR Order = New Order {Customer = Cust, orderdate = Datetime . Now. Date}; order. shippingaddress = New Addressinfo () {Address = "2th Street" , City = "New York" , Country = "USA" , Zipcode = "0000001" }; VaR Detail1 = New Orderdetail () {Product = product1, quantity = 3, unitprice = 10 }; VaR Detail2 = New Orderdetail () {Product = product2, quantity = 5, unitprice = 10}; Order. orderdetails. add (detail1); Order. orderdetails. add (detail2); CTX. orders. addobject (order); CTX. savechanges ();
Will the above Code work? The answer is: no. During persistence, updateexception is thrown because null values cannot be inserted into the name column of company. This exception occurs because the customer of the addobject method is marked as added, so the persistence process also tries to insert the customer. Because only the customer ID is set, the name is null, and the database does not allow the name to be null.
To work properly, you must use the changeobjectstate method to mark customer as unchanged before calling the savechanges method. In this case, only order and its details are persistent, while others remain unchanged.
Listing 7.9 creating an order using an independent association
CTX. Orders. addobject (order); OSM. changeobjectstate (Cust,Entitystate. Unchanged); OSM. changeobjectstate (product1,Entitystate. Unchanged); OSM. changeobjectstate (product2,Entitystate. Unchanged); CTX. savechanges ();
As you can see, independent association operations are a bit difficult.
Another problem is that the order cannot be associated with the customer because the customer has not yet been created. We do not want to go back to the customer form to create the customer and then create the order, but create the customer and order in the same form.
Object relationship graphs in different States are persisted.
It is easy to solve the problem above. You can create a complete customer instance, associate it with order, and do not set its status to unchanged.
If it is within a method, a flag may be required to specify whether the customer is old or new. If you use an external method, you need to pass this flag. But a more independent solution will be better. The problem is how to determine whether the customer needs to insert a database without flag.
ID is the key. In the case of foreign key association, if the foreign key attribute is 0, the customer is new; otherwise, the customer already exists. In the case of independent association, if the customer Object ID is 0, the customer is new; otherwise, the customer already exists.
7.3.2 modify the persistent Relationship Diagram
The problem arises again. If the shipping address is incorrect, you need to modify it. This is very simple. This does not involve relational graphs, because you only need to update the order and do not care about its details. Let us assume that the customer needs more red shirts (productid 1), instead of shoes (productid 1) and a new green shirt (productid 3 ). Worse, the order is associated with the wrong customer.
This is a little difficult. The data in the order Instance remains unchanged, but the associated customer changes. Some items in details have been changed or removed, and some items have been added. It takes a little effort to combine all updates.
Persistent modification using foreign key Association
The connection is very simple. Just run the Code:
Listing 7.10 updating orders with connection
Order. actualshippingdate =Datetime. Now. adddays (2 );VaRProduct1 =NewProduct() {Productid = 3 };VaRDetail1 =NewOrderdetail() {Product = product1, quantity = 5, unitprice = 3}; Order. orderdetails. add (detail1); Order. orderdetails [1]. quantity = 2; CTX. orderdetails. deleteobject (Order. orderdetails [2]); CTX. savechanges ();
This code executes four operations. The shipping date of the Order is updated, a new detail is added, a quantity with an existing detail is modified, and a detail is deleted. Is it easy to understand?
It is a little difficult to disconnect a connection. Assume that the update logic is placed in an external method-it does not know what has been modified, added, or removed. It receives the order and then needs a way to know what has been modified. This is common in hierarchical applications.
The solution is also very simple. Query the database to retrieve the order and its details. Then, use the applycurrentvalues method to update the order from the database with the value of the incoming order. However, applycurrentvalues only affects order, and the details remains unchanged. To know what details has been modified, you must use LINQ to objects.
The Order details in the added status is included in the incoming order instead of in the order retrieved from the database. The removed order details is in the order from the database, but it has not been received by the method. Order details in the retrieved database and input order may have been modified using applycurrentvalues. It is easy to understand through:
At the end of the match, the order from the database will be updated by the incoming data and ready to be persisted. See the following list:
Listing 7.11 updating orders when disconnected
Void Updateorder ( Order Order ){ Using ( VaR CTX = New Orderitentities ()){ VaR Dborder = CTX. Orders. Include ( "Orderdetails" ). First (O => O. orderid = orderid ); VaR Added = order. orderdetails. Sort T (order2.orderdetails ); VaR Deleted = order2.orderdetails. t (order. orderdetails ); VaR Modified = order2.orderdetails. intersect (Order. orderdetails); CTX. orders. applycurrentvalues (order); added. foreach (D => dborder. orderdetails. add (d); deleted. foreach (D => CTX. orderdetails. deleteobject (d); modified. foreach (D => CTX. orderdetails. applycurrentvalues (d); CTX. savechanges ();}}
Persistent modification using independent association
When an independent association is involved, the code for modifying order and its details remains unchanged. The difference is that the method for associating customer is the same.
Because there is no foreign key attribute, applycurrentvalues does not change the association between order and customer. The only way to change the customer is to assign an instance indicating the new customer to the customer attribute of order.
If the customer instance is not attached to the context, it is associated to the context in the added state. Obviously it's not what you need, because you don't want to insert a customer, but only modify the customer associated with the order. There are three ways to do this:
- Retrieve the customer from the database so that it has been attached to the context and assigned it to order.
- Associate customer and order, and then use the changeobjectstate method to modify the object state.
- Append an object to the context and associate it with order.
The following list shows how to change the customer association using the third method.
Listing 7.12 using an independent association to modify the Order's customer
VaROrder = getorder (); CTX. Companies. Attach (order. Customer); dborder. Customer = order. Customer; CTX. savechanges ();
7.3.3 Delete a persistent relationship Graph
Persistent deletion using foreign key properties
In the case of connection, retrieve order, call deleteobject, and then call savechanges.
When the connection is disconnected, create an order instance with the id filled, append it to the context, and call deleteobject and savechagnes. The following list shows how easy it is to delete a graph when the connection is disconnected.
Listing 7.13 deleting an order using a foreign key association when disconnected
VoidDeleteorder (IntOrderid ){Using(VaRCTX =NewOrderitentities()){OrderOrder =NewOrder() {Orderid = orderid}; CTX. Orders. Attach (order); CTX. Orders. deleteobject (order); CTX. savechanges ();}}
Pay attention to the performance issues. Even if EDM knows that there are cascade constraints in the database, the context will issue the DELETE command for each object attached to the context.
Assume that an order has 30 details. You may want to issue a delete for the entire order, but it is not what you think. In conceptual mode, cascade constraints between order and its details are specified. Therefore, if they are attached to the context, it is marked as deleted and a delete is sent for each of them to the database.
Of course, if you do not delete cascading constraints, You must retrieve all the details and mark them as deleted. Unfortunately, this solution is always prone to errors, because a new detail may be added between the retrieved details and their physical deletion, but the context is unknown. When deleting data from the database, this will lead to a foreign key error because the detail is not deleted when order is deleted.
The alternative solution is to initiate a custom database command to delete all details, and then let the context issue a separate delete for order.
|
You are probably thinking that if you delete an order and other people add another detail, there should be a concurrent check. In the next chapter, we will discuss it and ignore it here. |
Finally, it is easy to delete a cascade. The Code does not need to care about details, and the database performance is also improved. If you do not use cascading, you need more control during the deletion process, but you do not think that you need more code and performance degradation.
You can delete objects using the foreign key attribute.
Persistent deletion using independent association
In terms of concept, there is no difference between using an independent link to delete an object in a graph and using a foreign key attribute to delete an object in a graph. The code is changed. to delete an object, the context needs to append all one-to-one associated entities. If you want to delete a customer, you do not need orders because it is on the queue side. If you want to delete an order, you need its customer.
The customer is required to delete an order because State Manager marks the relationship between the two entities as deleted and converts it to SQL. A foreign key column is added to the WHERE clause of the Order DELETE command. Finally, delete an order, instead of issuing the delete from order where orderid = 1, but delete from order where orderid = 1 and customerid = 2.
In the case of a connection, the difference between an independent association and a foreign key association does not exist. When order is retrieved, state manager knows the relationship between its customers and them. In case of disconnection, the customer must be appended with order, which is very important. What you need to do is create an order, associate the relevant customer, append the order to the context, and then pass it to the deleteobject method. Because cascade constraints exist, details is automatically deleted. See the following list:
Listing 7.14 deleting an order using an independent association
VoidDeleteorder (IntOrderid,IntCustomerid ){Using(VaRCTX =NewOrderitentities()){VaROrder =NewOrder() {Orderid = orderid}; order. Customer =NewCustomer() {Companyid = 1}; CTX. Orders. Attach (order); CTX. Orders. deleteobject (order); CTX. savechanges ();}}
If the delete cascade constraint does not exist, consider the foreign key attribute as well. The only thing that needs to be pointed out is that in order to delete details, their associated products must also be loaded (just as order requires customer ). If you do not use Delete cascade to delete an order, you must load the entire graph.
7.3.4 persistent sequence-to-sequence relationship
No new things will be introduced for many-to-many relationships. I have learned all the required knowledge before. Add a product for supplier. You create instances for supplier and product, add the product to the product list of supplier, and call savechanges.
You can obtain the same result in the opposite way. Create supplier and product, add supplier to the suppliers product list, and call savechanges.
When you remove a product from supplier during connection, remove the product from supplier's product list and call savechanges. When the connection is disconnected, retrieve data from the database and compare the data with the incoming data to identify changes. As you can see, there is nothing new.
Now let's look at a new question. What happens if you mistakenly associate order with a non-existing customer? What happens when an error occurs during persistence? What should I do if I need to execute a custom command? These questions will be answered in the next section.