Using the Microsoft.CrmSdk assembly to generate entities in Dynamics 365 for Customer Engagement (version 9), I found out that the method GetEntityMetadata from CrmServiceClient does not get the most uptodate information from entities.
Here the code to show you:
using (var svc = new CrmServiceClient(strConn))
{
EntityMetadata em = svc.GetEntityMetadata(PREFIX + TABLE_NAME_D, EntityFilters.Attributes);
if (em == null)
{
Console.WriteLine($"Create entity [{PREFIX + TABLE_NAME_D}]");
CreateEntityRequest createRequest = new CreateEntityRequest
{
Entity = new EntityMetadata
{
SchemaName = PREFIX + TABLE_NAME_D,
LogicalName = PREFIX + TABLE_NAME_D,
DisplayName = new Label(TABLE_LABEL, 1036),
DisplayCollectionName = new Label(TABLE_LABEL_P, 1036),
OwnershipType = OwnershipTypes.UserOwned,
},
PrimaryAttribute = new StringAttributeMetadata
{
SchemaName = PREFIX + "name",
MaxLength = 30,
FormatName = StringFormatName.Text,
DisplayName = new Label("Residence", 1036),
}
};
CreateEntityResponse resp = (CreateEntityResponse)svc.Execute(createRequest);
em = svc.GetEntityMetadata(PREFIX + TABLE_NAME_D, EntityFilters.All);
// At this point, em is null!!!
}
}
After the createResponse is received, the entity is well created in Dynamics, but still the GetEntityMetadata called just after is still null. If I wait a few seconds and make another call, the response is now correct. But that's horrible!
Is there any way to "force" the refresh of the response?
Thanks.
Ok I found it! It's linked to a caching mechanism.
One must use the function ResetLocalMetadataCache to clean the cache, but there seems to be an issue with this function.
It will only works by passing the entity name in parameter (if you call it without parameter, it is supposed to clean the entire cache but that does not work for me).
EntityMetadata em = svc.GetEntityMetadata(TABLE_NAME_D, EntityFilters.All); // Request sent
em = svc.GetEntityMetadata(TABLE_NAME_D, EntityFilters.All); // Cache used
svc.ResetLocalMetadataCache(); // No effect?!
em = svc.GetEntityMetadata(TABLE_NAME_D, EntityFilters.All); // Cache used
em = svc.GetEntityMetadata(TABLE_NAME_D, EntityFilters.All); // Cache used
svc.ResetLocalMetadataCache(TABLE_NAME_D); // Cache cleaned for this entity
em = svc.GetEntityMetadata(TABLE_NAME_D, EntityFilters.All); // Request sent!
Related
I'm having an issue where the token validation fails after some time (exactly when varies I think but usually counted in days). Restarting the app resolves the issue, so I think it's something wrong with how I initialize things.
I'm using Firebase and below is the bootstrapping code that runs at app startup.
I read in a comment on this old post https://stackoverflow.com/a/29779351/611441 that Google rotates certs, so now I'm thinking that might be the issue? I'm only fetching the certs once for the lifetime of the application. If that's the case, how would I be able to refresh these every now and then since this only runs at startup?
public void ConfigureAuthentication(IAppBuilder app)
{
var issuerSigningKeys = GetIssuerSigningKeys();
var firebaseAdminProjectId = ConfigurationManager.AppSettings.Get("FirebaseAdminProjectId");
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions()
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { firebaseAdminProjectId },
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = context =>
{
context.OwinContext.Set<bool>("OnValidateIdentity", true);
return Task.FromResult(0);
}
},
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeys = issuerSigningKeys,
ValidAudience = firebaseAdminProjectId,
ValidIssuer = ConfigurationManager.AppSettings.Get("FirebaseAdminValidIssuer"),
IssuerSigningKeyResolver = (arbitrarily, declaring, these, parameters) => issuerSigningKeys
}
});
}
private static List<X509SecurityKey> GetIssuerSigningKeys()
{
HttpClient client = new HttpClient();
var task = client.GetStringAsync("https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com"));
task.Wait();
string jsonResult = task.Result;
//Extract X509SecurityKeys from JSON result
List<X509SecurityKey> x509IssuerSigningKeys = JObject.Parse(jsonResult)
.Children()
.Cast<JProperty>()
.Select(i => BuildSecurityKey(i.Value.ToString())).ToList();
return x509IssuerSigningKeys;
}
private static X509SecurityKey BuildSecurityKey(string certificate)
{
//Removing "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" lines
var lines = certificate.Split('\n');
var selectedLines = lines.Skip(1).Take(lines.Length - 3);
var key = string.Join(Environment.NewLine, selectedLines);
return new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(key)));
}
I think I've finally figured this out.
First of all, the signing keys seems to be rotated every 5 days because they have a validity property set with a date. This makes sense with the pattern I see...
However, I think the issue is in my code. The TokenValidationParameters' property IssuerSigningKeyResolver expects a delegate. But I'm getting the keys and assigning them to a variable which in turn is assigned to the property. So the "resolver" always resolves the initial keys returned. They'll never refresh. The fix is to simply assign the GetIssuerSigningKeys() method to the property instead:
IssuerSigningKeyResolver = (arbitrarily, declaring, these, parameters) => GetIssuerSigningKeys()
Working on a WebApi project that's backed by mssql with EntityFramework, and also Oracle (12c) using oracle's ManagedDataAccess.Client.OracleConnection. We use autofac to inject an instance of our context per request, but all oracle access is just done ad hoc.
We have certain operations that depend on both databases at the same time, so we opted to use the TransactionScope object to manage the transaction.
For the most part it works well, the light weight transactions that are promoted to distributed work great. But there is one issue I've encountered after completing a distributed transaction.
Given:
public void Test()
{
var preItem = new HelpItem
{
Field1 = "pre batch";
};
_context.Items.Add(preItem);
_context.SaveChanges(); // This save always works.
var batchResult = FooService.BatchOperation(true);
var postItem = new HelpItem
{
Field1 = "post batch";
};
_context.Items.Add(postItem);
_context.SaveChanges(); // This will succeed/fail depending on whether FooService caused a distributed transaction.
}
With the BatchOperation method as:
public Result BatchOperation(bool triggerDtc)
{
using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
if (triggerDtc){
// Make requests to both databases.
} else {
// Make request to one database.
}
// Always complete for the sake of the demonstration.
transaction.Complete();
}
}
If a distributed transaction is encountered and then completed & fully disposed EF doesn't seem to be able to recover and go back to working as it was before the transaction came into play.
The error:
Distributed transaction completed. Either enlist this session in a new
transaction or the NULL transaction.
What would be the correct way to handle this?
For this particular case you can simply create another transaction around the second part:
var batchResult = FooService.BatchOperation(true);
using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var postItem = new HelpItem
{
Field1 = "post batch";
};
_context.Items.Add(postItem);
_context.SaveChanges(); // This save depends on whether FooService caused a distributed transaction.
transaction.Complete();
}
But this issue came up because the FooService.BatchOperation method was altered with just a lookup to the other database, unknowingly breaking every method out there that continues to use the context after calling it. With normal transaction a single EF context can freely be used in and out of them without issue, is there any way to achieve the same with a distributed transaction?
EDIT:
This really just has me confused now. Just the act of making a request in another (non distributed) transactionscope is enough to restore EF functionality.
public IHttpActionResult Test()
{
var preItem = new HelpItem
{
Field1 = "pre batch";
};
_context.Items.Add(preItem);
_context.SaveChanges(); // This save works.
var batchResult = FooService.BatchOperation(true);
using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var lookupAnything = _context.Items.ToList();
transaction.Complete(); // This is optional, because we really don't care and it's disposed either way.
}
var postItem = new HelpItem
{
Field1 = "post batch";
};
_context.Items.Add(postItem);
_context.SaveChanges(); // Now this always works.
}
Obviously I can't just go around putting this everywhere, so still not sure what the actual solution is.
I wrote this simple query:
var connectionString = String.Format("Url={0}; Username={1}; Password={2}; Domain={3}", url, username, password, domain);
var myConnection = CrmConnection.Parse(connectionString);
CrmOrganizationServiceContext _service = new CrmOrganizationServiceContext(myConnection);
var whoAmI = _service.Execute(new WhoAmIRequest());
var query = new QueryExpression
{
EntityName = "phonecall",
ColumnSet = new ColumnSet(true)
};
query.PageInfo = new PagingInfo
{
Count = 20,
PageNumber = 1,
PagingCookie = null
};
query.Orders.Add(new OrderExpression
{
AttributeName = "actualstart",
OrderType = OrderType.Descending
});
query.Criteria = new FilterExpression() { FilterOperator = LogicalOperator.And };
query.Criteria.AddCondition("call_caller", ConditionOperator.In, lines);
var entities = _service.RetrieveMultiple(query).Entities;
I have a program which runs this query every minute. On the first execution the correct results are displayed but for subsequent queries the results never change as I update records in CRM.
If I restart my program the results refresh correctly again on the first load.
Why are the results not updating as records are modified in CRM?
It is the CrmOrganizationServiceContext that is doing the caching - I found the following worked a treat and the results of my RetrieveMultiple are no longer cached :)
Context = new CrmOrganizationServiceContext(CrmConnection.Parse(connectionString));
Context.TryAccessCache(cache => cache.Mode = OrganizationServiceCacheMode.Disabled);
RetrieveMultiple always brings back fresh results so there must be some other aspect of your program which is causing stale data to be displayed.
Trying to update a list of Incident records. The first one in the foreach updates, the next one throws an exception stating "the context is not currently tracking the incident entity". Is this the correct way to code this?
var openCases = (from o in xrmContext.IncidentSet
where o.StateCode == 0
select o).Take(5).ToList();
foreach (var c in openCases)
{
var numDays = ((TimeSpan) (DateTime.Now - c.CreatedOn)).Days;
Console.WriteLine("case age: {0}, case number:{1}", numDays, c.TicketNumber);
c.new_caseage = numDays;
xrmContext.UpdateObject(c);
xrmContext.SaveChanges();
}
When you call SaveChanges() it, in addition to saving any modified entity records, detaches all entity records being tracked in the context. Therefore, the second time you call SaveChanges() the entity record is not being tracked and you receive the error.
You should move the xrmContext.SaveChanges(); line to be after the foreach loop.
var openCases = (from o in xrmContext.IncidentSet
where o.StateCode == 0
select o).Take(5).ToList();
foreach (var c in openCases)
{
var numDays = ((TimeSpan) (DateTime.Now - c.CreatedOn)).Days;
Console.WriteLine("case age: {0}, case number:{1}", numDays, c.TicketNumber);
c.new_caseage = numDays;
xrmContext.UpdateObject(c);
}
xrmContext.SaveChanges();
All entities are detached by the OrganizationServiceContext after calling the SaveChanges method. To continue using the data context against previously retrieved entities, the entities need to be reattached.
However, the preference is to apply all modifications under a single call to SaveChanges, and then dispose the context, to avoid the need to reattach.
http://msdn.microsoft.com/en-us/library/gg695783.aspx
http://msdn.microsoft.com/en-us/library/gg334504.aspx#track_related
A better way to do what your try to do is using the message ExecuteMultipleRequest, with it can configure how many record to process for every iteration (internal iteration)
var openCases = (from o in xrmContext.IncidentSet
where o.StateCode == 0
select o).Take(5).ToList();
var requestWithResults = new ExecuteMultipleRequest()
{
// Assign settings that define execution behavior: continue on error, return responses.
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = false,
ReturnResponses = true
},
// Create an empty organization request collection.
Requests = new OrganizationRequestCollection()
};
foreach (var c in openCases)
{
var numDays = ((TimeSpan) (DateTime.Now - c.CreatedOn)).Days;
c.new_caseage = numDays;
CreateRequest createRequest = new CreateRequest { Target = c };
requestWithResults.Requests.Add(createRequest);
}
ExecuteMultipleResponse responseWithResults =
(ExecuteMultipleResponse)_serviceProxy.Execute(requestWithResults);
Hope it helps
I am receiving the following error calling GetAssetEquipmentOp:
"Error in processing entity WorkOrder unable to create entity object"
Here is the code so far:
public stringType getAssetDescription(string equipmentcode)
{
try
{
// Setup Service Objects
MP0302_GetAssetEquipment_001.GetAssetEquipmentService getservice = new MP0302_GetAssetEquipment_001.GetAssetEquipmentService();
MP0302_GetAssetEquipment_001.MP0302_GetAssetEquipment_001 getrequest = new MP0302_GetAssetEquipment_001.MP0302_GetAssetEquipment_001();
MP0302_GetAssetEquipment_001.MP0302_GetAssetEquipment_001_Result getresult = new MP0302_GetAssetEquipment_001.MP0302_GetAssetEquipment_001_Result();
// Setup Return Object
stringType desc = new stringType();
// Setup Service Parameters
getrequest.ASSETID = new MP0302_GetAssetEquipment_001.EQUIPMENTID_Type();
getrequest.ASSETID.EQUIPMENTCODE = equipmentcode;
getrequest.ASSETID.ORGANIZATIONID = new MP0302_GetAssetEquipment_001.ORGANIZATIONID_Type();
getrequest.ASSETID.ORGANIZATIONID.ORGANIZATIONCODE = _orgCodeBody;
// Setup Datastream Object
Datastream.EWS.Session sess = new Datastream.EWS.Session(_userid, _passwd, _orgCodeHead, _url, _tenant, false);
// Prepare Service Request
sess.PrepareServiceRequest(getservice);
// Call Web Service and get result
getresult = getservice.GetAssetEquipmentOp(getrequest);
// Extract Description
desc.stringValue = getresult.ResultData.AssetEquipment.ASSETID.DESCRIPTION;
desc.errorNum = 0;
// Close Up/Dispose
sess.CompleteServiceRequest(getservice);
sess.Dispose();
// Return value
return desc;
}
catch (Exception ex)
{
stringType errorStringType = new stringType();
errorStringType.errorNum = 1;
errorStringType.errorDesc = ex.Message;
return errorStringType;
}
}
I have checked the following:
- User group has interface permissions including BECONN
- User has "Connector" option selected
- User has status authorizations including * to Q for EVNT
Any help would be appreciated.
Problem solved! The problem was that the work order number did not exist. It is a very misleading error but once an existing work order was tested, it fetched the work order with no issues.