Skip to content

Commit 2a48b78

Browse files
committed
...
1 parent 08358a8 commit 2a48b78

File tree

3 files changed

+144
-51
lines changed

3 files changed

+144
-51
lines changed

doc-net/controlling-serialization-4x.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ redirect_from: "/doc-net/controlling-serialization.html"
55
---
66
# Controlling Serialization
77

8+
> **NOTE: This page is for Breeze running on .NET 4.x**
9+
810
If you're serving data with Breeze-flavored ASP.NET Web API controllers, you're using the Newtonsoft.Json .NET library to serialize and de-serialize data.
911

1012
All Web API controllers use this library. The native Web API configures it one way. Breeze configures it a little differently.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
layout: doc-net
3+
---
4+
# EFContextProvider
5+
6+
Many application servers use an ASP.NET Web API controller to handle the client's HTTP requests. And they use the Entity Framework (EF) to model and access a SQL database. Breeze has an ***EFContextProvider** component to make controller interactions with EF a little easier. It's basically a wrapper around your application's *ObjectContext* or *DbContext* that mediates between the Breeze controller and EF. It takes care of a lot of routine plumbing.
7+
8+
You can use the EFContextProvider "as is", right out-of-the-box when you're getting started. But you will almost certainly customize it to add your application's business logic. For example, you will want to **[intercept save requests and validate them](#SaveInterception)**. You may want to do something special immediately before or after the provider tells EF to save entities to the database. And you may want to dynamically control how the provider creates the EntityFramework ObjectContext or DbContext at the core of the EF operations.
9+
10+
This topic explores the *EFContextProvider* in greater detail and explains how to subclass it to get the behavior you need.
11+
12+
## Details
13+
14+
Any Breeze application that will be communicating with an Entity Framework backed domain model will contain either an *ObjectContext* or a *DbContext* that looks something like what is shown below:
15+
16+
17+
public partial class NorthwindIBContext : System.Data.Objects.ObjectContext {
18+
// automatically generated code from the EDMX designer
19+
}
20+
21+
Or
22+
23+
public partial class NorthwindIBContext : System.Data.Entity.DbContext {
24+
// Code-First DBSet definitions and any model initialization code
25+
}
26+
27+
This ObjectContext or DbContext will in turn be wrapped in an **EFContextProvider**. The Breeze.WebApi.EFContextProvider class may be found in the Breeze.WebApi dll. An instance of this EFContextProvider is then used to provide services to a standard .NET MVC 4 ApiController (Sytem.Web.Http.ApiControllerApiController). This will look something like:
28+
29+
public class NorthwindIBModelController : System.Web.Http.ApiController {
30+
31+
readnnly EFContextProvider<NorthwindIBContext> ContextProvider =
32+
new EFContextProvider<NorthwindIBContext>();
33+
34+
The remainder of the ApiController will then make use of this instance of the EFContextProvider as a helper object to provide an implementation for each of the ApiController's externally exposed methods. Again something like this:
35+
36+
[BreezeController]
37+
public class NorthwindIBModelController : System.Web.Http.ApiController {
38+
39+
readonly EFContextProvider<NorthwindIBContext> ContextProvider =
40+
new EFContextProvider<NorthwindIBContext>();
41+
42+
[HttpGet]
43+
public String Metadata() {
44+
return ContextProvider.Metadata();
45+
}
46+
47+
[HttpPost]
48+
public SaveResult SaveChanges(JObject saveBundle) {
49+
return ContextProvider.SaveChanges(saveBundle);
50+
}
51+
52+
[HttpGet]
53+
public IQueryable<Customer> Customers() {
54+
return ContextProvider.Context.Customers;
55+
}
56+
57+
[HttpGet]
58+
public IQueryable<Order> Orders() {
59+
return ContextProvider.Context.Orders;
60+
}
61+
62+
[HttpGet]
63+
public IQueryable<Customer> CustomersAndOrders() {
64+
return ContextProvider.Context.Customers.Include("Orders");
65+
}
66+
67+
[HttpGet]
68+
public IQueryable<Customer> CustomersStartingWithA() {
69+
return ContextProvider.Context.Customers
70+
.Where(c => c.CompanyName.StartsWith("A"));
71+
72+
<a name="SaveInterception"></a>In many cases, however, it will be important to "intercept" calls to the EFContextProvider and provide additional logic to be performed at specific points in either the query or save pipeline.
73+
74+
75+
These interception points may be accessed by subclassing the EFContextProvider and overriding specific virtual methods. This will look something like:
76+
77+
public class NorthwindContextProvider: EFContextProvider<NorthwindIBContext> {
78+
public NorthwindContextProvider() : base() { }
79+
80+
protected override bool BeforeSaveEntity(EntityInfo entityInfo) {
81+
// return false if we don't want the entity saved.
82+
// prohibit any additions of entities of type 'Role'
83+
if (entityInfo.Entity.GetType() == typeof(Role)
84+
&& entityInfo.EntityState == EntityState.Added) {
85+
return false;
86+
} else {
87+
return true;
88+
}
89+
}
90+
91+
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) {
92+
// return a map of those entities we want saved.
93+
return saveMap;
94+
}
95+
}
96+
97+
[BreezeController]
98+
public class NorthwindIBModelController : ApiController {
99+
100+
NorthwindContextProvider ContextProvider = new NorthwindContextProvider();
101+
102+
// other code shown earlier with no change.
103+
}
104+
105+
The current interception points for the EFContextProvider are described below. However, we do expect this list to grow as we receive additional feedback from all of you. Please feel free to contribute to our UserVoice regarding any specific extension that you think would be useful here.
106+
107+
## Create your ObjectContext/DbContext Dynamically
108+
109+
The *EFContextProvider calls a virtual method *T CreateContext() when it creates your ObjectContext/DbContext of type 'T'. The base implementation looks like this:
110+
111+
protected virtual T CreateContext() {
112+
return new T();
113+
}
114+
115+
Override and replace that in your *EFContextProvider* subclass and you will be able to make your context of type 'T' just the way you like it.
116+
117+
118+
**N.B.: The base *EFContextProvider* will still do a little post-creation configuration** to make sure it behaves as the EFContextProvider requires; it does not want the context doing any lazy loading or creating proxies. So if 'T' is an *ObjectContext*, the provider will do this:
119+
120+
objCtx.ContextOptions.LazyLoadingEnabled = false;
121+
122+
and if 'T' is a *DbContext it will do this:
123+
124+
dbCtx.Configuration.ProxyCreationEnabled = false;
125+
dbCtx.Configuration.LazyLoadingEnabled = false;
126+
127+
## Why not IDisposable?
128+
129+
Both the EF *ObjectContext* and the *DbContext* implement *IDisposable*. In Microsoft Web API controller samples they dispose of the EF context. But the Breeze.NET *EFContextProvider* is **not disposable** and makes no attempt to dispose of the EF context. Is that a mistake?
130+
131+
132+
We think not for a couple of reasons. First, the *EFContextProvider* should have the same lifetime as the *ApiController* and when the API controller is garbage collected any EF resources should be disposed of by the finalizer. Second, Joseph Albahari, the renowned author of "C# 4.0 in a Nutshell", says you don't have to:
133+
134+
> *Although DataContext/ObjectContext implement IDisposable, you can (in general) get away without disposing instances. Disposing forces the context's connection to dispose - but this is usually unnecessary because [Link to SQL] and EF close connections automatically whenever you finish retrieving results from a query. *[p.352]
135+
136+
137+
Not convinced? You have direct access to the *Context* object; cast it to *IDisposable* and call *Dispose* yourself.
138+

doc-net/persistencemanager-core.md

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ layout: doc-net
33
---
44
# PersistenceManager
55

6-
> **NOTE: This page is for Breeze running on .NET Core**
7-
6+
> **NOTE: This page is for Breeze running on .NET Core**<br><br>
87
> [Go here for .NET 4.x version](/doc-net/contextprovider-4x)
98
109
The `PersistenceManager` is a server-side component for managing data access and business validation with .NET technologies.
@@ -122,7 +121,7 @@ For example, we could calculate an entity property on the server in a <a href="#
122121
var cust = (Customer) info.Entity;
123122
cust.MOL = meaningOfLife();
124123

125-
// Add property to map so that ContextProvider updates db
124+
// Add property to map so that PersistenceManager updates db
126125
// original values don't matter
127126
info.OriginalValuesMap["MOL"] = null;
128127
}
@@ -140,7 +139,7 @@ Many apps set audit fields on the server for entities that have them. You might
140139
auditable.Modified = DateTime.UtcNow;
141140
auditable.UserId = CurrentUser.Id;
142141

143-
// Add property to map so that ContextProvider updates db
142+
// Add property to map so that PersistenceManager updates db
144143
// original values don't matter
145144
info.OriginalValuesMap["Modified"] = null;
146145
info.OriginalValuesMap["UserId"] = null;
@@ -255,52 +254,6 @@ Most developers rely on a pre-existing derived class such as the `EFPersistenceM
255254

256255
You will override it if you write your own `PersistenceManager`.
257256

258-
<a name="HelperMethods"></a>
259-
<a name="helpermethods"></a>
260-
261-
## PersistenceManager helper methods<a name="ContextProvidermethods"></a>
262-
263-
The `PersistenceManager` exposes public methods to help in the implementation of your virtual method overrides.
264-
265-
The following helpers enable re-use of database connections; such re-use reduces the need for distributed transactions:
266-
267-
**GetDbConnection** provides access to the underlying connection to the database. This is the same connection that PersistenceManager uses to save the entity changes to the database. Re-using this same connection allows you to perform queries and updates without a separate connection which might cause a distributed transaction. The return value may be a EntityConnection, SqlConnection, etc. depending upon the specific PersistenceManager implementation. For Entity Framework, use the `EntityConnection` and `StoreConnection` properties below.
268-
269-
**EntityConnection** is a read-only property that provides access to the EntityConnection used by the DbContext/ObjectContext. This is useful when you want to create a second DbContext with the same connection:
270-
271-
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
272-
{
273-
var context2 = new MyDbContext(EntityConnection); // create a DbContext using the existing connection
274-
var orders = saveMap[typeof(Order)]; // get the EntityInfo for all Orders in the saveMap
275-
foreach(var orderInfo in orders)
276-
{
277-
var order = orderInfo.Entity as Order; // get the order that came from the client
278-
var query = context2.Orders.Where(o => o.orderID == order.orderID);
279-
var oldOrder = query.FirstOrDefault(); // get the existing order from the database
280-
// compare values of order and oldOrder to see what has changed
281-
// because we have a business rule that only certain changes are allowed
282-
// ...
283-
}
284-
}
285-
286-
**StoreConnection** is a read-only property that provides access to the StoreConnection used by the DbContext/ObjectContext. This may be a SqlConnection, OracleConnection, etc. depending upon the provider. Use StoreConnection to do SQL queryies and updates directly to the database:
287-
288-
protected override void AfterSaveEntities(Dictionary<Type, List<EntityInfo>>
289-
saveMap, List<KeyMapping> keyMappings)
290-
{
291-
// simplistic example of logging the created entity IDs
292-
var text = ("insert into AuditAddedEntities (CreatedOn, EntityType, EntityKey) values ('{0}', '{1}', {2})";
293-
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
294-
var conn = StoreConnection; // use the existing StoreConnection
295-
var cmd = conn.CreateCommand();
296-
foreach (var km in keyMappings)
297-
{
298-
// put the real value of the key into the audit table
299-
cmd.CommandText = String.Format(text, time, km.EntityTypeName, km.RealValue);
300-
cmd.ExecuteNonQuery();
301-
}
302-
}
303-
304257
<a name="TransactionSettings"></a>
305258

306259
## Wrapping the entire save process in a transaction
@@ -312,7 +265,7 @@ But the actions of the `BeforeSave...` and `AfterSaveEntities` methods fall ***o
312265
If you need to include `BeforeSave...` and `AfterSaveEntities` processing within the save transaction, you must supply the optional `TransactionSettings` parameter to the `SaveChanges` call.
313266

314267
Here's an example:
315-
268+
[HttpPost]
316269
public SaveResult SaveWithTransactionScope(JObject saveBundle) {
317270
var txSettings = new TransactionSettings() { TransactionType = TransactionType.TransactionScope };
318271

0 commit comments

Comments
 (0)