Tip 13. An easy way to attach an entity

Source: Internet
Author: User

Tip 13. An easy way to attach an entity

Problem:

In some earlier hints, we discussed using attach to load an unchanged (unchanged) state to ObjectContext to avoid the overhead of querying.

If performance is your goal, attach is the weapon to choose from.

Unfortunately our API does not fit into the case of 99%, where there is only one entity set for each type. The Entity Framework supports single-type multi-entity sets (multiple entity sets Pertype) or Mest, and the API reflects this, asking you to provide the name of the entity set (EntitySet) you want to attach.

That is, like this:

If you're like me, you might resent it. Hard-coded into strings in code. They are prone to error and such things pollute your code, which is essentially a "minor problem".

Solutions in. NET 4.0

This problem is fixed in. NET 4.0 by returning objectset<t> with each entityset instead of objectquery<t> this strongly typed property. Objectset<t> has the Add, delete, and attach methods to deal with this problem directly, so you can write code like this:

1 ctx. Order.attach (order);

Not a single string appears!

This solution is ideal, you attach the required entity set, and it works regardless of whether you have mest or not.

Solutions in. NET 3.5

What should I do in. NET 3.5?

My point is that we should provide a generic version of Attach, which is as follows:

This method checks how many entityset exist in T, and if there is only one, it attaches the entity to that entity set. However, if more than one of them will throw an exception.

Although this method does not have the power to extend the method, it is also very easy to write.

Here's what you need to do:

    1. Get the <T> type of EntityType
    2. Get a list of EntitySet types that this entitytype may belong to. EntityType may be derived types (like car) and actually belong to a collection of parent types (like vehicles).
    3. Traverse the EntityContainer to find a match for each EntityContainer EntitySet.
    4. If one is found, it is appended, otherwise an exception is thrown.

Let's do the following:

But first of all, this is just an example-quality code, I'm a project manager, not a programmer, so the risk of using this code is self-employed:)

First we add an extension method to MetadataWorkspace to get a CLR type (o-space) corresponding to the conceptual model (C-space) EntityType.

1 public static EntityType getcspaceentitytype<t> (  2 This        metadataworkspace workspace  3)  4 {  5     if (workspace = = null)   6         throw new ArgumentNullException ("Workspace");  7     //Make sure the assembly for "T" is loaded  8     workspace. Loadfromassembly (typeof (T). Assembly);  9     //Try to get the Ospace type and if are found//look for the     cspace type too.     EntityType Ospa Ceentitytype = null;     Structuraltype cspaceentitytype = null,     if (workspace. Trygetitem<entitytype> (         typeof (T). FullName,         dataspace.ospace,         ospaceentitytype))         (workspace. Trygetedmspacetype (             ospaceentitytype,             cspaceentitytype))         {             return Cspaceentitytype as EntityType; +} +     return null; 26}

Since you may be calling this code before the <T> metadata is loaded, the first line of the Code guarantees that the assembly is loaded, and that the Assembly will not take any action if it has already been loaded.

Next we add a method to get an enumeration of all the types we need to match, that is, the hierarchy of the parent type, including the current type:

1 public static ienumerable<entitytype> GetHierarchy (  2 this     EntityType EntityType)  3 {  4     if (EntityType = = null)  5         throw new ArgumentNullException ("EntityType");  6     while (entityType! = null)  7     {  8         yield return entityType;  9         entityType = Entitytype.basetype as EntityType;     11}

Finally, we can begin to complete the Attachtodefaultset method:

1 public static void Attachtodefaultset<t> (  2 This     ObjectContext ctx,   3     T entity)  4 {  5< C6/>if (ctx== null) throw new ArgumentNullException ("CTX");  6     if (entity = = null) throw new ArgumentNullException ("entity");  7  8    9     metadataworkspace wkspace = ctx. MetadataWorkspace; Ten     EntitySet set = Wkspace        . Getentitysets (Wkspace. Getcspaceentitytype<t> ())        . Single ();     CTX. Attachto (set. Name, entity); 15}

The standard is used here. The single () method throws an exception if there is a possible entity set that does not exactly exist for a corresponding EntityType.

Using this implementation, we can rewrite the code in the previous article using the following method:

1 Product p = newproduct {ID = 1, Name = "Chocolate Fish"} ctx. Attachtodefaultset (P);

Unless, of course, you use mest ... But you may not use it!

Additional Instructions

While this piece of code works well, it does not have any optimizations.

Perhaps it makes sense to cache the name of a possible collection of CLR types, so that you do not have to do the same checks when you are attach, which is left to you for practice!

Prompt index

Yes, here is an index of the remaining hints for this series.

Tip 14. How to Cache Entity Framework reference data

Scene:

In order for the application to work, it makes sense to cache commonly used reference data.

Good examples of referencing data include things like states, countries, departments, and so on.

Usually you want these data to be available at any time to easily populate the drop-down list.

Where is a good example of caching reference data at hand is to have new customers register the page, part of the form to collect the user's address, including their state. In this example you need to reference the data to do two things:

1. In the Build form, select the state drop-down list.

2. Assign the state to the final user record.

How do you use the Entity Framework to support such scenarios?

Solution:

When designing a solution, we need to remember two key points.

1. An entity can only be attached to one objectcontext at a time, at least in. NET 3.5 SP1.

2. You may be using cached reference data (read objectcontexts) by many threads at the same time.

Essentially these two points contradict each other.

The solution is to copy the entity when we read it from the cache so that the additional copy will not affect any other threads.

If this is a webform solution, we might want to write code like this:

1 var customer = new customer{  2    Firstname = Txtfirstname.text,  3    Surname = Txtsurname.text,  4    Email = Txtemail.text,  5    Street = Txtstreet.text,  6 City    = Txtcity.text,  7 state    = Statescache.getsingle (  8       s = = S.id = Int32.Parse (ddstate.selectedvalue)  9    ),    Zip = Txtzip.text one} and CTX. Addtocustomers (customer); CTX. SaveChanges ();

But there's a big problem here. When you add customer to ObjectContext, the state of the copy is also added. If we do this, the Entity framework will think it needs to insert state into the database. And that's not what we want.

So we need to tell the entity Framework state that this copy has already existed in the database by using Attachto (...):

1 var state = Statescache.getsingle (2      

Then we can continue to build customer:

1 var customer = new customer{  2    Firstname = Txtfirstname.text,  3    Surname = Txtsurname.text,  4    Email = Txtemail.text,  5    Street = Txtstreet.text,  6 City    = Txtcity.text,  7 state    = state,< C14/>8    Zip = txtzip.text  9} ctx. SaveChanges ();

If you are vigilant enough, you may have found that I did not call Addtocustomers again (...).

Why is it? Well, when you build a relationship that already exists in the context (state = state), the customer is automatically added.

Now, when SaveChanges () is called, only customer is stored in the database. State is not persisted at all because the Entity Framework believes it has not changed.

Interestingly, we can use the fact that State is not supported as our condition.

Because the primary key attribute of state is the only thing that needs to be known when the Entity Framework constructs the relationship, even if the other properties are wrong, the primary key attribute is actually the only thing we need when we copy.

In this way, our copy code can be very simple:

1 Public State Clone 2 {3    return new state {ID = state.id}; 4}

or use the following lambda expression:

1 var cloner = (state s) = = new state {ID = s.id};

As long as we don't want to modify the copy, these are all we really need.

Now that we know what is needed, it's easy to write a very simple generic class that provides caching and "read-only copy" services.

1 public class cloningcache<t> where T:class  2 {  3     private list<t> _innerdata;  4     Private func<t, t> _cloner;  5 Public     Cloningcache (ienumerable<t> source, func<t, t> Cloner)  6     {  7         _innerdata = Source. ToList ();  8         _cloner = Cloner;  9     } public     T getsingle (func<t, bool> predicate) one     {ten         lock (_innerdata)         14             return _innerdata                         . Where (predicate)                         . Select (s = _cloner (s))                         . Single (); +}     20}

Note that the getsingle (...) method copies the results that it finds.

In addition, using this copy cache is very simple:

1 var statescache = new Cloningcache<state> (2       ctx. States, 3       (state s) = + new state {ID = s.id} 4);

The first parameter of the constructor is the data to be cached (that is, all the states in the database), and the second parameter is how to implement the copy, and we need to use this cache securely across multiple ObjectContext.

Once you initialize this cache (presumably in Global.asax), whatever the case you need to access the reference data directly, you can use this cache in a static variable.

Please let me know where it is unclear or if you have any questions.

Tip 15. How to avoid loading non-mandatory properties

Update: A series of important corrections have been made to the previous need for original values.

Problem:

Imagine if you query blog essays:

1 var myposts = from post in CTX. Posts  2 to               post. Created Descending 3               select Post;

Just so you can output the blog title and so on.

1 foreach (Var post in myposts) 2 {3      Console.WriteLine (' {0} on {1} ', post. Title, Post. Created); 4}

This way you do a lot of useless work to load properties that you don't actually need.

Read-only solution:

For read-only scenarios, the solution is easy.

You only need to do the projection:

1 var myposts = from post in CTX. Posts  2 to               post. Created Descending 3               select new {post. Title, Post. Created};

This way you avoid loading properties that you don't actually need.

This is especially important for entities that have many properties or that have properties that map to a BLOB column in the database, such as a property such as body mapping to a nvarchar (max) column.

Read and Write Solutions:

But what if you need to modify the entity?

Projecting here is not a good scenario, because unless you get a complete object, you will not get any object services, which means that you will not be able to update it.

Well...

As always, the key to getting a solution is to understand how the Entity Framework works.

When updating an entity, the Entity Framework sends updates to the database (pseudo-code) in the following format:

1 UPDATE [Table]  2 SET  3      ModifiedProperty1 = NewValue1,  4      ModifiedProperty2 = NewValue2,  5      ...  6      Modifiedpropertyn = Newvaluen  7 WHERE  8      keyproperty = KeyValue and  9      ModifiedProperty1 = OriginalValue1 AND10      ModifiedProperty2 = OriginalValue2 and one      ...      Modifiedpropertyn = Originalvaluen

Note properties that have not been modified do not appear anywhere in the update command.

Significant discovery: This means that you only need to know the original value of the primary key attribute. *

With these findings, we can try the following:

    1. Projecting only the columns we need to read and write
    2. Forge an entity by projection, ignoring columns we don't care about.
    3. Attach (...) That "partially correct" entity
    4. Make the changes you want to the entity
    5. SaveChanges (...)

This allows us to change our entities without instantiating properties that we are not interested in.

Here are some of the code that can do the work:

1//Project Just the columns we need  2 var myposts = from post in CTX. Posts   3 to               post. Created Descending  4               Select new {post.id, post. Title}; 5//Fabricate new entities in memory.  6//Notice The use of AsEnumerable () to separate the in DB query  7//from the LINQ to Objects construction of Post E Ntities.  8 var fabricatedposts = from P in myposts.asenumerable ()  9     Select New Post{id = p.id, Title = Post.  TITLE};10//Now we attach the posts one//and call a method to modify the Title (Var p in fabricatedposts) 13 {     CTX. Attachto ("Posts", p);     p.title = Changetitle (P.title); SaveChanges ();

Note that we only retrieved the id attribute (the primary key) and the title attribute (what we want to modify), but we still did the update successfully.

TA da!

* Warning/concurrency issues

If you use stored procedures to update entities, the content in this hint does not apply.

If you think about how the stored procedure works, you can see why. When updates are made using stored procedures, all current values (and some of the original values) are mapped to parameters, regardless of whether they have been modified. This basically means that you have to get all the original values: (

There are also times when you need to tell the Entity Framework some other primitive values because the update does not succeed without them:

    • Concurrency Properties: The original value of the concurrency attribute is included in the UPDATE statement to ensure that you can update the database only if you explicitly know the current database version. So no correct original value update will not succeed.
    • entityframework EntityKey Value: You also need to know 0.. 1 The original value of the relationship, even if the relationship is not ready to be changed. For example, if an order has a customer, you will need to know Customerreference.entitykey, and then you can use this established relationship to initialize a new order. When you use the FK attribute (in the upcoming. NET 4.0), the problem will no longer exist.

c-side The properties referenced by the mapping criteria: The value of the c-side mapping condition is used to figure out which map is applied, so there is no correct original value to establish the correct update mapping. Most people don't use this feature.

Tip 13. An easy way to attach an entity

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.