Below is my schema generation code, Two variables (storeIdForSurvey & questionIdForAnswer) are not being auto generated in the model class( Survey & Question), though they are present the in auto generated dao classes (SurveyDao & QuestionDao).
Object Oriented description of the domain is as : User has Store, Store has Survey,Survey has FollowupItems, Survey has Category, Category has Question, Question has History, Question has Answer.
private static void addUser(Schema schema) {
//User
Entity user = schema.addEntity("User");
user.addIdProperty();
user.addStringProperty("districtId");
user.addStringProperty("employeeId");
user.addStringProperty("name");
user.addStringProperty("sessionToken");
user.addStringProperty("userId");
//Store
Entity store = schema.addEntity("Store");
// foreign key
Property userIdForStore = store.addLongProperty("userIdForStore").getProperty();
store.addToOne(user, userIdForStore);
user.addToMany(store, userIdForStore);
store.addIdProperty();
store.addStringProperty("storeId");
store.addStringProperty("address");
store.addStringProperty("city");
store.addStringProperty("storeName");
store.addStringProperty("state");
store.addStringProperty("zip");
store.addStringProperty("storeManagerName");
store.addBooleanProperty("isSurveyHistoryAvailable");
//Survey
Entity survey = schema.addEntity("Survey");
//foreign key
Property storeIdForSurvey = survey.addLongProperty("storeIdForSurvey").getProperty();
survey.addToOne(store, storeIdForSurvey); // one store can have one survey at a time
store.addToOne(survey, storeIdForSurvey);
survey.addIdProperty();
survey.addStringProperty("surveyId");
survey.addStringProperty("dmSignImagePath");
survey.addStringProperty("dmSignImageName");
survey.addStringProperty("smSignImagePath");
survey.addStringProperty("smSignImageName");
survey.addStringProperty("startlatitude");
survey.addStringProperty("startlongitude");
survey.addStringProperty("submitLatitude");
survey.addStringProperty("submitLongitude");
survey.addStringProperty("acknowledgedBy");
survey.addStringProperty("deliveredBy");
survey.addStringProperty("name");
survey.addStringProperty("createdBy");
survey.addStringProperty("description");
survey.addStringProperty("storeId");
survey.addStringProperty("districtManager");
survey.addDateProperty("startDate");
survey.addDateProperty("submitDate");
survey.addDateProperty("syncDate");
survey.addDateProperty("createdDate");
survey.addDateProperty("actionItemAssignDate");
survey.addDateProperty("actionItemDueDate");
survey.addDoubleProperty("score");
//FolloupItems
Entity followupItem = schema.addEntity("FollowupItem");
//foreign key
Property surveyIdForFollowupItem = followupItem.addLongProperty("surveyIdForFollowupItem").getProperty();
followupItem.addToOne(survey, surveyIdForFollowupItem);
survey.addToMany(followupItem, surveyIdForFollowupItem);
followupItem.addIdProperty();
followupItem.addStringProperty("assignedTo");
followupItem.addStringProperty("comment");
followupItem.addStringProperty("photoName");
followupItem.addStringProperty("photoURL");
followupItem.addDateProperty("assignedDate");
followupItem.addDateProperty("dueDate");
followupItem.addDateProperty("expeireDate");
//Category
Entity category = schema.addEntity("Category");
//foreign key
Property surveyIdForCategory = category.addLongProperty("surveyIdForCategory").getProperty();
category.addToOne(survey, surveyIdForCategory);
survey.addToMany(category, surveyIdForCategory);
category.addIdProperty();
category.addStringProperty("categoryId");
category.addStringProperty("name");
category.addStringProperty("weight");
category.addStringProperty("surveyId");
category.addDoubleProperty("totalScore");
category.addIntProperty("sortOrder");
category.addBooleanProperty("completionStatus");
category.addBooleanProperty("hasActionItem");
//Question
Entity question = schema.addEntity("Question");
//foreign key
Property categoryIdForQuestion = question.addLongProperty("categoryIdForQuestion").getProperty();
question.addToOne(category, categoryIdForQuestion);
category.addToMany(question, categoryIdForQuestion);
question.addIdProperty();
question.addStringProperty("questionId");
question.addDateProperty("startDate");
question.addDateProperty("endDate");
question.addStringProperty("statement");
question.addStringProperty("type");
question.addStringProperty("weight");
question.addStringProperty("surveyCategoryName");
question.addIntProperty("displayOrder");
question.addBooleanProperty("naFlag");
question.addBooleanProperty("isRequired");
//Question History
Entity questionHistory = schema.addEntity("questionHistory");
//foreign key
Property questionIdForQuestionHistory = questionHistory.addLongProperty("questionIdForQuestionHistory").getProperty();
questionHistory.addToOne(store, questionIdForQuestionHistory);
question.addToMany(questionHistory, questionIdForQuestionHistory);
questionHistory.addIdProperty();
questionHistory.addStringProperty("questionId");
questionHistory.addStringProperty("secondLastHistory");
questionHistory.addStringProperty("lastHistory");
//Answer
Entity answer = schema.addEntity("Answer");
//foreign key
Property questionIdForAnswer = answer.addLongProperty("questionIdForAnswer").getProperty();
question.addToOne(answer, questionIdForAnswer);
answer.addToOne(question, questionIdForAnswer);
answer.addIdProperty();
answer.addStringProperty("projectType");
answer.addStringProperty("assignedTo");
answer.addStringProperty("comment");
answer.addStringProperty("photoUrl");
answer.addStringProperty("photoNmae");
answer.addStringProperty("selectedOption");
answer.addDateProperty("assignedDate");
answer.addDateProperty("dueDate");
answer.addDateProperty("expireDate");
answer.addDoubleProperty("score");
}
please read the documentation carefully:
public ToOne addToOne(Entity target, Property fkProperty)
Adds a to-one relationship to the given target entity using the given given
foreign key property (which belongs to this entity).
This means the following statement is correct:
Property storeIdForSurvey = survey.addLongProperty("storeIdForSurvey").getProperty();
survey.addToOne(store, storeIdForSurvey);
but the next statement is incorrect since the Property storeIdForSurvey is not member of the Entity store:
store.addToOne(survey, storeIdForSurvey);
Try to use this statement instead:
store.addToOneWithoutProperty("Survey", survey, "storeIdForSurvey");
Related
Consider the following class, consisting of a foreign key and a corresponding navigation property.
class MyClass
{
public long FkId { get; set; }
public MyOtherClass FkNavigationProperty { get; set; }
}
My update problem
MyClass myUpdate =
new MyClass
{
FkId = 5, //the new value which i want to save
FkNavigationProperty = new MyOtherClass { Id = 3 } //the old value that was loaded with the entity
}
context.Update(myUpdate);
context.SaveChanges();
The Problem
This leads to a saved MyClass Entity with FK value of 3 instead of the desired 5.
Why do I have this problem
Normally, I would make sure, that no navigation property is loaded on the updated entity.
But currently, this is impossible to make sure.
What I would like to know
What are the best practices for such situations?
Setting the navigation property to null and set only the foreign key id?
Set both, the foreign key id in on the entity and in the navigation property?
Load the desired foreign key entity and set it into the navigation property (leaving the foreign key on the old value)?
Other propositions?
EDIT: is it always the case, that navigation property "wins" over foreign key property? (if yes, is this documented somewhere?)
Thanks for any help with this!
Cheers, Mike
I have a custom entity A, which contains an annotation column (a built in entity that support upload files). I want to be able to read the annotation id for the entity record i get when a plugin is triggered.
The entity has all attributes i am except the annotation in any form, no referenced entity. It is worth to note that annotation entity is listed in the relationship tab but there is no reference to annotation field in the fields view in Dynamics online.
How can i lookup or get the annotation id in the entity A in a custom plugin.
The plugin triggers on the create message from custom entity A, since it has all the columns i want to process in addition the file uploaded in the annotation entity.
I looked at the sample sdk sample, but it is not useful since i want to get the annotation id first before retrieving it.
https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/gg328429(v=crm.8)
Any pointers or sample are appreciated it.
There is a one-to-many relationship from your custom entity to the annotation entity, because there can be many notes (and attachments) for each custom entity record.
Your plugin should create a new annotation record and set the objectid and objecttypecode fields on that annotation record to the current custom record that was just created.
Here's an example that uploads a simple text file and relates it to a custom entity record that was just created:
var newId = <new just-created custom entity record id goes here>;
var sampleFileText = "Hello World";
var sampleFileBytes = Encoding.ASCII.GetBytes(sampleText);
var sampleFileBase64 = System.Convert.ToBase64String(fileBytes);
var annotation = new Entity("annotation");
annotation.Attributes["objectid"] = new EntityReference("new_entity", newId); // <- Your custom entity name and new id here
annotation.Attributes["objecttypecode"] = "new_entity"; // <- Your custom entity name here
annotation.Attributes["subject"] = "Uploaded File";
annotation.Attributes["documentbody"] = sampleFileBase64 ;
annotation.Attributes["mimetype"] = #"text/plain";
annotation.Attributes["notetext"] = "Uploaded File";
annotation.Attributes["filename"] = "UploadedFile.txt";
Service.Create(annotation);
I want to create a one method using which i can Add/Update entity using Entity Framework 2.0-Preview.
I am using Insert Update Pattern.
For more Ref: https://msdn.microsoft.com/en-us/data/jj592676.aspx (Last example)
Below is a method code:
public string AttachEntity(Book book)
{
_context.Entry(book).State = (book.Id == 0)
? Microsoft.EntityFrameworkCore.EntityState.Added
: Microsoft.EntityFrameworkCore.EntityState.Modified;
string msg = $"Book details {_context.Entry(book).State} Successfully";
_context.Book.Attach(book);
_context.SaveChanges();
return msg;
}
and got below error:
InvalidOperationException: The property 'Id' on entity type 'Book' has
a temporary value while attempting to change the entity's state to
'Unchanged'. Either set a permanent value explicitly or ensure that
the database is configured to generate values for this property.
However Id is auto generated for book table.
and below urls for that action:
[For add] Home/AttachEntity?Id=1&Title=4th book&Description=New book added by attach method&Price=120&AuthorId=2
[For update] Home/AttachEntity?Title=4th book&Description=New book added by attach method&Price=120&AuthorId=2
To preface this question: my problem is not because I'm directly setting a key property in my model entity object (which is the cause of the issue in other search results for the same exception message).
I'm making heavy use of composite keys in my application, here's a simplified version of my current DB schema (key fields in *asterisks*):
Tenants( *TenantId*, ... )
Categories( *TenantId*, *CategoryId*, ... )
Documents( *TenantId*, *DocumentId*, CategoryId, ... )
The Documents table has FK relationships with both Tenants and Categories, both using the same Documents.TenantId column. The Documents.CategoryId column is NULLable.
When I do something like this, I get the exception:
Tenant tenant = GetTenant( 123 );
Document doc = tenant.Documents.First();
Category newCategory = new Category();
newCategory.TenantId = 123;
dbContext.Categories.Add( newCategory );
doc.Category = newCategory; <-- exception is thrown on this line, without calling dbContext.SaveChanges() at all.
I believe the exception is because setting Category on the Document instance causes the TenantId property to be set indirectly by EF (because it's part of the Documents -> Categories FK association.
What is the solution?
Workaround Update
I'm able to hack it by creating the new Category entities then saving them, to get the IDENTITY values back, then setting the Document properties directly:
Tenant tenant = GetTenant( 123 );
Document doc = tenant.Documents.First();
Category newCategory = new Category();
newCategory.TenantId = 123;
dbContext.Categories.Add( newCategory );
dbContext.SaveChanges();
doc.CategoryId = newCategory.CategoryId
dbContext.SaveChanges();
But ideally I'd like this to work in a single call to SaveChanges() and using the Entity Model Navigation Properties instead of scalar attribute properties.
For this initial problem, I worked-around it using the "Workaround Update" I posted to my original posting.
However this problem happened again for a different entity type (again, with a composite key involved in a foreign-key) and I noticed that EF throws the exception even if you call dbContext.Entry() on any entity in the graph while the new entity is in the Added state - but it does not throw the exception again if you re-call Entry() or even SaveChanges(), and in fact it saves the new entities correctly in spite of the initial exception - so I'm thinking this might just be a bug in EF.
Here's essentially what I have now:
Tenant tenant = GetTenant( 123 );
Document doc = tenant.Documents.First();
Category newCategory = new Category();
newCategory.TenantId = 123;
dbContext.Categories.Add( newCategory );
doc.CategoryId = newCategory.CategoryId
try {
dbContext.Entry( doc );
}
catch(InvalidOperationException) {
}
dbContext.SaveChanges();
It's ugly, but works - and avoids having to call SaveChanges twice.
I'm using .NET 3.5 SP1 in ASP.NET MVC application.
While using ObjectContext with Http Request lifetime, and trying to attach an entity ALREADY present in context, we get error:
"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
For Example, the code :
Category newCategory = new Category {CategoryId = CategoryIdSelected};
ctx.AttachTo("CategorySet", newCategory);
will give error if 'Category' with CategoryId = CategoryIdSelected exists in ObjectContext.
Modified code to check for existing entity:
Category newCategory = new Category {CategoryId = CategoryIdSelected};
ObjectStateEntry stateEntry = null;
if( ctx.ObjectStateManager.TryGetObjectStateEntry(newCategory, out stateEntry)){
//EntityObject already attached in context, get it
newCategory = (EntityObject)stateEntry.Entity;
}else{
ctx.AttachTo("CategorySet", newCategory);
}
The modified code is still giving same error:
"[System.InvalidOperationException] = {"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
Please advise ?
Thank You
QUESTION ADDENDUM:
More problems attaching Entities when using ObjectContext having lifetime of Http Request.
For Example, if we have 'AppUser','Category' and Department entities.
public class AppUser : System.Data.Objects.DataClasses.EntityObject{
public int Uid {get; set;}
public string UserName {get; set;}
public string Password {get; set;}
public Department Dept {get; set;}
public Category catg {get; set;}
...........
}
AppUser has relationship with Department and Category Entities.
Now when trying to attach 'user':
user = new AppUser{Uid=1,catg = new Category {categoryId=10}, Dept = new Department{departmentId=101}, ...}
var key = ctx.CreateEntityKey("AppUserSet", user);
if (ctx.ObjectStateManager.TryGetObjectStateEntry(key, out stateEntry)) {
will work ONLY if, in context :
there is NO Category with categoryId=10, and
there is NO Department with departmentId=101
One option, is to ensure context does not have attached entities by always retrieving using NOMERGE NoTracking option. BUT I found following problems with MergeOption.NoTracking:
Second call would still result in db hit
You don't get EntityKeys on EntityRefs. So EntityKey of XXXReference is null,which means NO FK Stub. Please see.
How to get EntityKey of Reference w/o loading both ends (both entities)?
Even though Entity are Detached, they have a reference to the DataContext (via entity._realtionships._context). Please see.
Please advise.
Thank You.
Your code is using two different contexts. You check one, and then attach to the other:
if( csContext. //...){
///
}else{
ctx. // ...
}
The entity appears to be in ctx, but not in csContext. My advice is to use only one context at a time whenever possible; it's much less confusing.
Update
OK, you've changed the code in your question.
My guess is that your stub object doesn't have an EntityKey, so TryGetObjectStateEntry is returning false. Try:
if( ctx.ObjectStateManager.TryGetObjectStateEntry(
new EntityKey("MyEntities.CategorySet", "CategoryId", CategoryIdSelected),
out stateEntry)){
Obviously, replace "MyEntities" with the actual model name.