Entity Framework skill series 13-Tip 51-55

Source: Internet
Author: User
Tags list of attributes

 

Tip 51. How to load EF metadata in any form of stream

In tip 45, I showed how to generate a connection string at runtime, which is pretty nice.

The problem is that it relies on metadata files (. csdl. ssdl. msl) stored on local disks.

But what if these files are stored on the web server or in similar locations, and you even have no permission to access the local file system and cannot copy them to the local machine?

You can also load the metadata in the stream. This prompt will show you how to do it.

Step 1: Obtain XmlTextReaders for CSDL, MSL, and SSDL:

This can be as simple as possible, such as 'new XmlTextReader (url )'.

However, in this example, I want to show you how to perform this operation using string variables. Of course, you can obtain this string from any place:

String csdl = "... ";
String ssdl = "... ";
String msl = "... ";

Var csdlReader = new StringReader (csdl );
Var ssdlReader = new StringReader (ssdl );
Var mslReader = new StringReader (msl );

Var csdlXmlReader = new XmlTextReader (csdlReader );
Var ssdlXmlReader = new XmlTextReader (ssdlReader );
Var mslXmlReader = new XmlTextReader (mslReader );

Step 2: Create ItemCollections:

Next, you need to prepare an EdmItemCollection for CSDL, A StoreItemCollection for SSDL, and a StorageMappingItemCollection for MSL:

Var edmItemCollection = new EdmItemCollection (
New [] {csdlXmlReader}
);
Var storeItemCollection = new StoreItemCollection (
New [] {ssdlXmlReader}
);
Var storeMappingItemCollection =
New StorageMappingItemCollection (
EdmItemCollection,
StoreItemCollection,
New [] {mslXmlReader}
);

The only object remotely interested is StorageMappingItemCollection, which, together with MSL, requires other ItemCollection to verify the ing.

Step 3: Create a MetadataWorkspace:

Next, you need to combine these itemcollections into a MetadataWorkspace:

Var mdw = new MetadataWorkspace ();
Mdw. RegisterItemCollection (edmItemCollection );
Mdw. RegisterItemCollection (storeItemCollection );
Mdw. RegisterItemCollection (storeMappingItemCollection );

Step 4: Create an EntityConnection:

Finally, we need an EntityConnection. To create an EntityConnection, we need a local database connection. In general, this is a SqlConnection, but because EF has a program model, this connection can also be another connection string such as the Oracle database:

Var sqlConnectionString = @ "Data Source =. \ SQLEXPRESS; Ini... ";
Var sqlConnection = new SqlConnection (sqlConnectionString );
Var entityConnection = new EntityConnection (
MetadataWorkspace,
SqlConnection
);

Step 5: Create ObjectContext and use it as needed

The last step is to use the EntityConnection you just created to construct your ObjectContext and use it as usual:

Using (var ctx = new ProductCategoryEntities (entityConnection ))
{
Foreach (var product in ctx. Products)
Console. WriteLine (product. Name );
}

That's it... Not necessarily easy. Once you know how to do it, it's easy.

Tip 52. How to reuse the type in the data service client

By default, when you add a data service reference, you will get the automatically generated code, which contains a strong type of DataServiceContext and all ResourceType classes.

Click "show all files" to view the automatically generated code in the project:

Then expand yourData Service reference, And then expand the subordinateReference. datasvcmapAnd then openReference. csFile:

Step 1-disable code generation

If you want to reuse some existing classes, you need to disable code generation.

This is quite easy-selectReference. datasvcmapFileAttributeAnd then emptyCustom ToolProperty. The default value is 'dataserviceclientgenerator '.

Step 2-create a strong DataServiceContext:

When code generation is disabled, we also lose the strong DataServiceContext that makes programming more convenient.

You can write your DataServiceContext like this, and the code is quite easy:

Public class SampleServiceCtx: DataServiceContext
{
Public SampleServiceCtx (Uri serviceRoot): base (serviceRoot ){
Base. ResolveName = ResolveNameFromType;
Base. ResolveType = ResolveTypeFromName;
}
Protected Type ResolveTypeFromName (string typeName)
{
If (typeName. StartsWith ("Sample ."))
{
Return this. GetType (). Assembly. GetType (
TypeName. Replace ("Sample.", "Tip52 .")
False );
}
Return null;
}
Protected string ResolveNameFromType (Type clientType)
{
If (clientType. Namespace. Equals ("Tip52 "))
{
Return "Sample." + clientType. Name;
}
Return null;
}
Public DataServiceQuery <Product> Products {
Get {
Return base. CreateQuery <Product> ("Products ");
}
}
}

Note that DataServiceQuery is returned for the Product attribute. <Product>, WhereProductIs the type we are trying to reuse.

The key to this work is to map the data service Resource typeName to a client Type code, and vice versa.

This ing is controlled by two functions. We tell DataServiceContext In the constructor. You can see that in this example, we simply use the 'tid52' namespace of the client to the 'sample' namespace on the server side.

Step 3-try:

Once you have set up a parser, you can easily reuse existing types:

Var root = new Uri ("http: // localhost/Tip52/sample. svc ");
Var ctx = new SampleServiceCtx (root );
Foreach (Product p in ctx. Products)
{
Console. WriteLine ("{0} costs {1}", p. Name, p. Price );
P. Price ++ = 0.30 M; // Cross the board price increases!
Ctx. UpdateObject (p );
}
Ctx. SaveChanges ();

That's it.

Warning:

Only when the client and the server have the same property names can this work, because there is no way to rename the property.

Likewise, because of the way objects work in the data service client, this will not work when your class has a Reference attribute and a backing foreign key attribute and the class is automatically fixed-up to make the two values consistent.

Tip 53. How to debug ef poco ing

If you try to use the POCO class in EF4.0, it is easier to encounter problems when you map the model to the CLR class.

If you encounter any annoying problem here, the best way to figure out the problem is to explicitly load metadata for the POCO type.

For example, if the Product is of the POCO type and you encounter problems when making it work, you can try this code to find out what the problem is:

Using (MyContext ctx = new MyContext ())
{
Ctx. MetadataWorkspace. LoadFromAssembly (
Typeof (Product). Assembly,
MappingWarning
);
}

MappingWarning may be a method that accepts any string without returning a value, as shown in the following code:

Public void MappingWarning (string warning)
{
Console. WriteLine (warning );
}

After completing this step, EF will traverse the types in your assembly and try to check whether they match the types in the conceptual model, for some reason, if a CLR type is identified and subsequently excluded-not a valid match-your method will be called and the warning in our example will pop up to the console.

Tip 54. How to use a declarative Expression to improve performance

Background:

When I recently wrote a series of blog posts about data service providers, I stopped writing this kind of code snippet that uses reflection to copy attribute values from one object to another:

Foreach (var prop in resourceType
. Properties
. Where (p => (p. Kind & ResourcePropertyKind. Key)
! = ResourcePropertyKind. Key ))
{
Var clrProp = clrType
. GetProperties ()
. Single (p => p. Name = prop. Name );
Var defaultPropValue = clrProp
. GetGetMethod ()
. Invoke (resetTemplate, new object [] {});
ClrProp
. GetSetMethod ()
. Invoke (resource, new object [] {defaultPropValue });
}

Problem:

This Code contains at least two major issues.

1. Search for attributes and methods in a loop through reflection

2. Call those methods using reflection in a loop.

We can store the get/set methods of all attributes in some data structures that can be cached to solve the problem (1 ).

But fixing the problem (2) requires a little more skill. To make the code truly generic, you need something like lightweight code generation.

Solution:

Thanks to the declaration expression added in. NET4.0. This means that you can now create expressions that describe multi-line declarations, and those statements can be executed like other tasks.

Polat says 'ha '...

I searched the internet and found Bart's excellent blog post on declarative expressions. This is enough to make me very excited.

Step 1-familiarize yourself with the API

With the enthusiasm for discovering new things, I decided to first try something simple and try to convert this delegate object into an expression:

Func <int> func = () => {
Int n;
N = 2;
Return n;
};

If you only do this, the result is terrible:

Expression <Func <int> expr = () => {
Int n;
N = 2;
Return n;
};

Unfortunately, C # does not support this method. Instead, you need to manually construct this expression, as shown in the following code:

Var n = Expression. Variable (typeof (int ));
Var expr = Expression. Lambda <Func <int>
(
Expression. Block (
// Int n;
New [] {n },
// N = 2;
Expression. Assign (
N,
Expression. Constant (2)
),
// Return n;
N
)
);

It's quite easy.

Step 2-demonstrate that we can specify an attribute

Now we have tried this API and it is time to solve our problem.

What we need is a function that sets one or more attributes of an object to modify an object (in this example, product), like this:

Action <Product> baseReset = (Product p) = >{ p. Name = null ;};

This Code uses the new expression API to create an equivalent action ):

Var parameter = Expression. Parameter (typeof (Product ));
Var resetExpr = Expression. Lambda <Action <Product> (
Expression. Block (
Expression. Assign (
Expression. Property (parameter, "Name "),
Expression. Constant (null, typeof (string ))
)
),
Parameter
);
Var reset = resetExpr. Compile ();
Var product = new Product {ID = 1, Name = "Foo "};
Reset (product );

When the reset (product) is called, the product Name becomes null.

Step 3-set all non-key attributes

Now we only need to create a function. when it receives a specific CLR type, it will create an expression to reset all non-key attributes.

In fact, collecting a list of attributes and their expected values is not very important for this topic, so let's imagine that we have put the obtained information in a dictionary, as shown in the following code:

Var properties = new Dictionary <PropertyInfo, object> ();
Var productProperties = typeof (Product). GetProperties ();
Var nameProp = productProperties. Single (p => p. Name = "Name ");
Var costProp = productProperties. Single (p => p. Name = "Cost ");
Properties. Add (nameProp, null );
Properties. Add (costProp, 0.0 M );

With this data structure, our job is to create an expression that has the same effect as the following Action:

Action <Product> baseReset = (Product p) => {
P. Name = null;
P. Cost = 0.0 M;
};

First, I need to create all the value assignment expressions:

Var parameter = Expression. Parameter (typeof (Product ));
List <Expression> assignments = new List <Expression> ();
Foreach (var property in properties. Keys)
{
Assignments. Add (Expression. Assign (
Expression. Property (parameter, property. Name ),
Expression. Convert (
Expression. Constant (
Properties [property],
Property. PropertyType
)
)
);
}

Next we inject the value assignment expression into a block in Lambda, compile the entire project, and test our new function:

Var resetExpr = Expression. Lambda <Action <Product> (
Expression. Block (
Assignments. ToArray ()
),
Parameter
);
Var reset = resetExpr. Compile ();
Var product = new Product {ID = 1, Name = "Foo", Cost = 34.5 M };
Reset (product );
Debug. Assert (product. Name = null );
Debug. Assert (product. Cost = 0.0 M );

As expected, this is very useful.

To solve this problem, I am going to put it in the updated blog. We need a type-based dictionary which we can use to store a specific type of reset action. In this case, if a Type reset action is not found, we only need to create a new...

And the performance problem should already be J.

Tip 55. How to expand an IQueryable through packaging

In the past few years, I have often wanted to go deep into the underlying layer to see what happened inside IQueryable, but I have never found a simple method, at least so far.

It is interesting to dive into and do something like this, so you can:

L Log before query execution

L rewrite the expression. For example, replace an unsupported expression such as EF, LINQ to SQL, and LINQ to Objects with a supported expression.

In any case, I'm glad to check some sample code completed by Vitek (BTW, his new blog here) not long ago, I realized that I could generalize it to create an InterceptedQuery <> with an InterceptingProvider.

The basic idea is that you can use IQueryable as follows:

Public IQueryable <Customer> MERs
{
Get {
Return InterceptingProvider. CreateQuery (_ ctx. MERs, visitor );
}
}

Here, the visitor is an ExpressionVisitor that will be accessed. It may overwrite any query composed of MERs before committing the query to the underlying layer (in this example, Entity Frameork.

Implementation

This implementation is quite simple thanks to Vitek.

Let's start implementing InterceptedQuery <>. In fact, this is not worth mentioning:

Public class InterceptedQuery <T>: IOrderedQueryable <T>
{
Private Expression _ expression;
Private InterceptingProvider _ provider;

Public InterceptedQuery (
InterceptingProvider provider,
Expression expression)
{
This. _ provider = provider;
This. _ expression = expression;
}
Public IEnumerator <T> GetEnumerator ()
{
Return this. _ provider. ExecuteQuery <T> (this. _ expression );
}
IEnumerator IEnumerable. GetEnumerator ()
{
Return this. _ provider. ExecuteQuery <T> (this. _ expression );
}
Public Type ElementType
{
Get {return typeof (T );}
}
Public Expression
{
Get {return this. _ expression ;}
}
Public IQueryProvider
{
Get {return this. _ provider ;}
}
}

Next is InterceptedProvider, which is more interesting:

Public class InterceptingProvider: IQueryProvider
{
Private IQueryProvider _ underlyingProvider;
Private Func <Expression, Expression> [] _ visitors;

Private InterceptingProvider (
IQueryProvider underlyingQueryProvider,
Params Func <Expression, Expression> [] visitors)
{
This. _ underlyingProvider = underlyingQueryProvider;
This. _ visitors = visitors;
}

Public static IQueryable <T> Intercept <T> (
IQueryable <T> underlyingQuery,
Params ExpressionVisitor [] visitors)
{
Func <Expression, Expression> [] visitFuncs =
Visitors
. Select (v => (Func <Expression, Expression>) v. Visit)
. ToArray ();
Return Intercept <T> (underlyingQuery, visitFuncs );
}

Public static IQueryable <T> Intercept <T> (
IQueryable <T> underlyingQuery,
Params Func <Expression, Expression> [] visitors)
{
InterceptingProvider provider = new InterceptingProvider (
UnderlyingQuery. Provider,
Visitors
);
Return provider. CreateQuery <T> (
UnderlyingQuery. Expression );
}
Public IEnumerator <TElement> ExecuteQuery <TElement> (
Expression expression)
{
Return _ underlyingProvider. CreateQuery <TElement> (
InterceptExpr (expression)
). GetEnumerator ();
}
Public IQueryable <TElement> CreateQuery <TElement> (
Expression expression)
{
Return new InterceptedQuery <TElement> (this, expression );
}
Public IQueryable CreateQuery (Expression expression)
{
Type et = TypeHelper. FindIEnumerable (expression. Type );
Type qt = typeof (InterceptedQuery <>). MakeGenericType (et );
Object [] args = new object [] {this, expression };

ConstructorInfo ci = qt. GetConstructor (
BindingFlags. NonPublic | BindingFlags. Instance,
Null,
New Type [] {
Typeof (InterceptingProvider ),
Typeof (Expression)
},
Null );

Return (IQueryable) ci. Invoke (args );
}
Public TResult Execute <TResult> (Expression expression)
{
Return this. _ underlyingProvider. Execute <TResult> (
InterceptExpr (expression)
);
}
Public object Execute (Expression expression)
{
Return this. _ underlyingProvider. Execute (
InterceptExpr (expression)
);
}
Private Expression InterceptExpr (Expression expression)
{
Expression exp = expression;
Foreach (var visitor in _ visitors)
Exp = visitor (exp );
Return exp;
}
}

Note that the query is executed at any time. we intercept the current expression and call all registered 'visitors 'in sequence, and then execute the final expression on the underlying provider.

Implementation Instructions:

In the infrastructure, our implementation uses Func <Expression, Expression> instead. system. linq. expressions. expressionVisitor, mainly because. NET is a little late for the community, so currently many visitors are not inherited from the System. linq. expressions. expressionVisitor.

You just need to take a look at Matt Warren's excellent IQToolkit, which contains many examples.

However, we would like to encourage the use of System. Linq. Expressions. ExpressionVisitor, so there is a convenient overload for this.

Remember that if you wrap the Entity Framework and perform any complicated rewrite queries, you need to avoid any calling expressions-see Colin's blog post.

One of IQToolbox's useful visitor is called ExpressionWriter and some of its plug-ins-It exposes all constructors and basic Visit methods-you can use it to convert expressions before the Entity Framework query is executed output to the console:

CustomersContext _ ctx = new CustomersContext ();
ExpressionWriter _ writer = new ExpressionWriter (Console. Out );
Public IQueryable <Customer> MERs {
Get {
Return InterceptingProvider. Intercept (_ ctx. MERs, _ writer. Visit );
}
}

You also need to pay attention to the TypeHelper class that is useful in IQToolbox in the non-strong type method CreateQuery. It does help us to create a correct example of the generic InterceptedQuery <> type.

Thanks again, Matt!

Combine these:

The last example is displayed.

Here I simulate the work of the WCF/ADO. NET data service to process this request:

GET ~ /People /? $ Filter = Surname eq 'James'

If the backend is a non-strong data service provider.

// Create some data
List <Dictionary <string, object> data = new List <Dictionary <string, object> ();
Data. Add (
New Dictionary <string, object >{{ "Surname", "James" },{ "Firstname", "Alex "}}
);
Data. Add (
New Dictionary <string, object >{{ "Surname", "Guard" },{ "Firstname", "Damien "}}
);
Data. Add (
New Dictionary <string, object >{{ "Surname", "Meek" },{ "Firstname", "Colin "}}
);
Data. Add (
New Dictionary <string, object >{{ "Surname", "Karas" },{ "Firstname", "Vitek "}}
);
Data. Add (
New Dictionary <string, object >{{ "Surname", "Warren" },{ "Firstname", "Matt "}}
);
// Create a couple of visitors
Var writer = new ExpressionWriter (Console. Out );
Var dspVisitor = new DSPExpressionVisitor ();

// Intercept queries to the L2O IQueryable
Var queryRoot = InterceptingProvider. Intercept (
Data. AsQueryable (), // L2O's iqueryable
Writer. Visit, // What does the expression look like first?
DspVisitor. Visit, // Replace GetValue ().
Writer. Visit // What does the expression look like now?
);

// Create a Data Services handle for the Surname property
ResourceProperty surname = new ResourceProperty (
"Surname ",
ResourcePropertyKind. Primitive,
ResourceType. GetPrimitiveResourceType (typeof (string ))
);

// Create a query without knowing how to access the Surname
// From x.
Var query =
From x in queryRoot
Where (string) DataServiceProviderMethods. GetValue (x, surname ))
= "James"
Select x;

// Execute the query and print some results
Foreach (var x in query)
Console. WriteLine ("Found Match: {0 }",
X ["Firstname"]. ToString ()
);

As you can see, we have some People data in a dictionary list, and we try to find the person whose last name is 'James.

The problem is that data service does not know how to get the last name in a dictionary. Therefore, it injects a request to DataServiceProviderMethods. GetValue (..).

Okay.

Unfortunately, the LINQ to Objects query provider does not have enough context information to process this query-Blindly calling GetValue in this query will fail.

Therefore, we intercept this query. The DSPExpressionVisitor (I will not go deep here) simply adds

DataServiceProviderMethods. GetValue (x, surname)

Replace it with the following:

X [surname. Name]

If you look for the form, you can see that this is the same as the following:

X ["Surname"]

When the entire expression is accessed, you will eventually get the following query:

Var query =
From x in queryRoot
Where (string) x [surname. Name]) = "James"
Select x;

This can be well processed by using this Linq to Objects!

Summary

This is a general purpose solution that allows an IQueryable to be superimposed on the other and translated/rewritten/recorded before the query expression is passed into the underlying provider.

Enjoy.

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.