Context message for UPDATE is not working - c#

I am working on a CRM Dynamics Plugin. There is a field on custom entity named "email". I want to make sure that for two entity records email addresses should be unique. For that purpose I have written following code:
public class Class1 : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
// Get a reference to the organization service.
IOrganizationServiceFactory factory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
//</snippetAccountNumberPlugin2>
// Verify that the target entity represents an account.
// If not, this plug-in was not registered correctly.
if (context.MessageName.ToUpper() == "CREATE")
{
if (entity.LogicalName == "new_assignment1entity")
{
try
{
QueryExpression query = new QueryExpression("new_assignment1entity");
query.ColumnSet.AddColumns("new_email");
EntityCollection result1 = service.RetrieveMultiple(query);
foreach (var a in result1.Entities)
{
int size = result1.Entities.Count;
if (a.Attributes["new_email"].ToString().Equals(entity["new_email"]))
throw new InvalidPluginExecutionException("Duplicate Email found!");
}
}
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
{
//You can handle an exception here or pass it back to the calling method.
throw new InvalidPluginExecutionException("Some problem occurred while Querying Records!");
}
}
}
else if (context.MessageName.ToUpper() == "UPDATE")
{
if (entity.LogicalName == "new_assignment1entity")
{
try
{
QueryExpression query = new QueryExpression("new_assignment1entity");
query.ColumnSet.AddColumns("new_email");
EntityCollection result1 = service.RetrieveMultiple(query);
foreach (var a in result1.Entities)
{
int size = result1.Entities.Count;
if (a.Attributes["new_email"].ToString().Equals(entity["new_email"]))
throw new InvalidPluginExecutionException("Duplicate Email found!");
}
}
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
{
//You can handle an exception here or pass it back to the calling method.
throw new InvalidPluginExecutionException("Some problem occurred while Querying Records!");
}
}
}
}
}
}
When User creates a new entity record with duplicate email address this code works and shows a dialog box printing error message. But when User edit an existing record (update and existing record) and makes the email address duplicate then this code does not work and updated record with duplicated email address saved.
I am guessing that Context message with UPDATE else part is not working.
Please help me out.

It's not really worth trying to debug this as unfortunately you are going about this in a horribly inefficient way. (Though the most likely cause is the way you are querying being subject to a "feature" of CRM which means you are not querying all the records you think you are).
In short, your code says:
Get me ALL(*) instances of the new_assignment1entity entity
Look at each record until I find one with an email address that matches (case-sensitive) the value just provided in the update
Throw an exception when you encounter the first exact match (otherwise continue with the transaction)
Mains points of note:
QueryExpression will only return the maximum first 5000 records in CRM
You should be filtering your query to only return new_assignment1entity records where the new_email attribute matches the provided value
String.Equals(string) is case-sensitive so to truly check for a duplicate, you should convert the case of each value
Your size variable serves no purpose
Your code will throw an exception if the new/updated record has no value for new_email. You should check that the attribute exists before attempting to access it

I resolved this issue. The problem why only Create execution flow was running and not Update is that I had only registered the plugin for create message step. To overcome this issue, I added a new step in same plugin and registered it with update message as show in following screenshot:
And it worked like charm.
Apart from this, #GregOwens mentioned very helpful points.These should follow as best practices in CRM Development.

Related

Easy tables with Xamarin Forms - InvalidOperationException

I am using this tutorial in order to connect a xamarin.forms app with easy tables. I cannot add data to the database in Azure as i get
System.InvalidOperationException
The error message is the following
An insert operation on the item is already in the queue.
The exception happends in the following line of code.
await usersTable.InsertAsync(data);
In order to add a user
var user = new User { Username = "username", Password = "password" };
bool x = await AddUser(user);
AddUser
public async Task<bool> AddUser(User user)
{
try
{
await usersTable.InsertAsync(user);
await SyncUsers();
return true;
}
catch (Exception x)
{
await new MessageDialog(x.Message.ToString()).ShowAsync();
return false;
}
}
SyncUsers()
public async Task SyncUsers()
{
await usersTable.PullAsync("users", usersTable.CreateQuery());
await client.SyncContext.PushAsync();
}
where
IMobileServiceSyncTable<User> usersTable;
MobileServiceClient client = new MobileServiceClient("url");
Initialize
var path = Path.Combine(MobileServiceClient.DefaultDatabasePath, "DBNAME.db");
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<User>();
await client.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
usersTable = client.GetSyncTable<User>();
Please check your table. You probably have added the item already. Also, I would suggest that you don't set the Id property for your entity, because you might be inserting a same ID that's already existing in your table. It's probably the reason why the exception is appearing.
Hope it helps!
Some debugging you can do:
1) Turn on diagnostic logging in the backend and debug the backend: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter8/developing/#debugging-your-cloud-mobile-backend
2) Add a logging delegating handler in your MobileServiceClient setup: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter3/server/#turning-on-diagnostic-logs
The MobileServicePushFailedException contains an inner exception that contains the actual error. Normally, it is one of the 409/412 HTTP errors, which indicates a conflict. However, it can also be a 404 (which means there is a mismatch between what your client is asking for and the table name in Easy Tables) or 500 (which means the server crashed, in which case the server-side diagnostic logs indicate why).
Easy Tables is just a Node.js service underneath the covers.

Generic Create or Update method for CRM 2016 Early bound entities

I've got this method I'm trying to create for CRM.
internal Guid CreateOrUpdateRecord(Entity entity)
{
var guid = Guid.Empty;
if (entity.Id == null || entity.Id == Guid.Empty)
{
guid = _serviceProxy.Create(entity);
}
else
{
_XRM.UpdateObject(entity);
_XRM.SaveChanges();
//_serviceProxy.Update(entity);
guid = entity.Id;
}
return guid;
}
The purpose of it being that I dont need to care if an object is new or gotten from CRM so that my code can just set the variables and throw it in this method to save or update it. With this I don't need to create if structures in multiple places to deal with this issue every time.. I'm using this for multiple entity types.
The code is however giving me some grief in the update method.
This code
_XRM.UpdateObject(entity);
_XRM.SaveChanges()
has a tendency to throw:
The context is not currently tracking the 'xxx' entity.
and this
_serviceContext.Update(entity);
throws:
EntityState must be set to null, Created (for Create message) or
Changed (for Update message) CRM C#
So, any suggestions as to how I should create a single method I can throw any Entity into and it will get updated or created accordingly.
Update
changed the update portion of the method to:
{
if(_XRM.IsAttached(entity) == false)
{
_XRM.Attach(entity);
}
_XRM.UpdateObject(entity);
_XRM.SaveChanges();
guid = entity.Id;
}
Not sure if this is the best way, but it seems to work.
I believe just creating an entity does not add it, which is why you have to attach it. This method is a rather dangerous method in that it doesn't allow for Alternate Keys in update. In that case, the entity will not have an Id, but should have the appropriate attributes that define the alternate key used for the update.

Entity Framework 5 Update works only once per object / row

I'm using Entity Framework 5 with MySQL Database and just wanted to update a row attribute "user_loginstatus" between 0 and 1. The first time when I log in via client it updates just fine for the first attempt, after trying to update again it doesn't do anything with no exception.
I log in like this:
public async void LoginExecute()
{
// Checking Connection before etc...
if (await _dataService.IsLoginDataValidTask(UserObj.Username, md5))
{
Trace.WriteLine("LoginCommand Execute: Eingeloggt");
UserObj = await _dataService.GetUserDataTask(UserObj.Username);
await _dataService.SetUserStatusTask(UserObj.Id, 1);
await _dataService.WriteLog(UserObj.Id, "login", "Programm", GetLocalAdress());
Messenger.Default.Send(UserObj);
Messenger.Default.Send(new NotificationMessage("GoToMenuPage"));
}
else
{
// Error Stuff...
}
}
SetUserStatus Method in DataService Class
public Task SetUserStatusTask(int id, int status)
{
return Task.Factory.StartNew(() =>
{
try
{
var user = _entities.users.Find(id);
user.user_loginstatus = status;
_entities.SaveChanges();
}
catch (Exception ex)
{
Trace.WriteLine("DataService SetUserStatusTask: " + ex.Message);
}
});
}
GetUserData Method in DataService Class
public Task<User> GetUserDataTask(string username)
{
return Task.Factory.StartNew(() =>
{
try
{
var user = from us in _entities.users
where us.user_name.Equals(username)
select new User
{
Id = us.user_id,
Username = us.user_name,
FirstName = us.user_firstname,
LastName = us.user_lastname,
Gender = us.user_gender,
Email = us.user_mail,
Group = us.user_usergroup,
Avatar = us.user_avatar,
LoginStatus = 1
};
return user.FirstOrDefault();
}
catch (Exception ex)
{
Trace.WriteLine("DataService GetUserDataTask: " + ex);
return null;
}
});
}
So "users" is my table from the database and "User" / "UserObj" my custom Object.
With the Messenger (from MVVM Light) I just set via MainViewModel the Views, reset the unused ViewModels (ViewModel = new VieModel(...); or ViewModel = null;) and pass the current / logged in User Object.
With the same strategy I just Logout like this
public ICommand LogoutCommand
{
get
{
return new RelayCommand(async () =>
{
await _dataService.SetUserStatusTask(CurrentUser.Id, 0);
if(CurrentUser.Id > 0 && IsLoggedIn)
await _dataService.WriteLog(CurrentUser.Id, "logout", "Programm", GetLocalAdress());
IsLoggedIn = false;
CurrentUser = new User();
Messenger.Default.Send(new NotificationMessage("GoToLoginPage"));
});
}
}
So I can log in with my running Client so often I want, but the "user_loginStatus" only sets the changes the first login time to 1 and back to 0, but when I log out then and login back with the same user, it wont change it anymore. When I login (still same running Client) with another user it sets again the first time the "user_loginstatus" to 1 and back to 0 and then only again when I restart my Client..
What could I do wrong?
This is just basically from my comment regarding the original question:
I had similiar problems several times. Usually it is based on the fact that the entity you modified can't be validated properly and your dbContext fails without a proper exception because it still holds on to false entity. If this is the case you could circumvent this problem by using scoped contexts and embedding your data access operations in a using statement.
Alternatively you could try to explicitly tell EF that the entity has changes e.g.:
_entities.Entry(user).State = EntityState.Modified;
Regarding your other question:
In theory you shouldn't have to tell EF explicitly that the entity's values have changed. Change tracking should do that automatically. The only exception i could think of, is when you try to modify an entity that is explicitly not tracked anymore. When you call _entities.Find(id) it will look in the context if it finds the object with the matching primary key value and load it. Since you already modified this object before, the context will simply get the old object you already modified to set the login status the first time.
This "old" object is probably not tracked anymore and you have to tell EF explicitly that it has changed, by changing it's state from attached to modified.
in LoginExecute() you have UserObj, but in LogoutCommand() you have CurrentUser. Is it OK?

Generic Error with MS CRM Plugin

I am trying to run a sample plugin for MS CRM. but I am getting following error:
An error occurred. Contact a system administrator or refer to the
Microsoft Dynamics CRM SDK troubleshooting guide.
here is the code:
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("account") &&
context.InputParameters["account"] is Entity)
{
// Obtain the target entity from the input parmameters.
Entity entity = (Entity)context.InputParameters["account"];
try
{
//check if the account number exist
if (entity.Attributes.Contains("account number") == false)
{
//create a task
Entity task = new Entity("task");
task["subject"] = "Account number is missing";
task["regardingobjectid"] = new EntityReference("account", new Guid(context.OutputParameters["id"].ToString()));
//adding attribute using the add function
// task["description"] = "Account number is missng for the following account. Please enter the account number";
task.Attributes.Add("description", "Account number is missng for the following account. Please enter the account number");
// Obtain the organization service reference.
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// Create the task in Microsoft Dynamics CRM.
service.Create(task);
}
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}//end class
This is an example code, and I have verified that all the entities and fields which are utilized by this plugin are defined and are at there places. but I am continuously getting this Business Error.
I found the Solution: Instead of explicitly mentioning "account", we have to use:
Entity entity = (Entity)context.InputParameters["Target"];
The second reason for error was the restriction inside CRM which was not allowing creation of new accounts. Works fine when used to create new "contact."
Thanks a lot everyone for help.
Please check it like this
Entity entity = context.InputParameters["account"] as Entity;
some times that doesn't work properly.

Deleting a chlid object from a collection with MVC3 Entity Framework 4.3 Code First

I have found numerous posts with very similar problems as mine, but they all seem to use a DeleteObject() method that doesnt appear to exist in the version of EF i am using! (although its more than likely i am looking in the wrong places)
Anyway, my problem. I have two classes, User and Email where a user can have many emails. I have an edit form that will quite happily allow me to edit all email addresses attached to each user. I would like to allow each email address to be deleted simply by clearing the text box in the edit form (to be done using JavaScript function triggered by a remove button associated with each box later on). To this end, when the data is posted back, I loop through all the email addresses and test if the address property is null, at which point i want to delete that Email object completely. Below is my edit controller that is (not) doing the business:
[HttpPost]
public ActionResult Edit(int id, User User)
{
try
{
foreach (Email Email in User.Emails)
{
if (Email.Address == null)
{
User.Emails.Remove(Email);
}
else
{
db.Entry(Email).State = EntityState.Modified;
}
}
db.Entry(User).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return RedirectToAction("Index");
}
}
This works to a point, using the debugger i can see that it is quite happily removing the Email object from the User, but as soon as it has done that once it will throw an exception when it tries to go through the loop again:
Message=Collection was modified; enumeration operation may not execute.
my question: what is the correct method of deleting the Email object, not only from the User, but from my db too?
Thanks in advance ;-)
EDIT:
I have modified my controller to create two lists, one for Emails with and address and one for those where the address is null, and loop through those instead:
[HttpPost]
public ActionResult Edit(int id, User User)
{
try
{
var notNullEmails = User.Emails.Where(e => e.Address != null).ToList();
foreach (Email Email in notNullEmails)
{
db.Entry(Email).State = EntityState.Modified;
}
var isNullEmails = User.Emails.Where(e => e.Address == null).ToList();
foreach (Email Email in isNullEmails)
{
db.Emails.Remove(Email);
}
db.Entry(User).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return RedirectToAction("Index");
}
}
But that resulted in this exception:
Message=The object cannot be deleted because it was not found in the ObjectStateManager.
So i adapted the foreach used for the null addresses to explicitly pull the email to be deleted into the context like so:
var isNullEmails = User.Emails.Where(e => e.Address == null).ToList();
foreach (Email Email in isNullEmails)
{
Email EmailToDelete = db.Emails.Find(Email.Id);
db.Emails.Remove(EmailToDelete);
}
But that results in this exception:
Message=Value cannot be null.
I think i am getting there thanks to you guys, but i could use a little more help please :-)
You have two different problems:
One is, you can't remove items from a collection as you iterate the collection. Iterate through a List copy uoi make with .ToList() instead.
The other is, calling the Remove() method you are calling is only removing the relationship to the User - not actually removing it from the database. This would generate a referential integrity error, if the db enforces it.
Instead, You want to call Remove on the Emails DbSet.
context.emails.Remove(email);
Remove() is the method used by the DbContext. DeleteObject and RemoveObject are older/alternative APIs
I know you said about not using DeleteObject, but I had the same problem and thought I should post the answer here for others searching about this issue:
int i = User.Emails.Count - 1;
while (true)
{
if (i<0)
{
break;
}
Db.Emails.DeleteObject(User.Emails.ElementAt<Email>(i));
i--;
}
I know this is really old, but i thought i should probably answer it with the method i have found to actually work:
foreach(Email email in User.Emails)
{
if(string.IsNullOrEmpty(email.Address))
{
db.Entry(email).State = System.Data.EntityState.Deleted;
}
}
db.SaveChanges();
I think this is the simplest of any solution that i have seen so far, but feel free to correct me if there is a better way.

Categories

Resources