Tip 20. How to handle fixed-length primary keys
This is the 20th article in the ongoing Entity Framework hint series.
Fixed-Length field fills:
If you have a fixed-length column in your database, such as a column of type nchar (10), the fill will automatically occur when you insert it once. So for example, if you insert ' 12345 ', you will get 5 autofill spaces to create a 10-character-length string.
In most cases, this autofill will not be problematic. However, if you use one of these columns as your primary key when using the Entity Framework, you may be having trouble identifying identity resolution.
What is identity recognition (resolution)?
Identity recognition is an important feature of most ORM support. It ensures that each "entity" in the corresponding database has only one object in memory.
So if there is already an object in ObjectContext that represents a specific "entity", then you execute another query to request the "entity", the query will return the same object that already exists, instead of creating another new object.
This is important because it prevents you from making multiple conflicting changes to different objects of the same "entity" to get into the wrong situation.
So why did it go wrong?
It is generally not possible for you to make mistakes, but there is a possibility of error. Here's why ...
In the Entity Framework we "identify" based on the value of the EntityKey (aka Primary key) in the CLR and we do not make any autoincrement for you, which is not like a database.
This means that if the primary key of the product entity is a nchar (10) Type of ProductCode, you write the following code:
1 Product p1= New product 2 {3 ProductCode = "SG500", 4 Description = "7200rpm GB HDD" 5};6 ctx. Addtoproducts (p1); 7 CTX. SaveChanges ();
The EntityKey in memory is ' SG500 '. However, the database stores the ' SG500 '.
Now if you write a query like this:
1 Product p2 = ctx. Products.first (p = = P.productcode = = "SG500");
Because of the query semantics of SQL Server, this will match the ' SG500 ' record in the database, and the result ' entity ' will have a value of ' SG500 ' EntityKey.
If you run this query again in the same ObjectContext, identity recognition will take effect and return P1 instead of creating a complete new object. For example, this test will not fail:
1 Debug.Assert (Debug.referenceequals (P1, p2));
Unfortunately, this is not the case, because P1 's EntityKey is ' SG500 ' and P2 's EntityKey is ' SG500 '. When using CLR semantic comparisons, they are obviously different. So identity recognition will fail and eventually you will get different objects.
At this point you will begin to understand that you are unlikely to encounter this situation. It is only when you create an object of an entity in the same context and then query and modify the different objects of the same entity that it really creates a problem, if you do this presumably means you have a long life cycle of ObjectContext, which is generally not recommended.
However, this whole description of the less likely to occur problems if they occur can lead to very serious errors, so it is best to avoid early.
Ensure that you avoid identity recognition problems:
All solutions for this problem are extremely simple. When you create (or attach) your entity for the first time, simply populate the primary key yourself. Get an entity like this:
1 Product p1= New product 2 {3 ProductCode = "SG500 ", 4 Description = "7200rpm GB HDD" 5};
You might even want to create a small tool method to populate for you:
1 ProductCode = Pad ("SG500", 10);
If you do this, the identity recognition code of the Entity framework will work well.
If you have a fixed-length primary key when you use entity frameork, I highly recommend a protective code like this.
Tip 21. How to use the single () operator – EF4.0 only
This is the 21st article in the ongoing Entity Framework hint series, which is the first article for EF4.0 only.
Entity Framework 4.0 Beta 1
You may have heard that the VS Beta 1 is already available for subscribers to download, so that a person can experience the EF 4.0 Beta 1 firsthand. If you have any questions about our new pre-release products, please go to this forum.
Supports single () and Singleordefault () methods
One of the features we added is support for the single () Operator of LINQ.
The expected semantics of this method is that if there is not just one match it will throw an exception. For example, this:
1 var person = (from P in CTX. People 2 where p.firstname = = "Alex" 3 select P). Single ();
If there are no matches or if more than one match is found, an exception is thrown.
We have also added support for Singleordefault (). It's a little different, it means at least there should be a match, which means that if there is not a match it can.
How is this going to happen?
Some people in the forum have noticed that we have added support for single () and note that the T-SQL being executed is a TOP (2) query. This is as follows:
1 SELECT TOP (2) ... 2 from [dbo]. [People] As [Extent1] 3 WHERE N ' Alex ' = [Extent1]. [Firstname]
As Matthieu in the Forum mentions in his answer, if you think about it carefully, you'll find it makes sense. We have to get the first two results (no more) to determine if we should throw an exception, and if the result has two rows instead of just one, the exception is thrown DataReader.
Tip 22. How to make include real include
This is the 22nd article in the ongoing Entity Framework hint series
If you want to preload in the Entity Framework, it's generally very simple, you just need to write code like this:
1 var results = 2 from post in CTX. Posts.include ("Comments") 3 where post. Author.emailaddress = = "[Email protected]" 4 select Post;
In this example, for each matching article you will get its comments, which are in a query, also known as preloaded.
Cool. So far so good.
However, if you start doing more interesting queries that change the "shape" of the query, or introduce a join or something else, it might look like this:
1 var results = 2 from post in CTX. Posts.include ("Comments") 3 from the blog in post. BLOGS4 where blog. Owner.emailaddress = = "[Email protected]" 5 select Post;
This include will not work.
Why?
When the first example is translated into a query, the selected column is the same from beginning to the same. The filter condition introduces a join, but this does not affect which columns are selected, so the shape from the Include () execution start to the last select query is unchanged.
In the second example, when the include () is executed, the query contains only the columns of the post table, but the second from changes the shape of the query to include the Post table and two table columns of the blog table, which means that the shape of the result is changed, albeit temporarily, But this temporary shape change makes the Include () stop working.
Now in this particular example, you can rewrite this query as follows to make the include work again:
1 var results = 2 from post in CTX. Posts.include ("Comments") 3 where post. Blogs.any (4 b = b.owner.emailaddress = = "[Email protected]" 5 ) 6 select Post;
... So the include will work again. Because the query has been rewritten to avoid changing the shape when the Include () is applied until the end of the query.
Unfortunately, this type of workaround has some impact but is intrusive because it forces you to change the way you write queries just so that include can work.
However, there is a better option.
Workaround
This workaround is actually very simple, and you just need to move the include to the end of the query.
1 var results = 2 (from post in CTX. Posts3 from blog in post. BLOGS4 where blog. Owner.emailaddress = = "[Email protected]" 5 select Post) as Objectquery<post>). Include ("Comments");
In order for this to work, the last select must be an entity, in other words need to select Post instead of select New {...}, if so you can convert the type of the result to ObjectQuery and make an include.
This works well because changes remain unchanged when the include is applied until the end of the query.
Tip 23. How to forge Enums in EF4
Enums is not supported in the current EF4.
Now we'll listen to the feedback on Beta1 to make some adjustments, so it's hard to anticipate, but it doesn't seem to be supported at this point.
Yesterday I brought a workaround, though just very little work, but quite interesting.
Workaround
To make this way work, you need to use. NET4.0 and requires the use of POCO classes.
Imagine that you have an enumeration such as the following:
1 public enum-Priority 2 { 3 -High, 4 -Medium, 5 low 6}
The first step is to create a complextype that has only one property, such as the following:
1 <complextype name= "Prioritywrapper" > 2 <property type= "Int32" name= "Value" nullable= "false"/> 3 < /complextype>
Then if you want to have a property in an entity that returns an enum instead of the complextype that returns the wrapper.
As I said, this works only in Poco mode. This is because you need to do something interesting in this compound type of prioritywrapper:
1 public class Prioritywrapper 2 { 3 private priority _t; 4 public int Value { 5 get { 6 return (int) _t; 7 } 8 set { 9 _t = (priority) value, ten} One}, public priority enumvalue 13< c20/>{ get { return _t;) set { _t = value; +} 21}
Note that this type has an int type of Value property as defined by complextype, but this class also has a way to set the Enumvalue property with the get priority.
With this class we can use it in POCO entities, so for example imagine you have a task entity:
1 public class Task 2 {3 public virtual int Id {get; set;} 4 public virtual Prioritywrapper priority {get; set ; } 5 Public virtual string title{get; set;} 6}
The next step is interesting, adding some implicit conversions between prioritywrapper and priority:
1 public static implicit operator Prioritywrapper (priority P) 2 { 3 return new Prioritywrapper {enumvalue = P }; 4} 5 6 public static implicit operator priority (Prioritywrapper PW) 7 { 8 if (PW = = null) return Priori Ty. High; 9 else return PW. Enumvalue; 10}
With these implicit conversions, you have an illusion that the priority attribute in the Task class (Prioritywrapper type) is actually a priority (enum type).
For example you can do this:
1 Task Task = new Task {2 Id = 5, 3 priority = Priority.high, 4 Title = "Write Tip 23" 5};
And you don't need to do this:
1 Task Task = new Task {2 Id = 5, 3 priority = new Prioritywrapper {enumvalue = Priority.high}, 4 Title = "Write Tip 23" 5};
Can do this:
1 if (task. Priority = = Priority.high)
Without having to:
1 if (task. Priority.enumvalue = = Priority.high)
But what about the query?
You can even use this enumeration in queries:
1 var highpriority = 2 from task in CTX. Task 3 where task. Priority.value = = (int) Priority.high 4 Select Task;
Pretty cool, huh?
This is not good enough for our native support enumeration at this time, but not much, especially from the point of view of some programming relative to the entity.
Coding happily.
Tip 24. How to get ObjectContext by an entity
Customers often ask how to return to ObjectContext by an entity.
In general, we do not recommend this kind of attempt now. But sometimes you really need a way to get ObjectContext.
For example, if you are in a method where the current scope can only access the entity, you need to ObjectContext, possibly for some other query, and so on.
One idea is to change the signature of the method so that the caller must pass in ObjectContext when the method is invoked. Unfortunately, changing signatures like that is not always possible, especially if you are implementing some implementation-independent persistence interfaces, such as a repository, which can also cause cascading changes in all your code, annoying.
Another idea is to put ObjectContext into a thread-local variable. Similar to the way httpcontext.current works.
But these two solutions may not work for you ...
Warning:
Before entering the solution, a series of important warnings are given in order:
Warning # #: This solution is only available if the entity has at least one association.
Warning# #: This solution does not prove desirable performance characteristics, because while we are merely acquiring the context, it also invokes several intermediate methods involving find/COMPUTE:
Warning# #: This method does not apply to an unattached entity. While it is surprising that it can work with notracking queries, you may have thought that notracking entities are inherently detached, but that is strictly not true. Although the notracking query is not tracked, the relationship load still works, and this is what we use.
Warning Check ...
You've been warned!
Workaround:
This solution relies on a separate extension method:
1 public static ObjectContext GetContext ( 2 this ientitywithrelationships entity 3) 4 { 5 if ( entity = = null) 6 throw new ArgumentNullException ("entity"); 7 8 var relationshipmanager = entity. RelationshipManager; 9 var relatedend = relationshipmanager.getallrelatedends () one . FirstOrDefault (); if (relatedend = = null) -throw new Exception ("No relationships Found"), + var query = Relatedend.createsourcequery () as ObjectQuery; if (query = = null), throw new Exception ("The Entity is Detached"), and return to query. Context; 22}
How does this work?
All entities with associations in. NET 3.5 Implement the IEntityWithRelationships interface, in fact even in EF4 if you have a changetracking proxy for POCO entities, This proxy also implements the IEntityWithRelationships interface.
By that you can access to RelationshipManager and get the first relatedend, which is irrelevant to which end we choose, because we don't care about the actual end, it's just a means of getting end.
By Relateend, you create a ojbectouery, but do not execute to load the related entity. If you get null because the entity is separated by ObjectContext, as I said in the warning, you're not lucky.
Finally by the query you can get ObjectContext.
With this extension method, you simply do this:
1 ObjectContext context = Myentity.getcontext ();
Maybe you want a strong type of context ... This is not a problem:
1 Mycontext context = Myentity.getcontext () as Mycontext;
Little one, it's easy!
Tip 25. How to get an entity easily through a primary key
Sometimes, you want to write code like this:
1 var customer = CTX. Customers.getcustomerbyid (5);
And not like this:
1 var customer = CTX. Customers.first (c = c.id = = 5);
In. NET 4.0, it is trivial to achieve this by modifying the T4 template that is generated by the completion code. You only need to add an extension method for each EntityType, as follows:
1 public static Customer Getcustomerbyid (2 This objectquery<t> query, 3 int Id 4) 5 {6 return que Ry. First (c + = C.id = = ID); 9 3
That's good, but is there a way to do this without adding a method to each type?
Universal Solutions
There is indeed a more general approach, though not type-safe, but it can work well.
The core of this idea is to build a query using ESQL. This method can be used in both 3.5 and 4.0:
1 public static T-Get<t, k> (this objectquery<t> query, K key) 2 { 3//get the EntityType 4
entitytype EntityType = query. Context.metadataworkspace 5 . Getcspaceentitytype<t> (); 6 7 if (entityType.KeyMembers.Count! = 1) 8 throw new Exception ("You need to pass all the Keys"); 9 //build The esql one -string esql = string. Format ("it.{ 0} = @{0} ", entitytype.keymembers[0]. Name); //execute The query, return to query. Where (+ ESQL, new ObjectParameter (Entitytype.keymembers[0]. Name, key) . First (); 19}
How does this work?
- This method uses the extension method of getcspaceentitytype<t> () in hint 13 to get the EntityType for T.
- Once we get EntityType, we check and make sure we have the correct number of arguments, that is, if this method only receives a key value, then the EntityType to be validated must have only a single primary key column.
- Next we generate an esql fragment to modify the query's WHERE clause. For example, if you are looking for a customer with the primary key as the ID, we will generate "It.id = @ID".
- Next we create a ObjectParameter parameter that is referenced in esql for the previous step, and the value of the parameter comes from the parameter named key for this method.
- Finally we execute this query and get the result of first (). In 4.0 you should use single () instead, but this method is not supported in 3.5.
So it's done.
Now you can write this:
1 Customer c = ctx. Customers.get (5);
Now you need a warning that this method is not type-safe, so the following code compiles through:
Even if the customer has a primary key of an integer type. You will only notice errors at run time when esql is parsed.
This implementation does not support federated primary keys, but adding more overloads to receive more parameters is fairly easy.
This is left to the reader as an exercise!
Tip 20. How to handle fixed-length primary keys