When i try to send an instance of MULTIMEDIA type, with
hasStream="true"
property set to true, the WCF Data Server seems not to receive entity data.
On the client side i iterate over a collection of objects and i try to send them to another wcf data service. The reference to the "other wcf data service" is:
this.centralCtx
Also i set the saved stream for the each new entity and initialize all properties copying them from the source entity:
foreach (LOCAL_TYPE localObject in localObjects)
{
if (entityName == "MULTIMEDIA")
{
CentralService.ARTICOLI article = null;
CentralService.MULTIMEDIA multimedia = new CentralService.MULTIMEDIA();
LocalService.MULTIMEDIA lMultimedia = localObject as LocalService.MULTIMEDIA;
multimedia.ID_MULTIMEDIA = lMultimedia.ID_MULTIMEDIA;
multimedia.DATA_CREAZIONE = lMultimedia.DATA_CREAZIONE;
multimedia.DATA_ULTIMA_MODIFICA = lMultimedia.DATA_ULTIMA_MODIFICA;
multimedia.ARTICOLO_ID = lMultimedia.ARTICOLO_ID;
this.centralCtx.TryGetEntity(
new Uri(this.centralCtx.BaseUri + "ARTICOLI('" + multimedia.ARTICOLO_ID
+ "')", UriKind.Absolute), out article);
article.MULTIMEDIA.Add(multimedia);
this.centralCtx.AddRelatedObject(article, "MULTIMEDIA", multimedia);
DataServiceStreamResponse streamResponse = this.localCtx.GetReadStream(localObject);
this.centralCtx.SetSaveStream(multimedia, streamResponse.Stream,
true, "image/jpeg", "");
//this.centralCtx.UpdateObject(article);
}
else {
CENTRAL_TYPE cloned = DbHelper.FlatCloneFromType<LOCAL_TYPE, CENTRAL_TYPE>
(localObject, centralCtx);
this.centralCtx.AddObject(entityName, cloned);
}
}
try
{
this.centralCtx.SaveChanges();
Notify(progressAction, "Exported table " + entityName, null);
successAction(this.Log);
}
catch (Exception ex)
{
Notify(progressAction, "Error exporting table " + entityName, ex);
this.synchResult = SynchResultType.Error;
exceptionAction(ex);
}
This is change interceptor code:
[ChangeInterceptor("MULTIMEDIA")]
public void OnChangeMultimedia(MULTIMEDIA changedObject, UpdateOperations op)
{
switch (op)
{
case UpdateOperations.Add:
if(changedObject.ID_MULTIMEDIA == null)
changedObject.ID_MULTIMEDIA = Guid.NewGuid().ToString();
changedObject.STATO_INTERNO = "TRASFERITO";
changedObject.DATA_ULTIMA_MODIFICA = changedObject.DATA_ULTIMA_MODIFICA == null
? DateTime.Now.ToLocalTime() : changedObject.DATA_ULTIMA_MODIFICA;
this.CurrentDataSource.SaveChanges();
break;
default: break;
}
}
All the properties of changedObject on the server inside MULTIMEDIA change interceptor are always null. Why?
I finally got the answer.
The sending of an entity marked with the attribute hasStream implies two requests.
The first is a POST during which the server creates a record in the database and save the file on the file system.
The second is a MERGE during which the client performs an update to the ID passed from the server
That's why during the first request to the server all the properties of the object are null.
Related
I am using the Xi interface to retrieve data from an OPC .NET server. The issue that I am having though is that the server seems to be incorrectly identifying a parameter as not being historized.
Even though all the information from DeltaV indicates that the corresponding parameter for the ObjectAttributes object returned from the server should indicate that it is collecting history, the IsCollectingHistory property actually indicates that it is false.
History collection is enabled:
The parameter in question is in the history collection:
I won't include the screenshot but I can also open the historical trend for that parameter. But as you can see below, when I inspect the retrieved object while debugging, it says that it is not being historized.
Here is some of the code that I am using:
FindCriteria criteria = createCriteria(path, false);
List<Parameter> parameters = new List<Parameter>();
IEnumerable<ObjectAttributes> enumerableObject;
int i = 0;
try
{
enumerableObject = iContext.FindObjects(criteria, 50);
}
catch (System.ServiceModel.FaultException)
{
//This error is thrown when no data is returned.
return null;
}
A few lines down, I then do some object initialization for my Parameter object, assigning it the properteries from the object that was received from the server. It is not shown below but I then add my Parameter object to a collection if it is being historized. It never gets added to the collection because the IsCollectingHistory property is always false.
enumerableObject = enumerableObject.Skip(1);
foreach (ObjectAttributes oa in enumerableObject)
{
Parameter _parameter = new Parameter
{
IsHistorized = oa.IsCollectingHistory,
IsLeaf = oa.IsLeaf
};
//...
Any ideas on where I am going wrong?
Edit:
After trying MotteAndBailey's answer, an error is thrown at the call to AddNewDataObjectToDataJournalList. The message associated with it is "The OPC HDA Create Browse failed".
Below is a screenshot of the error in a message box when using HDAprobe:
Some info on properties in OPC.NET -
The Xi server wraps the OPCDA and OPCHDA servers. The item properties (attributes) available on each server and the means of accessing them using Classic (DCOM) OPC vary considerably.
OPCDA
The OPCDA server provides a method for determining item properties:
IOPCItemProperties::GetItemProperties()
This call provides the client with a way to read all an item’s attribute values using the item ID (string path for the item in the Server’s address space) and propertyID. The Xi server uses this call when performing a FindObjects() call. FindObjects() returns all the properties exposed by the OPCDA server for an item.
OPCHDA
The OPCHDA server has a method for determining item properties, but it requires the item handle not the ItemID/path:
IOPCHDA_SyncRead::ReadAttribute()
As a result, the FindObjects() call for an OPCHDA server through Xi only returns the item ID property. The other attributes are set to defaults. To determine what the actual values of the other properties are a user must add the item to a list and call ReadAttribute() on the specific property they wish to see.
In summary, the OPCDA server works pretty much like you would expect and returns the object properties in a single method call; however, the OPCHDA server requires an additional step to get all the object properties.
The sample code will produce an Xi Journal list with only the on-scan history items.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xi.Client.Base.API;
using Xi.Client.Base;
using Xi.Contracts;
using Xi.Contracts.Data;
using Xi.Contracts.Constants;
using System.ServiceModel.Description;
using Xi.Client.CommonDialogs;
using System.Runtime.Serialization;
namespace ConsoleFindObjectsHDA
{
public class DotNetSupport
{
IXiEndpointDiscovery iEndpointDiscovery;
IXiContext iContext;
IXiEndpointBase readEndpoint;
ServiceEndpoint RMSvcEndpt;
//the list of items the user is trying to read
IXiDataJournalList iDataJournalList;
public void GetSomeData()
{
//get a connection to an OPC.NET server
if (Connect())
{
//we have to have a list to add a tag to and hold returned datasets
if (CreateJournalList())
{
//now use the list to add items and read their attributes - only "good" ones will be left on the list
ListAllHDAItemsOnScan();
if (iDataJournalList.Count() > 0)
{
//at this point we <should> have a DataJournalList containing only the HDA items on scan
//we can use the normal data read methods to get history for the items if we wish
// <do some history thing here>
}
else
{
Console.WriteLine("\nThere were no points on-scan in the historian");
}
Console.WriteLine("\nPress <return> to exit program");
Console.ReadLine();
}
////clean up if we have open connections/contexts
Cleanup();
}
}
//we will use FindObjects to browse all the leaves in the HDA server, then add them one-by-one to a datalist
//when we query their on-scan property and it is true we leave them on the list
//...if not, we remove them (giving us a list of the good HDA points)
void ListAllHDAItemsOnScan()
{
FindCriteria criteria = GetLeafCriteria();
IEnumerable<ObjectAttributes> enumerableObject;
try
{
//ask the server for a list of leaves...up to 50 max returned in this call
enumerableObject = iContext.FindObjects(criteria, 50);
//for each string itemID: add it to the list, read the attribute, and remove it from the list if not on-scan
foreach (ObjectAttributes oa in enumerableObject)
{
//we do not have to commit this because we have indicated the operation is NOT prep-only
Console.WriteLine("Adding OPCHDA item {0}.", oa.InstanceId.LocalId);
IXiHistoricalDataObject ixObject = iDataJournalList.AddNewDataObjectToDataJournalList(oa.InstanceId, false);
//we are getting the CURRENT (from NOW -to- NOW) item status
FilterCriterion fc1 = GetFilterCriteriaDateTime(DateTime.Now);
//tell the server what property (attribute) we want to read
List<TypeId> lstp = new List<TypeId>();
TypeId typeit = new TypeId("", "", DVServerAttributes.DELTAV_DVCH_ON_SCAN.ToString());
lstp.Add(typeit);
//read the property from the server
iDataJournalList.ReadJournalDataProperties(fc1, fc1, ixObject, lstp);
//find the current item and check it for "on-scan"
if (ixObject != null)
{
if (ixObject.PropertyValues.First().PropertyValues.UintValues.First() == 1)
{
Console.WriteLine("OPCHDA item {0} is on-scan.\n", oa.InstanceId.LocalId);
}
else
{
if (ixObject.PrepForRemove())
{
Console.WriteLine("OPCHDA item {0} is not on-scan. Removing item.\n", oa.InstanceId.LocalId);
iDataJournalList.CommitRemoveableElements();
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception in FindObjects(). The exception is:{0}\n", ex.Message);
}
}
//create a filtercriterion for a specific date time - this is an EQUAL (not > or <) comparison operator
public FilterCriterion GetFilterCriteriaDateTime(DateTime dtChosenTime)
{
//simple timestamp filter which is used by the read call
FilterCriterion filterCriterion = null;
// this is a timestamp
string filterOperand = FilterOperandNames.Timestamp;
//make the given time with UTC/Local set to local time just to be sure
DateTime dtTmp1 = DateTime.SpecifyKind(dtChosenTime, DateTimeKind.Local);
object comparisonValue = dtTmp1;
//timestamp equal to this one
uint oper = FilterOperator.Equal;
//create the filter
filterCriterion = new FilterCriterion()
{
OperandName = filterOperand,
Operator = oper,
ComparisonValue = comparisonValue,
};
return filterCriterion;
}
//create a filtercriterion for leaves (OPCHDA items)
public FilterCriterion GetLeafFilterCriterion()
{
//simple filter for leaves
FilterCriterion filterCriterion = null;
// what this criterion applies to
string filterOperand = FilterOperandNames.BranchOrLeaf;
//Must equal "LEAF" to match
uint oper = FilterOperator.Equal;
//create the filter
filterCriterion = new FilterCriterion()
{
OperandName = filterOperand,
Operator = oper,
ComparisonValue = "LEAF",
};
return filterCriterion;
}
//set up the FindCriteria search of the server
public FindCriteria GetLeafCriteria()
{
FindCriteria findCriteria = null;
findCriteria = new FindCriteria();
//our browse starts at the root - NULL means "continue browsing from where you are"
findCriteria.StartingPath = new ObjectPath("//", "HDA");
//a list of OR-ed filter criteria (we have only one)
ORedFilters orthefilters = new ORedFilters();
//the FilterCriteria list (there is only one criterion)
orthefilters.FilterCriteria = new List<FilterCriterion>();
orthefilters.FilterCriteria.Add(GetLeafFilterCriterion()); //we want leaves
//add our OR-ed filter to the filterset filters list (whew!)
findCriteria.FilterSet = new FilterSet();
findCriteria.FilterSet.Filters = new List<ORedFilters>();
findCriteria.FilterSet.Filters.Add(orthefilters);
return findCriteria;
}
//connect to the OPC.NET server and get a read endpoint
public bool Connect()
{
//set this to point to your OPC.Net server
string serverUrl = "http://localhost:58080/XiServices/ServerDiscovery";
bool bReturnVal = false;
try
{
Console.WriteLine("Getting Endpoint Discovery from server:\n{0}\n", serverUrl);
//This class is used to locate a server and obtain its list of ServiceEndpoints.
iEndpointDiscovery = new XiEndpointDiscovery(serverUrl) as IXiEndpointDiscovery;
//we have the server...now check for endpoints
//there should always be TCP endpoints for a DeltaV OPC.NET server so we do not search HTTP and Named Pipes to find one
//and we do not consider choosing the fastest option between the three (TCP/HTTP/NamedPipes). We just use the TCP/IP one.
// GetServiceEndpointsByBinding searches the list of endpoints on the XiEndpointDiscovery connection with the specified contractType and binding type.
// We use the ResourceManagement endpoint to find the the other open endpoints on the server (some might be disabled)
IEnumerable<ServiceEndpoint> resourceEndpoints = iEndpointDiscovery.GetServiceEndpointsByBinding(typeof(IResourceManagement).Name, typeof(System.ServiceModel.NetTcpBinding));
//use the first (probably only) resource endpoint for TCP/IP to open a context between client and server
if ((resourceEndpoints != null) && (resourceEndpoints.Count() > 0))
{
var serviceEndpoints = resourceEndpoints as IList<ServiceEndpoint> ?? resourceEndpoints.ToList();
//pick the first RM endpoint we found
RMSvcEndpt = ((IList<ServiceEndpoint>)serviceEndpoints).First();
//Open the Context using the RM endpoint and some other values including timeout, what we want to read (HDA), and the GUID for this context
Console.WriteLine("Opening Client Context with Initiate\n");
iContext = XiContext.Initiate(RMSvcEndpt,
iEndpointDiscovery.ServerEntry,
300000,
(uint)ContextOptions.EnableJournalDataAccess, //HDA
(uint)System.Threading.Thread.CurrentThread.CurrentCulture.LCID,
Guid.NewGuid().ToString());
if (iContext != null)
{
//find a read endpoint using the XiEndpointDiscovery connection
IEnumerable<ServiceEndpoint> readseps = iEndpointDiscovery.GetServiceEndpointsByBinding(typeof(IRead).Name, RMSvcEndpt.Binding.GetType());
//if we found at least one read endpoint, connect the context to it
readEndpoint = null;
if (readseps != null)
{
Console.WriteLine("Adding Read endpoint to Context\n");
ServiceEndpoint sep = readseps.ElementAt<ServiceEndpoint>(0);
readEndpoint = iContext.OpenEndpoint(sep, 30000, new TimeSpan(5000));
if (readEndpoint != null)
{
bReturnVal = true; //everything went OK
}
else
{
Console.WriteLine("Unable to add Read endpoint to Context\n");
bReturnVal = false; //failed
}
}
}
else
{
Console.WriteLine("Unable to open Client Context\n");
bReturnVal = false;
}
}
}
catch (Exception)
{
bReturnVal = false;
}
return (bReturnVal);
}
public bool CreateJournalList()
{
bool retval = false;
try
{
//create a new list of HDA objects for this read
// update buffer
// rate rate filterset(not used here)
iDataJournalList = iContext.NewDataJournalList(1000, 1000, null);
if (iDataJournalList != null)
{
//we need to add the list to a read endpoint to give it data access
iDataJournalList.AddListToEndpoint(readEndpoint);
//enable the list so we can connect the items we add to it and read data
iDataJournalList.EnableListUpdating(true);
retval = true;
}
}
catch (Exception)
{
retval = false;
}
return retval;
}
public void Cleanup()
{
if (iContext != null)
{
iContext.Dispose();
}
}
} //class DotNetSupport
}
namespace ConsoleFindObjectsHDA
{
public class DVServerAttributes
{
//some DeltaV-specific OPCHDA attributes
public static uint DELTAV_DESC = 2147483650;
public static uint DELTAV_ENG_UNITS = 2147483651;
public static uint DELTAV_EU100 = 2147483666;
public static uint DELTAV_EU0 = 2147483667;
public static uint DELTAV_DVCH_LAST_DOWNLOAD = 2147483682;
public static uint DELTAV_DVCH_ON_SCAN = 2147483683;
public static uint DELTAV_NAMED_SET = 2147483698;
}
}
We're creating a WPF app in which we execute python scripts from different Test Stations and show the output in its corresponding output panel, To run the scripts in parallel we are using Task but when we run the scripts in parallel from the stations, We are getting the output of other stations also into the station that is started first, we're using the following code,
private void ZmqStatusListener(string endPoint)
{
using (Context context = new Context())
{
StatusPort = string.Empty;
TestResultPort = string.Empty;
using (Socket server = context.Socket(SocketType.REP))
{
try
{
if (isStatusContextActive == false || isPortChanged == true)
{
server.Bind(endPoint);
isStatusContextActive = true;
}
}
catch (ZMQ.Exception ex)
{
if (ex.Errno != 100)
{
string IPCPort = _globalParameters.GlbParam.GlbParamIpcStartPort;
if (IPCPort == string.Empty)
{
IPCPort = "0";
}
if (endPoint == EditorConstants.PortAddress.PortPrefix + IPCPort)
{
StatusPort = endPoint;
TestReultError = EditorConstants.CommonMessageTypes.TestReultError + ex.Message + EditorConstants.CommonMessageTypes.StackTraceMessage + ex.StackTrace;
}
StopExecOfScript(default(object));
isCancelledtask = true;
ScriptStatusDesc = new ScriptStatusDesc()
{
Status = "Failed",
statusDescription = "Failed"
};
}
}
while (true)
{
string message = server.Recv(Encoding.UTF8);
UpdateTestResults(message);
server.Send(" ACK", Encoding.UTF8);
// if (message == "Test Passed")
//break;
}
}
}
}
and for testing purpose we're breaking the while loop in this code based on a test message we kept in the python script, then we are able to get the output in the respective station correctly but this way we can only run in a synchronous fashion which we don't want as we require to run the test stations in parallel and the while loop should not break as it should be listening for the response.
We were able to solve the issue by getting clues doing a sample app to reproduce the issue and to first know whether our ClrZmq pattern was correct for us or not and it is correct. The resolution we followed is that when we needed to bind that data to its corresponding View's Model object in its ViewModel so had to retrieve View's DataContext which is of Type ISomeXViewModel for the particular TestStation using an Id of that TestStation we did this cos all of our TestStations are dynamically added and we even store it to be accessed wherever necessary. This issue was caused to due multiple instances of UserControls so we explicitly needed to update the TestStation manually with a little more effort.
Sample Code Snippet
private void BindTestResult(string xmlPayLoad)
{
// converting xmlPalLoad to a class/model object
ITestStationViewModel viewModel = (ITestStationViewModel)((IView)DynamicTestStationsGrid.Children[StationNumber].Content).DataContext;
// IView class has DataContext property so I am type casting the Content which is ContentControl to IView type first and later to ITestStationViewModel
viewModel.TestStationModel = xmlPayLoadModel;
}
Thanks.
Trying to unit test functions which access a entity framework. So i tried to put all the entity code into the test function below? However it stops at the Linq statement; obviously trying to access the database is too much drama for it. Maybe a work around would be too to create a replica database within the unit test function based on sql lite or compact;(Its not a big database anyways) then execution would not have to leave the test function? Is this possible and how would i implement it?
public void RetreiveKeyFnTest()
{
StegApp target = new StegApp(); // TODO: Initialize to an appropriate value
string username = "david"; // TODO: Initialize to an appropriate value
string password = "david1"; // TODO: Initialize to an appropriate value
string ConnectionString = ConfigurationManager.ConnectionStrings["DatabaseEntities"].ToString();
var dataContext = new DatabaseEntities(ConnectionString);
var user = dataContext.Users.FirstOrDefault(u => u.Username.Equals(username) && u.Password.Equals(password));
Assert.IsNotNull(user);
//target.RetreiveKeyFn(username, password);
//Assert.IsInstanceOfType(target.RetreiveLogs,typeof(DataAccess));
//Assert.IsInstanceOfType(target.p);
//Assert.IsNotNull(target.RetreiveLogs.AuthenitcateCredentials(username,password));
//Assert.Inconclusive("A method that does not return a value cannot be verified.");
}
Below is the code i am trying to test:
public void RetreiveKeyFn(string username, string password)
{
BusinessObjects.User p = RetreiveLogs.AuthenitcateCredentials(username,password);
if (p != null)
{
if (RetreiveLogs.RetreiveMessages(p.UserId) == null)
{
DisplayLogs.Text = "Sorry No messages for you recorded in Database, your correspondant might have chose not to record the entry";
}
else
{
MessageBox.Show("LogId = " + RetreiveLogs.RetreiveMessages(p.UserId).LogId + "\n" +
"UserId = " + RetreiveLogs.RetreiveMessages(p.UserId).UserId + "\n" +
"Message Key = " + RetreiveLogs.RetreiveMessages(p.UserId).MessageKey + "\n" + "PictureId = " + RetreiveLogs.RetreiveMessages(p.UserId).PictureId +
" Date & time = " + RetreiveLogs.RetreiveMessages(p.UserId).SentDateTime);
DisplayLogs.Visible = true;
}
}
else
{
MessageBox.Show("Please enter your correct username and password in order to retreive either key, image or both from Databse");
}
}
First, you should be able to access the same database in your test application as the one you're using in your main/actual application. You just need to make sure that your Test project contains your connection string in its own App.config.
The initialization of the context should be done either inside your StegApp(), or you should be able to pass a context to your StegApp() from a different scope. From what I read of your code, your StegApp() will not be able to access the dataContext variable you created.
Your test for null user already happens inside the RetrieveKeyFn() under the AuthenticateCredentials() method so there's no need for the first "Assert.IsNotNull(user)". I would recommend separating your business logic for RetrieveKeyFn from your UI behaviors so that you can easily do unit tests. You can bind the "Messagebox" operations to say a button click event handler which calls just RetrieveKeyFn(). I would suggest maybe something like this:
public class StegApp
{
public DatabaseEntities context;
//other properties
public StegApp()
{
//assuming your DatabaseEntities class inherits from DbContext.
//You should create other constructors that allow you to set options
//like lazy loading and mappings
this.context = new DatabaseEntities();
}
//ASSUMING YOUR RetrieveLogs.RetrieveMessages() function returns
//a Message object. replace this type with whatever type the
//RetrieveLogs.RetrieveMessages() method returns.
public Message RetrieveKeyFn (string username, string password)
{
BusinessObjects.User p = RetreiveLogs.AuthenitcateCredentials(username,password);
if (p != null)
{
var message = RetrieveLogs.RetrieveMessages(p.UserId);
if (message == null)
// handle behavior for no messages. In this case
// I will just create a new Message object with a -1 LogId
return new Message {LogId =-1};
else
return message;
}
else
//handle behavior when the user is not authenticated.
//In this case I throw an exception
throw new Exception();
}
//on your button click handler, do something like:
// try
// {
// var message = RetrieveKeyFn(txtUsername.Text.Trim(), txtPassword.Text.Trim());
// if (message.LogId == -1)
// DisplayLogs.Text = "Sorry No messages for you recorded in Database, your correspondant might have chose not to record the entry";
// else
// {
// MessageBox.Show("Log Id = " + message.LogId)
// etc. etc. etc.
// }
// }
// catch
// {
// MessageBox.Show ("user is not authenticated");
// }
}
When you do your unit test, remember to have the appropriate configuration strings in your test project's App.Config If the app.config does not yet exist, go ahead and create one. You should create tests for all possibilities (i.e. 1) user is valid, you get the message, 2) user is valid, there are no messages, 3) user is invalid).
Here's an example for case 2
[TestMethod]
public void RetrieveKeyFnTest1()
{
StegApp target = new StegApp(); // this creates your context. I'm assuming it also creates your RetrieveLogs object, etc
var username = "UserWithNotMessages"; //this user should exist in your database but should not have any messages. You could insert this user as part of your TestInitialize method
var password = "UserWithNotMessagesPassword"; //this should be the proper password
var message = target.RetrieveKeyFn(username, password);
Assert.AreEqual (-1, message.LogId);
}
I got my unit tests to work fine. The mistake i had was not to copy the app.config file into the test project! Although to be honest i expected Visual studio would have done that anyways.
Steve Marx writes about new extension methods to perform upserts in Azure Table Storage as part of the new storage protocol version here:
http://blog.smarx.com/posts/extension-methods-for-the-august-storage-features
However, what if I want to do the original operation of unconditional-merge-or-throw, rather than an upsert. I want to merge an object, updating a single field, but throw if the entity doesn't exist rather than create a new entity that contains only the properties I'm merging.
Is this possible? Note that I want to use upsert elsewhere, so I've taken to having IoC provide me with contexts created from GetDataServiceContext2011 instead of GetDataServiceContext. I suppose I could alternate between the two, but that won't help when the Azure team updates the official libraries.
According to MSDN:
The Insert Or Merge Entity operation uses the MERGE verb and must be
called using the 2011-08-18 version or newer. In addition, it does not
use the If-Match header. These attributes distinguish this operation
from the Update Entity operation, though the request body is the same
for both operations.
So, how do I get the storage library to emit a wildcard If-Match on save rather than emit no If-Match at all?
Just use AttachTo with an asterisk for an etag. That will result in an If-Match: *. Here's a complete working example:
class Entity : TableServiceEntity
{
public string Text { get; set; }
public Entity() { }
public Entity(string rowkey) : base(string.Empty, rowkey) { }
}
class Program
{
static void Update(CloudStorageAccount account)
{
var ctx = account.CreateCloudTableClient().GetDataServiceContext();
var entity = new Entity("foo") { Text = "bar" };
ctx.AttachTo("testtable", entity, "*");
ctx.UpdateObject(entity);
ctx.SaveChangesWithRetries();
}
static void Main(string[] args)
{
var account = CloudStorageAccount.Parse(args[0]);
var tables = account.CreateCloudTableClient();
tables.CreateTableIfNotExist("testtable");
var ctx = tables.GetDataServiceContext();
try { Update(account); } catch (Exception e) { Console.WriteLine("Exception (as expected): " + e.Message); }
ctx.AddObject("testtable", new Entity("foo") { Text = "foo" });
ctx.SaveChangesWithRetries();
try { Update(account); } catch (Exception e) { Console.WriteLine("Unexpected exception: " + e.Message); }
Console.WriteLine("Now text is: " + tables.GetDataServiceContext().CreateQuery<Entity>("testtable").Where(e => e.PartitionKey == string.Empty && e.RowKey == "foo").Single().Text);
tables.DeleteTableIfExist("testtable");
}
}
I'm tryring to do a simple insert with foreign key, but it seems that I need to use db.SaveChanges() for every record insert. How can I manage to use only one db.SaveChanges() at the end of this program?
public static void Test()
{
using (var entities = new DBEntities())
{
var sale =
new SalesFeed
{
SaleName = "Stuff...",
};
entities.AddToSalesFeedSet(sale);
var phone =
new CustomerPhone
{
CreationDate = DateTime.UtcNow,
sales_feeds = sale
};
entities.AddToCustomerPhoneSet(phone);
entities.SaveChanges();
}
}
After running the above code I get this exception:
System.Data.UpdateException: An error occurred while updating the entries. See the InnerException for details. The specified value is not an instance of a valid constant type
Parameter name: value.
EDIT: Changed example code and added returned exception.
Apperantly using UNSIGNED BIGINT causes this problem. When I switched to SIGNED BIGINT everything worked as it supposed to.
I tried to do this "the right way":
And then I wrote this little test app to scan a directory, store the directory and all its files in two tables:
static void Main(string[] args)
{
string directoryName = args[0];
if(!Directory.Exists(directoryName))
{
Console.WriteLine("ERROR: Directory '{0}' does not exist!", directoryName);
return;
}
using (testEntities entities = new testEntities())
{
StoredDir dir = new StoredDir{ DirName = directoryName };
entities.AddToStoredDirSet(dir);
foreach (string filename in Directory.GetFiles(directoryName))
{
StoredFile stFile = new StoredFile { FileName = Path.GetFileName(filename), Directory = dir };
entities.AddToStoredFileSet(stFile);
}
try
{
entities.SaveChanges();
}
catch(Exception exc)
{
string message = exc.GetType().FullName + ": " + exc.Message;
}
}
}
As you can see, I only have a single call to .SaveChanges() at the very end - this works like a charm, everything's as expected.
Something about your approach must be screwing up the EF system.....
it might be related with the implementation of AddToSalesFeedSet etc..
there is chance that you are doing commit inside ?
any way, my point is that i encountered very close problem, was tring to add relation to new entity with existed entity that been queried earlier - that has unsigned key
and got the same exception;
the solution was to call Db.collection.Attach(previouslyQueriedEntityInstance);