EF : related entities are set as INSERT instead of being only referenced to the added entity - frameworks

I tried to find an answer through the related questions I got but I didn't see the same situation I have now. I a beginner with this framework.
The thing is that in the DB the TopicFeedbackType is always referred but TopicNavigatedUrl and Product are always inserted event if I attach the entity. What am I doing wrong? I attach the found entity to the correct entity set name. The entity gets attached. I noticed that before the p_TopicQuickFb have one of its property updated by an attached entity, its EntityKey is null, but when the currentNavUrl(for example) is set, the EntityKey of p_TopicQuickFb is not null anymore. Its value is "EntitySet=TopicQuickFeedbacks" but there is no id.
I am really lost in there.
public void AddTopicQuickFeedback(TopicQuickFeedback p_TopicQuickFb, string p_SessionID)
{
TopicFeedbackType currentType = this.GetTopicFeedbackType(p_TopicQuickFb.TopicFeedbackType.FeedbackType);
bool currentTypeAttached = false;
TopicNavigatedUrl currentNavUrl = this.GetTopicNavigatedUrl(p_TopicQuickFb.TopicNavigatedUrl.Url);
bool currentNavUrlAttached = false;
Product currentProduct = this.GetProduct(p_TopicQuickFb.Product.Name, p_TopicQuickFb.Product.MajorVersion, p_TopicQuickFb.Product.MinorVersion);
bool currentProductAttached = false;
using (COHFeedbackEntities context = GetObjectContext())
{
TopicFeedback tf = GetTopicFeedback(p_SessionID, context);
if (tf != null)
{
if (currentType != null)
{
p_TopicQuickFb.TopicFeedbackType = null;
context.AttachToOrGet<TopicFeedbackType>("TopicFeedbackTypes", ref currentType);
currentTypeAttached = true;
p_TopicQuickFb.TopicFeedbackType = currentType;
}
if (currentNavUrl != null)
{
p_TopicQuickFb.TopicNavigatedUrl = null;
context.AttachToOrGet<TopicNavigatedUrl>("TopicNavigatedUrls", ref currentNavUrl);
currentNavUrlAttached = true;
p_TopicQuickFb.TopicNavigatedUrl = currentNavUrl;
}
if (currentProduct != null)
{
p_TopicQuickFb.Product = null;
context.AttachToOrGet<Product>("Products", ref currentProduct);
currentProductAttached = true;
p_TopicQuickFb.Product = currentProduct;
}
tf.TopicQuickFeedbacks.Add(p_TopicQuickFb);
context.SaveChanges();
context.Detach(tf);
if (currentNavUrlAttached)
{
context.TopicNavigatedUrls.Detach(currentNavUrl);
}
if (currentProductAttached)
{
context.Products.Detach(currentProduct);
}
if (currentTypeAttached)
{
context.TopicFeedbackTypes.Detach(currentType);
}
}
}
}
I found the method in this post : Is is possible to check if an object is already attached to a data context in Entity Framework?
public static void AttachToOrGet<T>(this System.Data.Objects.ObjectContext context, string entitySetName, ref T entity)
where T : IEntityWithKey
{
System.Data.Objects.ObjectStateEntry entry;
// Track whether we need to perform an attach
bool attach = false;
if (
context.ObjectStateManager.TryGetObjectStateEntry
(
context.CreateEntityKey(entitySetName, entity),
out entry
)
)
{
// Re-attach if necessary
attach = entry.State == EntityState.Detached;
// Get the discovered entity to the ref
entity = (T)entry.Entity;
}
else
{
// Attach for the first time
attach = true;
}
if (attach)
{
context.AttachTo(entitySetName, entity);
}
}
Test method:
User user = new User(true, false, false);
string commentStr = "This is my comment";
Product product = new Product("ProductName", 7, 0);
TopicFeedbackComment commFeedback = new TopicFeedbackComment(commentStr, new TopicNavigatedUrl("http://testurl.com/test0"), product);
TopicFeedback feedback = new TopicFeedback(sessionID, user, FeedbackState.New);
provider.AddTopicFeedback(feedback);
TopicFeedback addedFeedback = provider.RetrieveTopicFeedback(sessionID);
provider.AddTopicFeedbackComment(commFeedback, sessionID);
Running this again and again do just INSERT to the
Can't post images so I can provide schema it if necessary.

My answer is in my last comment. I found it by myself.
If someone would like to comment why it's working this way it would be nice! :)

Related

Working on pre-operation plug-in to update "Modified By" field in MSCRM -- Need help fixing code

I am trying to update the "Modified By" field based on a text field called "Prepared By", which contains the name of a user. I've created a pre-operation plug-in to do this and believe I am close to done. However, the "Modified By" field is still not successfully getting updated. I am relatively new to coding and CRM, and could use some help modifying the code and figuring out how I can get this to work.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace TimClassLibrary1.Plugins
{
public class CreateUpdateContact : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
tracingService.Trace("Start plugin");
tracingService.Trace("Validate Target");
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
tracingService.Trace("Retrieve Target");
var target = (Entity)context.InputParameters["Target"];
String message = context.MessageName.ToLower();
SetCreatedByAndModifiedBy(tracingService, service, target, message);
}
private void SetCreatedByAndModifiedBy(ITracingService tracingService, IOrganizationService service, Entity target, string message)
{
tracingService.Trace("Start SetPriceList");
tracingService.Trace("Validate Message is Create or Update");
if (!message.Equals("create", StringComparison.OrdinalIgnoreCase) && !message.Equals("update", StringComparison.OrdinalIgnoreCase))
return;
tracingService.Trace("Retrieve Attributes");
var createdByReference = target.GetAttributeValue<EntityReference>("new_createdby");
var modifiedByReference = target.GetAttributeValue<EntityReference>("new_modifiedby");
tracingService.Trace("Retrieve And Set User for Created By");
RetrieveAndSetUser(tracingService, service, target, createdByReference, "createdby");
tracingService.Trace("Retrieve And Set User for Modified By");
RetrieveAndSetUser(tracingService, service, target, modifiedByReference, "modifiedby");
}
private void RetrieveAndSetUser(ITracingService tracingService, IOrganizationService service, Entity target, EntityReference reference, string targetAttribute)
{
tracingService.Trace("Validating Reference");
if (reference == null)
return;
tracingService.Trace("Retrieving and Validating User");
var user = RetrieveUserByName(service, reference.Name, new ColumnSet(false));
if (user == null)
return;
tracingService.Trace("Setting Target Attribute");
target[targetAttribute] = user.ToEntityReference();
}
private Entity RetrieveUserByName(IOrganizationService service, string name, ColumnSet columns)
{
var query = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = columns,
Criteria = new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression
{
AttributeName = "fullname",
Operator = ConditionOperator.Equal,
Values = { name }
}
}
}
};
var retrieveResponse = service.RetrieveMultiple(query);
if (retrieveResponse.Entities.Count == 1)
{
return retrieveResponse.Entities.FirstOrDefault();
}
else
{
return null;
}
}
}
}
If you do get use from method Retreiveusernyname then you have to use below code
target[“modifiedby”] = new EntityRefrence(user.logicalname,user.id);
I don't see anything obviously wrong with your update, however you are taking a complicated and unnecessary step with your RetrieveUserByName() method. You already have EntityReference objects from your new_createdby and new_modifiedby fields, you can simply assign those to the target:
if (message.Equals("create", StringComparison.OrdinalIgnoreCase))
{
target["createdby"] = target["new_createdby];
}
else if (message.Equals("update", StringComparison.OrdinalIgnoreCase))
{
target["modifiedby"] = target["new_modifiedby];
}
If new_createdby and new_modifiedby are not entity references, then that would explain why your existing code does not work, if they are, then use my approach.

two similar plugins in Dynamics CRM 2016 ONPREMISE to merge into one

I have two similar plugins in Dynamics CRM 2016 ONPREMISE to merge into one.
They're
registered to the same entity,
both triggered by update message,
one plugin check value of 3 fields, the other check value of 4 fields. If all equal to a specified value, then go on. If not, return.
4.set, map or calculate the value from old record to new record. two plugins handle two sets of fields.
create a new record.
What I can think is "if else-if " structure. But it looks so naive. Anybody have any advice?
The other plugin checks 3 other fields and execute similar action creating new record with some other fields set or mapped.
Thanks,
protected void ExecuteApplication(LocalPluginContext localContext)
{
IPluginExecutionContext context = null;
IOrganizationService service = null;
ITracingService tracer = null;
context = localContext.PluginExecutionContext;
service = localContext.OrganizationService;
tracer = localContext.TracingService;
try
{
// ensure we have an application and update message
Entity application = new Entity(applicationEntityName);
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
application = (Entity)context.InputParameters["Target"];
if (!application.LogicalName.ToLower().Equals(this.applicationEntityName))
{
return;
}
}
else
{
return;
}
if (context.MessageName.ToLower() != "update")
{
return;
}
// Fetch data from PreImage
Entity postImageApplication = context.PostEntityImages["PostImage"];
//check three fields are not null
if (application.GetAttributeValue<OptionSetValue>("statuscode") == null ||
postImageApplication.GetAttributeValue<EntityReference>("new_service").Name == null ||
postImageApplication.GetAttributeValue<EntityReference>("new_source").Name == null)
{
return;
}
if (
application.GetAttributeValue<OptionSetValue>("statuscode").Value == 881780003 &&
postImageApplication.GetAttributeValue<EntityReference>("new_service").Name == "CIC"
)
// process if the update meets the criteria
{
Entity newApplication = new Entity("new_application");
// set
newApplication.Attributes.Add("new_effectiveapplication", true);
newApplication.Attributes.Add("new_confirmdate", DateTime.Now);
newApplication.Attributes.Add("new_signdate", DateTime.Now);
//mapped
if (postImageApplication.Attributes.Contains("new_client"))
{
newApplication.Attributes.Add("new_client", postImageApplication["new_client"]);
}
if (postImageApplication.Attributes.Contains("new_servicecentre"))
{
newApplication.Attributes.Add("new_servicecentre", postImageApplication["new_servicecentre"]);
}
service.Create(newApplication);
}
else
{
return;
}
I like to abstract unwieldy predicates into their own method.
How about something like this:
private bool allFieldsHaveValues()
{
return application.GetAttributeValue<OptionSetValue>("statuscode") != null
&& postImageApplication.GetAttributeValue<EntityReference>("new_service").Name != null
&& postImageApplication.GetAttributeValue<EntityReference>("new_source").Name != null;
}
private bool valuesAreValid()
{
return application.GetAttributeValue<OptionSetValue>("statuscode").Value == 881780003
&& postImageApplication.GetAttributeValue<EntityReference>("new_service").Name == "CIC";
}
if (allFieldsHaveValues() && valuesAreValid())
{
Entity newApplication = new Entity("new_application");

Crm plugin update fails

I have created two new fields named "Price" for quote and quote product and I want to update the second every time I update the first.
Here is my code:
protected void ExecutePostAccountUpdateContacts(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
string oldPrice = "";
string newPrice = "";
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
var ServiceContext = new OrganizationServiceContext(service);
ITracingService tracingService = localContext.TracingService;
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
// get the post entity image
Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
if (preImageEntity.Attributes.Contains("Price"))
{
oldPrice = (string)preImageEntity.Attributes["Price"];
}
if (postImageEntity.Attributes.Contains("Price"))
{
newPrice = (string)postImageEntity.Attributes["Price"];
}
if (newPrice != oldPrice)
{
try
{
//Create query to get the related contacts
var res = from c in ServiceContext.CreateQuery("Products")
where c["parentQuoteid"].Equals(entity.Id)
select c;
foreach (var c in res)
{
Entity e = (Entity)c;
e["Price"] = newPrice;
ServiceContext.UpdateObject(e);
}
ServiceContext.SaveChanges();
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}
Although you haven't asked a question, your query isn't quite right. So I am assuming your plugin fails when querying for product with a parentquoteid.
Not all linq operators are implemented, also , pass the entity logical name to the create query as a parameter, so instead of Products, just product. There is no out of the box field called parentquoteid, are you missing your custom attribute prefix?
var res = from c in ServiceContext.CreateQuery("product")
where c.GetAttributeValue<Guid>("new_parentquoteid") == entity.Id
select c;

Entity Framework not populating Navigation property on savechanges

I am currently trying to build a web front end for a Report editor that allows the user to create different sections that are basically text fields. The first screen I allow the user to see causes them to populate some very minor initial data and I use it to spawn a new Report table record. I am using a t4 to generate my simple repositories:
public Entities.Result Add(ref Report entity) {
Entities.Result result;
try {
this.Context.Reports.AddObject(entity);
this.Context.SaveChanges();
result = new Entities.Result {
Success = true,
Messages = new List<Entities.ResultMessage>()
};
} catch (System.Exception ex) {
this.EventLogger.Error(ex);
List<Entities.ResultMessage> messages = new List<Entities.ResultMessage>();
messages.Add(new Entities.ResultMessage { Message = ex.ToString(), MessageSeverity = 10 });
result = new Entities.Result {
Success = false,
Messages = messages
};
}
return result;
}
and the same thing when they add a new section
public Entities.Result Add(ref ReportSection entity) {
Entities.Result result;
try {
this.Context.ReportSections.AddObject(entity);
this.Context.SaveChanges();
result = new Entities.Result {
Success = true,
Messages = new List<Entities.ResultMessage>()
};
} catch (System.Exception ex) {
this.EventLogger.Error(ex);
List<Entities.ResultMessage> messages = new List<Entities.ResultMessage>();
messages.Add(new Entities.ResultMessage { Message = ex.ToString(), MessageSeverity = 10 });
result = new Entities.Result {
Success = false,
Messages = messages
};
}
return result;
}
I call this from the Index controller in the form of:
Report newReport = new Report() {
ApprovalDate = null,
Approver = string.Empty,
Author = System.Web.Security.Membership.GetUser().UserName,
Created = DateTime.Now,
IsActive = true,
IsSubmitted = false,
ReleaseDate = null,
SerialNumber1 = string.Empty,
SerialNumber2 = string.Empty
};
var result = this._reportRepository.Add(ref newReport);
this goes through just fine and I get the record in the database. I then take some of their entries a populate an initial ReportSection object like so:
var newSection = new ReportSection {
DisplayOrder = 1,
ReportId = newReport.Id,
ReportSectionTypeId = 1,
SectionText = title
};
var r = this._reportSectionRepository.Add(ref newSection);
You'll notice I'm referring to the newReport.Id in there so I have the valid ID returned from the repository add method. The section add method returns to me a valid newSection object that has a valid ID and when I check in the db it exists. However, EF is not populating the navigational property on newSection.Report that lets me get between the two. This is what I'm trying to figure out, why is this not accessible? I have a temporary work around that defeats some of my DAL validation logic, but I can load the Report object then add the ReportSection through Report.ReportSections and then call an update method on the repo that does an ApplyCurrentValues.
A bit more detail:
Visual Studio 2010
C# .Net 4.0
MVC 2.0
EF 4.0 ( or maybe 4.1 )
I am using Ninject to fill in the repositories:
Bind<IReportRepository>().To<ReportRepository>().WithConstructorArgument("connectionString",
ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString);
You could directly set the navigation property instead of the foreign key property:
var newSection = new ReportSection {
DisplayOrder = 1,
Report = newReport,
ReportSectionTypeId = 1,
SectionText = title
};
If the context in _reportRepository and _reportSectionRepository are the same and newReport is still attached to it that's all you need to do.
If the contexts are not the same or newReport is not attached to the context, you must attach newReport first to the context:
public Entities.Result Add(ref ReportSection entity) {
Entities.Result result;
try {
this.Context.Reports.Attach(entity.Report);
this.Context.ReportSections.AddObject(entity);
this.Context.SaveChanges();
// etc.

Entity Framework - Saving child entities on update

I have an Invoice entity and it has child InvoiceLog entities. When I first create an Invoice and add its InvoiceLog entities and save, it works fine. However, if I then edit the Invoice and try to add additional InvoiceLog entities, it completely ignores the new InvoiceLog entities and doesn't save them at all. Any ideas what I'm doing wrong?
//POST: /Secure/Invoices/Save/
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(Invoice invoice)
{
invoice.UpdateDate = DateTime.Now;
invoice.DeveloperID = Developer.DeveloperID;
invoice.InvoiceStatusID = (int)Enums.InvoiceStatus.Open;
if (invoice.InvoiceID == 0)
{//inserting new invoice.
DataContext.InvoiceData.Insert(invoice);
}
else
{//attaching existing invoice.
DataContext.InvoiceData.Attach(invoice);
}
AddHours(invoice);
//save changes.
DataContext.SaveChanges();
//redirect to invoice list.
return RedirectToAction("Index");
}
private void AddHours(Invoice invoice)
{
//get existing logs.
IQueryable<InvoiceLog> existingLogs = null;
if(invoice.InvoiceID > 0)
{
existingLogs = DataContext.InvoiceData.GetLogs(invoice.InvoiceID);
}
//create new logs.
var numDays = invoice.EndDate.Subtract(invoice.StartDate).TotalDays;
for (int k = 0; k <= numDays; k++)
{
//check if log already exists.
var existingLog = existingLogs.ToList().FindIndex(l => l.LogDate == invoice.StartDate.AddDays(k));
if (existingLog == -1)
{
//add new log.
var log = new InvoiceLog();
log.CreateDate = DateTime.Now;
log.UpdateDate = DateTime.Now;
log.Hours = 0;
log.InvoiceID = invoice.InvoiceID;
log.LogDate = invoice.StartDate.AddDays(k);
invoice.InvoiceLogs.Add(log);
}
}
}
Thanks,
Justin
You can try to add InvoiceLogs items before attaching the invoice:
else
{//attaching existing invoice.
AddHours(invoice);
DataContext.InvoiceData.Attach(invoice);
}