I have a WCF service which is used to add tenders to the database, which is MS SQL Server 2005. WCF uses LINQ-to-SQL.
Each tender can have a lot of documents and a lot of items. The customers can add one object per service call. That is, the do something like this:
TendersServiceClient service = new TenderServiceClient();
service.BeginTransaction();
// Adding a new tender
service.AddTender(TenderDTO tenderInfo);
// Adding tender's documents
foreach (DocumentDTO documentInfo in documents)
service.AddTenderDocument(tenderInfo.TenderID, documentInfo);
// Adding tender's items
foreach (ItemDTO itemInfo in items)
service.AddTenderItem(tenderInfo.TenderID, itemInfo);
service.CommitTransaction();
Notice the BeginTransaction() and CommitTransaction(). That is, all the above procedure must either succeed completely or be rolled back completely. For example, if one of the items couldn't be inserted, then the whole tender shouldn't exist...
So the question is how do I implement this kind of transaction. The problem is that WCF is stateless, of course. So new DataContext is created for each service call. If I use a static DataContext instead, then I'll be able to use its built-in transactions capabilities, but then how can I handle other customers who can try to add another tender in the same time (they must be, of course, outside this transaction)?
So please help me with some kind of design pattern to achieve this. I am free to change the code both of the service and of the client, so feel free with your suggestions =)
Do you control the interface of the service?
If so, surely the elegant solution is for the service to accept an aggregate Tender object in a single method rather than having the chatty methods that you have now. The Tender would then have Items and Documents as subcollections, and the data access code could handle all the updates in single transaction much more easily.
Unless I am misunderstanding, it seems very similar to an Order/OrderDetails scenario, where the same logic applies.
First off, you would have to use transactional service calls here - and since you have a "initializing" call, a bunch of intermediary calls, and then possibly one to end all the calls, I would recommend you have a look at the "IsInitiating" and "IsTerminating" attributes on the OperationContract for the methods - this will allow you to specify one method to start your session, and one that will finish it.
Next, make sure to configure your service as a transactional service by putting the "TransactionFlow" attribute on either the service or all operations - whichever you prefer.
In your client code, you'll have to use System.Transactions to create a TransactionScope, which will wrap your service calls. This is a lightweight or a fully two-phased distributed transaction coordinator - depending on what your calls do in detail.
Something along those lines:
1) Mark your binding as transactional:
<bindings>
<wsHttpBinding>
<binding name="TransactionalWsHttp" transactionFlow="true" />
</wsHttpBinding>
</bindings>
2) Service contract:
[ServiceContract]
public interface ITenderService
{
// method to start your submission process
[OperationContract(IsInitiating=true, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void StartTenderProcess();
// all your other methods "in between"
[OperationContract(IsInitiating=false, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void AddTender()
[OperationContract(IsInitiating=false, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void AddTenderDocument()
[OperationContract(IsInitiating=false, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void AddTenderItem()
...
// method to end your submission process
[OperationContract(IsInitiating=false, IsTerminating=true)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void FinishTenderProcess();
}
3) In your client code:
using (TransactionScope ts = new TransactionScope())
{
serviceClient.StartTenderProcess();
.....
serviceClient.FinishTenderProcess();
ts.Complete(); // Transaction Commit
}
Does that help you for the time being to get you started ??
Marc
Related
I'm trying to get my head around WCF transactions but struggling with one concept. What is the difference in behaviour of
TransactionFlow(TransactionFlowOption.Mandatory)
and
OperationBehavior(TransactionScopeRequired = true)
As far as I can tell from the documentation they are kind of two ways of achieving the same thing, but I cannot really figure it out.
Cheers,
Stephen
When a transaction scope is required for a method, WCF will create a transaction to execute the method's logic. Transaction flows allow you to chain transactions, so that multiple service methods that require transactions can all be joined into a single transaction instead of separate transactions.
Consider the following simple setup:
[OperationBehavior(TransactionScopeRequired = true)]
public void ServiceMethod1()
{
// Executes ServiceMethod2 and ServiceMethod3
}
[OperationBehavior(TransactionScopeRequired = true)]
[TransactionFlow(TransactionFlowOption.Mandatory)]
public void ServiceMethod2()
[OperationBehavior(TransactionScopeRequired = true)]
[TransactionFlow(TransactionFlowOption.Mandatory)]
public void ServiceMethod3()
ServiceMethod1 requires a transaction, so WCF spins up a transaction and then calls ServiceMethod2 and ServiceMethod3. Although these services also require a transaction, they are flowed. So instead of spinning up new transactions, they adopt the same transaction that was created by ServiceMethod1. If anything fails along the way or the transaction isn't committed, the entire chain gets rolled back. If the transactions were not flowed, each method would spin up a transaction that acted on it's own and had no effect on other methods being called.
I have a WCF service that returns a class that implements IExtensibleDataObject. I need to add a new field to this class. I updated the DataContract interface and made the change to the class. Now when I try to run my client application I get the following error:
Could not load file or assembly
'xxx.yyyy.zzzz, Version=1.3.9.26111,
Culture=neutral,
PublicKeyToken=b09e2f3e9b5894f0' or
one of its dependencies. The located
assembly's manifest definition does
not match the assembly reference.
(Exception from HRESULT: 0x80131040)
The AssemblyVersion of the WFC class has been changed - does that break client?
EDIT:
There are clients in production that use this service. I do not want to make them update their service reference and redeploy their clients for this simple change if possible.
It depends on how you have set up your data contract.
In WCF, using all the defaults, your service will use the DataContractSerializer (DCS) to serialize your object into XML. The DCS will serialize your fields in order - first the public ones, then the private ones. Within each visibility group, it will order the fields/properties alphabetically by name.
Thus, if you introduce a new public property MiddleName and you already had FirstName and LastName, you would be fine: the old XML would have been
<YourObject>
....
<FirstName>.....</FirstName>
<LastName>.....</LastName>
</YourObject>
and your new one would just simply add a new property at the end:
<YourObject>
....
<FirstName>.....</FirstName>
<LastName>.....</LastName>
<MiddleName>....</MiddleName>
</YourObject>
Such an update "add something at the end" should work just fine, the DCS will just simply ignore additional tags in the XML.
However: had you introduced a property called Gender, that would be stuck between <FirstName> and <LastName> and would thus break the order of the data in your XML and thus would break the data contract - no "old" client will be able to call your new service.
In order to take control of this, you can put specific Order= attributes on your data members in your data contract:
[DataContract]
public class SomeAddress
{
[DataMember(Order=0)]
public string FirstName;
[DataMember(Order=1)]
public string LastName;
}
Then you could easily add a new property - just add it to the end of the list!
[DataContract]
public class SomeAddress
{
[DataMember(Order=0)]
public string FirstName;
[DataMember(Order=1)]
public string LastName;
[DataMember(Order=2)]
public string Gender;
}
So by using the Order= attribute on your data contracts, you can take control of your XML layout, and you can make simple extensions of existing data contracts non-breaking updates.
For more background and in-depth know-how, you ought to read Serialization in Windows Communication Foundation on MSDN Magazine's web site - highly recommended.
From your service reference context menu choose... "Update Service Reference"
I am working on WCF Data service which imported stored procedure, as below.
[WebGet]
public List<GetMTSearchResultTest_Result> GettMTSearchResultTest()
{
MediaMarketResearch_PRODEntities ent = new MediaMarketResearch_PRODEntities();
return ent.GetMTSearchResultTest().ToList();
}
when i consuming this in my client application it says error as "The closed type MMRClient.MMRServiceReference.GetMTSearchResultTest_Result does not have a corresponding element settable property."
I am getting this error while bind to the grid view as below.
DataServiceContext context = new DataServiceContext(new Uri("http://localhost:4131/MMRDataService.svc/"));
IEnumerable<GetMTSearchResultTest_Result> empResult = context.Execute<GetMTSearchResultTest_Result>(new Uri("http://localhost:4131/MMRDataService.svc/GettMTSearchResultTest"));
GridView1.DataSource = empResult;
GridView1.DataBind();
Note: I imported this stored proc as complex type.
Please advice me on this.
Regards,
Jaydeep
I think this link may help you (see the selected answer).
Essentially, what the solution may be is to create a partial class for GetMTSearchResultTest_Result and decorate it with a DataServiceKey attribute, providing a non-nullable column that acts as a primary key (although I don't think it has to be unique).
So your partial class would look something like:
[DataServiceKey("YourKeyColumnName")]
public partial class GetMTSearchResultTest_Result {
}
If you're just doing reads, I don't think you'll need any implementation.
Hopefully this works. Let me know if there are issues/questions and I'll update accordingly.
You can always make a new service reference to a non data service. That is to a normal WCF service. You can simply have a [ContractOperation] returning a list of the troubled "complex types" and that's it.
This way you would have two services the original data service and a new normal WCF service. But this shouldn't be such an issue. You don't have to make the troubled "complex type" as a Entity.
We have a situation where we have multiple databases with identical schema, but different data in each. We're creating a single session factory to handle this.
The problem is that we don't know which database we'll connect to until runtime, when we can provide that. But on startup to get the factory build, we need to connect to a database with that schema. We currently do this by creating the schema in an known location and using that, but we'd like to remove that requirement.
I haven't been able to find a way to create the session factory without specifying a connection. We don't expect to be able to use the OpenSession method with no parameters, and that's ok.
Any ideas?
Thanks
Andy
Either implement your own IConnectionProvider or pass your own connection to ISessionFactory.OpenSession(IDbConnection) (but read the method's comments about connection tracking)
The solution we came up with was to create a class which manages this for us. The class can use some information in the method call to do some routing logic to figure out where the database is, and then call OpenSession passing the connection string.
You could also use the great NuGet package from brady gaster for this. I made my own implementation from his NHQS package and it works very well.
You can find it here:
http://www.bradygaster.com/Tags/nhqs
good luck!
Came across this and thought Id add my solution for future readers which is basically what Mauricio Scheffer has suggested which encapsulates the 'switching' of CS and provides single point of management (I like this better than having to pass into each session call, less to 'miss' and go wrong).
I obtain the connecitonstring during authentication of the client and set on the context then, using the following IConnectinProvider implementation, set that value for the CS whenever a session is opened:
/// <summary>
/// Provides ability to switch connection strings of an NHibernate Session Factory (use same factory for multiple, dynamically specified, database connections)
/// </summary>
public class DynamicDriverConnectionProvider : DriverConnectionProvider, IConnectionProvider
{
protected override string ConnectionString
{
get
{
var cxnObj = IsWebContext ?
HttpContext.Current.Items["RequestConnectionString"]:
System.Runtime.Remoting.Messaging.CallContext.GetData("RequestConnectionString");
if (cxnObj != null)
return cxnObj.ToString();
//catch on app startup when there is not request connection string yet set
return base.ConnectionString;
}
}
private static bool IsWebContext
{
get { return (HttpContext.Current != null); }
}
}
Then wire it in during NHConfig:
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.Provider<DynamicDriverConnectionProvider>() //Like so
So I currently have 2 WSDLs added as Service References in my solution. They look like this in my app.config file (I removed the "bindings" field, because it's uninteresting):
<system.serviceModel>
<client>
<endpoint address="http://localhost:8080/query-service/jse" binding="basicHttpBinding" bindingConfiguration="QueryBinding" contract="QueryService.Query" name="QueryPort" />
<endpoint address="http://localhost:8080/dataimport-service/jse" binding="basicHttpBinding" bindingConfiguration="DataImportBinding" contract="DataService.DataImport" name="DataImportPort" />
</client>
</system.serviceModel>
When I utilize a WSDL, it looks something like this:
using (DataService.DataClient dClient = new DataService.DataClient())
{
DataService.importTask impt = new DataService.importTask();
impt.String_1 = "someData";
DataService.importResponse imptr = dClient.importTask(impt);
}
In the "using" statement, when instantiating the DataClient object, I have 5 constructors available to me. In this scenario, I use the default constructor:
new DataService.DataClient()
which uses the built-in Endpoint Address string, which I assume is pulled from app.config. But I want the user of the application to have the option to change this value.
1) What's the best/easiest way of programatically obtaining this string?
2) Then, once I've allowed the user to edit and test the value, where should I store it?
I'd prefer having it be stored in a place (like app.config or equivalent) so that there is no need for checking whether the value exists or not and whether I should be using an alternate constructor. (Looking to keep my code tight, ya know?)
Any ideas? Suggestions?
EDIT
Maybe I should ask about these Alternate constructors as well.
For example, one of them looks like this:
new DataService.DataClient(string endPointConfigurationName,
string remoteAddress)
What values could get passed for "endPointConfigurationName" and "remoteAddress"?
EDIT2
Answering my own questions here, the "endPointConfigurationName" appears to be the same as the "name" in the app.config XML and the "remoteAddress" is formatted the same as "endpoint address" in the app.config XML.
Also! The answer to my first question about getting the EndPointAddresses is the following:
ClientSection clSection =
ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
ChannelEndpointElementCollection endpointCollection =
clSection.ElementInformation.Properties[string.Empty].Value as ChannelEndpointElementCollection;
Dictionary<string, string> nameAddressDictionary =
new Dictionary<string, string>();
foreach (ChannelEndpointElement endpointElement in endpointCollection)
{
nameAddressDictionary.Add(endpointElement.Name,
endpointElement.Address.ToString());
}
EDIT3
Ok, I think I've figured out the 2nd half (and thus, full solution) to my problem. I found this on another website and I modified it to meet my needs:
Configuration configuration;
ServiceModelSectionGroup serviceModelSectionGroup;
ClientSection clientSection;
configuration =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
serviceModelSectionGroup =
ServiceModelSectionGroup.GetSectionGroup(configuration);
clientSection = serviceModelSectionGroup.Client;
foreach (ChannelEndpointElement endPt in clientSection.Endpoints)
{
MessageBox.Show(endPt.Name + " = " + endPt.Address);
}
configuration.Save();
With this code, we have access to the clientSection.Endpoints and can access and change all the member properties, like "Address". And then when we're done changing them, we can do configuration.Save() and all the values get written out to a user file.
Now here's the catch. In debug mode, the "configuration.save()" does not appear to actually persist your values from execution to execution, but when running the application normal (outside of debug mode), the values persist. (Which is good.) So that's the only caveat.
EDIT4
There is another caveat. The changes made to the WSDLs do not take effect during runtime. The application needs to be restarted to re-read the user config file values into memory (apparently.)
The only other thing that I might be interested in is finding a way (once the values have been changed) to revert the values to their defaults. Sure, you can probably delete the user file, but that deletes all of the custom settings.
Any ideas?
EDIT5
I'm thinking Dependency Injection might be perfect here, but need to research it more...
EDIT 6
I don't have comment privileges but you need to run
ConfigurationManager.RefreshSection("client");
to have the cache updated so that changes happen immediately.
If you're using Microsoft Add Web Reference to create your service reference, then I think you may have trouble changing the connection programmatically. Even if you did change the auto generated code, as soon as you did an Update Service Reference it'd be overwritten.
You're best bet is to scratch Microsoft's auto generated code and build your own WCF classes. It's not difficult, and offers lots of flexibility / scalability.
Here's an excellent article on this very subject.
As for storing the custom addresses, it would depend on your app whether it's a Silverlight, Windows or web app. My personal choice is the database.