Entity Framework savechanges() update delete insert - entity-framework

I want my SaveChanges() function to update a record in my database and if the return value is '1' which is coming from my stored procedure then and only then 'delete' command (stored procedure) should not be executed.
Now, the problem is db.SaveChanges() (an instance of ObjectContext) is updating my record successfully but after updating, it executes the delete command. How should I tell my function that not to execute delete command.
using (var db = new PRLAdminEntities())
{
bool isExists = false;
string lastExisting = string.Empty;
string errorString = string.Empty;
db.Connection.Open();
trans = db.Connection.BeginTransaction();
//accounts to be sent back to client
var countriesToSendBack = new List<Polo.Common.Shared.Entities.Country>();
//process each account requiring database update
if (request.CountriesToUpdate != null)
{
foreach (var country in request.CountriesToUpdate)
{
//countriesToSendBack.Remove(country);
var temp = from row in db.Countries where row.Name.ToUpper() == country.Name.ToUpper() select row;
if (temp.Count<Polo.Common.Shared.Entities.Country>() > 0 && country.ChangeTracker.State == ObjectState.Added)
{
countriesToSendBack.Add(country);
db.Countries.ApplyChanges(country);
isExists = true;
lastExisting = country.Name;
errorString += country.Name + ", ";
//db.GetAllCountries();
//break;
continue;
}
if (country.ChangeTracker.State == ObjectState.Deleted)
{
db.DeleteObject(country);
}
//if a change or modification (not a delete)
if (country.ChangeTracker.State != ObjectState.Deleted)
{
//this account should be sent back
if (!countriesToSendBack.Contains((country)))
countriesToSendBack.Add(country);
if (country.Active == false)
{
db.Countries.ApplyCurrentValues(country);
}
}
//apply all changes
db.Countries.ApplyChanges(country);
}
if (isExists)
{
//response.Success = false;
//errorString.Replace(", " + lastExisting + ",", " & " + lastExisting);
//response.FaultMessage = "Duplicate Records";
}
}
//save all changes
int total = db.SaveChanges();
response.Success = true;
foreach (var countryItem in countriesToSendBack)
{
countryItem.Id = (from row in db.Countries where row.Name.ToUpper() == countryItem.Name.ToUpper() select row.Id).FirstOrDefault();
}
trans.Commit();
//refresh the account data which gets timestamp etc
db.Refresh(RefreshMode.StoreWins,countriesToSendBack);
//set the response values
response.Countries = countriesToSendBack;
}
}

Perhaps I misread your question, I do not totally get what you are trying to do.
But why not call SaveChanges() after the change and when all checks are positive perform a remove() and call savechanges() again?
There is no harm is calling SaveChanges() multiple times. It will mirror it's data to your database. If you perform a remove it will try to delete it in your database. That's the nice thing about it.. it does what you tell it to do ;-)

Related

FATAL_ERROR|System.LimitException: Too many SOQL queries: 201

I am getting too many SOQL queries 201 error in apex class
I tried to check the no of queries within loop
Below is exact error -
11:48:43.9 (2785518121)|FATAL_ERROR|System.LimitException: Too many SOQL queries: 201
Class.GEN_CalculateActToWinScores.calcUserEligible: line 1343, column 1
Class.GEN_ActonFactsScoreUserEligibleBatch.execute: line 74, column 1
11:48:43.9 (2851114444)|CODE_UNIT_FINISHED|GEN_ActonFactsScoreUserEligibleBatch
11:48:43.9 (2852614277)|EXECUTION_FINISHED
Below is the code for method GEN_CalculateActToWinScores.calcUserEligible -
// Method for set user as ready for AoF
public static void calcUserEligible(List<User> usersList ){
List<Act_on_Facts__c> actOnFactDelete = new List<Act_on_Facts__c>();
Set<String> oppOpenStageNameSet = new Set<String>();
oppOpenStageNameSet.add(GEN_Constants.OPPORTUNITY_IDENTIFY);
oppOpenStageNameSet.add(GEN_Constants.OPPORTUNITY_QUALIFY);
oppOpenStageNameSet.add(GEN_Constants.OPPORTUNITY_PROPOSE);
oppOpenStageNameSet.add(GEN_Constants.OPPORTUNITY_NEGOTIATE);
Set<String> oppReadOnlyRecordTypeNameSet = new Set<String>();
oppReadOnlyRecordTypeNameSet.add(GEN_Constants.READONLY_SINGLE_ACCOUNT_OPP_RECORDTYPENAME);
oppReadOnlyRecordTypeNameSet.add(GEN_Constants.READONLY_WON_AND_DONE_OPP_RECORDTYPENAME);
oppReadOnlyRecordTypeNameSet.add(GEN_Constants.READONLY_CHILD_OPP_RECORDTYPENAME);
oppReadOnlyRecordTypeNameSet.add(GEN_Constants.READONLY_MULTI_ACCOUNT_OPP_RECORDTYPENAME);
if(usersList.size() > 0){
List<Id> idList = new List<Id>();
Integer countResult = 0;
List<User> newUserList = new List<User>();
Integer limitQuery; //10000 //2001
Integer limitResult; //2000
if(CSL_ActOnFactsLimits__c.getValues('QUERY_LIMIT') != null){
limitQuery = Integer.valueOf(CSL_ActOnFactsLimits__c.getValues('QUERY_LIMIT').Value__c);
}
if(CSL_ActOnFactsLimits__c.getValues('MAX_QUERY_RESULTS') != null){
limitResult = Integer.valueOf(CSL_ActOnFactsLimits__c.getValues('MAX_QUERY_RESULTS').Value__c);
}
Boolean eligible = true;
for (User userElement : usersList){
String logDetail = ' QUERY_LIMIT:limitQuery: '+limitQuery+' MAX_QUERY_RESULTS:limitResult: '+limitResult;
logDetail += ' userElement.id: '+userElement.id;
eligible = true;
CSH_ActOnFacts_UserEligible__c userEligible = null;
if(CSH_ActOnFacts_UserEligible__c.getValues(userElement.id) != null){
userEligible = CSH_ActOnFacts_UserEligible__c.getValues(userElement.id);
}
CSH_ActOnFacts_UserEligible__c profileEligible = null;
if(CSH_ActOnFacts_UserEligible__c.getValues(userElement.ProfileId) != null){
profileEligible = CSH_ActOnFacts_UserEligible__c.getValues(userElement.ProfileId);
}
List<Act_on_Facts__c> actonfacts = [select id from Act_on_Facts__c where Lookup_User__c = : userElement.id limit 1];
if(userElement.ManagerId == null){
userElement.Line_Manager_Optional__c = true;
userElement.Act_On_Facts_Manager_List__c = null;
}
if(userElement.Default_Macro_Segment__c == null){
userElement.Default_Macro_Segment__c = 'None';
}
if (userElement.IsActive == false){
eligible = false;
}else if(profileEligible != null || userEligible != null) {
eligible = false;
}else{
countResult = [select count() from Account WHERE OwnerId = :userElement.id LIMIT :limitQuery];
logDetail += ' Account.countResult: '+countResult;
if(countResult!=null && countResult > limitResult ){
eligible = false;
}
if(eligible){
if(oppOpenStageNameSet !=null && oppOpenStageNameSet.size()>0 && oppReadOnlyRecordTypeNameSet !=null && oppReadOnlyRecordTypeNameSet.size()>0){
countResult = [select count() from Opportunity WHERE OwnerId = :userElement.id AND StageName IN:oppOpenStageNameSet AND RecordType.DeveloperName NOT IN: oppReadOnlyRecordTypeNameSet LIMIT :limitQuery];
logDetail += ' Opportunity.countResult: '+countResult;
if(countResult!=null && countResult > limitResult ){
eligible = false;
}
}
}
}
if(!eligible){
userElement.Act_on_Facts_Eligible__c = false;
// remove user from Act on Facts
if(actonfacts != null && actonfacts.size()>0){
for(Act_on_Facts__c a : actonfacts){
actOnFactDelete.add(a);
}
}
}else{
userElement.Act_on_Facts_Eligible__c = true;
}
logDetail += ' userElement.Act_on_Facts_Eligible__c: '+userElement.Act_on_Facts_Eligible__c;
ApplicationLog.logEntry(ApplicationLog.SEVERITY_INFO, 'A2WBatch', 'GEN_ActonFactsScoreUserEligibleBatch:'+userElement.id+': ', logDetail);
newUserList.add(userElement);
}
try{
if(newUserList.size()>0){
update newUserList;
system.debug('HC Update- newUserList ' + newUserList);
}
if(actOnFactDelete.size()>0){
delete actOnFactDelete;
system.debug('HC Update- actOnFactDelete ' + actOnFactDelete);
}
}catch (Exception e){
system.debug('Error updating user ' + e);
}
}
}
Code for GEN_ActonFactsScoreUserEligibleBatch.execute-
global void execute(Database.BatchableContext BC, List<sObject> scope){
CSH_A2W_Settings__c a2wCS = CSH_A2W_Settings__c.getInstance();
if(scope != null){
List<User> userList = scope;
if(userList.size()>0){
if(CSL_ActOnFactsLimits__c.getValues('run_userTrigger') != null){
CSL_ActOnFactsLimits__c run_userTrigger = CSL_ActOnFactsLimits__c.getValues('run_userTrigger');
run_userTrigger.Value__c = 'false';
update run_userTrigger;
}
//GEN_CalculateActOnFactsScores.calcUserEligible(userList); //Commented as part of Decommission activity of AoF
if(a2wCS != null && a2wCS.Enabled_in_Batches__c == True){
GEN_CalculateActToWinScores.calcUserEligible(userList);
}
}
}
}
I am trying to analysis what will be the best possible ways to remove these errors or is there any alternate way to implement the same.
All these are queries in a loop:
for (User userElement : usersList){
...
List<Act_on_Facts__c> actonfacts = [select id from Act_on_Facts__c where Lookup_User__c = : userElement.id limit 1];
...
countResult = [select count() from Account WHERE OwnerId = :userElement.id LIMIT :limitQuery];
...
[select count() from Opportunity WHERE OwnerId = :userElement.id AND StageName IN:oppOpenStageNameSet AND RecordType.DeveloperName NOT IN: oppReadOnlyRecordTypeNameSet LIMIT :limitQuery];
As a very quick & dirty solution you can change the batch's size (how many records are passed to each execute). Default is 200.
Call your class with optional parameter Database.executeBatch(new GEN_ActonFactsScoreUserEligibleBatch(), 10); and see if it helps.
"Proper" fix would require some restructuring, taking queries out of the loop, maybe using some Maps where user's id is the key...
If these were custom objects a "pro" Apex developer would cheat, make these queries in one go, pulling user and related lists, something like
SELECT Id,
(SELECT Id FROM Accounts__r LIMIT 1),
(SELECT Id FROM Opportunities__r WHERE ... LIMIT 1),
(SELECT Id FROM Act_On_Facts__r)
FROM User
WHERE Id IN :scope
This won't work here because relation from Account to owner doesn't have a nice name ("Accounts" won't work). You should be still able to do it on the custom object (last subquery in my example, you can call it outside of the loop)
You might still be able to pull something like that off but it'd probably require looking at sharing-related tables... I'd say doable but if you have time to play with it. If you don't - change scope size and call it a day. Will execute bit longer but 1-liner fix is a win in my book.

Only Update Entity (Parent or children) if Property Values Have Changed with EF

As I build a project with Entity Framework 6 (using EF for the first time), I noticed that when I only Update the relationships of an Entity, EF updates the main Entity too.
I can tell this is happening because I'm using System Versioned tables on Sql Server 2017.
This is a made up scenario, but most of the concept is here.
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
location.Name = locationsViewModel.Name;
// ... other properties
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
I assume, that by marking the entire Entity as Modified, EF will call the update statement.
How can I avoid an UPDATE to the parent Entity, if no properties have changed on the parent, but still Add/Update the child relationships?
I assume I have to check that each property has not changed and therefore I should not be setting location state to Modified, but how would I handle the newly added Times?
Update #1
So I tried what I mentioned and it works, but is this the correct way to do this?
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
/*******************
This is new part
*******************/
if (
location.Name != locationsViewModel.Name
// || ... test other properties
) {
location.Name = locationsViewModel.Name;
// ... other properties
_db.Entry(location).State = EntityState.Modified;
} else {
_db.Entry(location).State = EntityState.Unchanged;
}
/*******************/
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
/* removed, added above */
//_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
So after trial and error, I guess I misunderstood how EF handles the EntityState. I though if a child was Modified, you had to set the parent as Modified as well.
Gladly, that's not the case and the code below works as desired.
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
/*******************
This is new part
check if at least one property was changed
*******************/
if (
location.Name != locationsViewModel.Name
|| location.Ref != locationsViewModel.Ref
// || ... test other properties
) {
location.Name = locationsViewModel.Name;
location.Ref = locationsViewModel.Ref;
// ... other properties
// Tell EF that the Entity has been modified (probably not needed, but just in case)
_db.Entry(location).State = EntityState.Modified;
} else {
// Tell EF that the Entity has *NOT* been modified
_db.Entry(location).State = EntityState.Unchanged;
}
/*******************/
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
/* removed, added above */
//_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}

Handling concurrency exceptions when passing the objects ids and timestamps using jQuery

I have the following business scenario inside my Asp.net MVC 4 asset management system :-
Scenario 1) A user selects multiple servers , then he selects a Rack Tag ,and click on
assign . so the selected servers will be assigned to the new Rack.
Scenario 2) And i want to check for any concurrency exception , if for example the selected
servers have been modified by another user since they were retrieved .
so i have wrote the following jQuery which will send the object ids+timestamps to the action method:-
$('body').on("click", "#transferSelectedAssets", function () {
var boxData = [];
$("input[name='CheckBoxSelection']:checked").each(function () {
boxData.push($(this).val());
});
var URL = "#Url.Content("~/Server/TransferSelectedServers")";
$.ajax({
type: "POST",
url: URL,
data: { ids: boxData.join(","), rackTo: $("#rackIDTo").val()}
,
success: function (data) {
addserver(data); })});
and inside the action method i have the following code:-
public ActionResult TransferSelectedServers(string ids, int? rackTo)
{
if (ModelState.IsValid)
{
try
{
var serverIDs = ids.Split(',');
int i = 0;
foreach (var serverinfo in serverIDs)
{
var split = serverinfo.Split('~');
var name = split[0];
//System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] bytearray = Encoding.Default.GetBytes(split[1]);
i++;
var server = repository.FindServer_JTechnology(Int32.Parse(name));
if (server == null)
return Json(new { IsSuccess = false, reload = true, description = " Some Servers might have been deleted, Transferre process has been cancelled .", rackid = rackFrom }, JsonRequestBehavior.AllowGet);
server.RackID = rackTo;
server.timestamp = bytearray;
string ADusername = User.Identity.Name.Substring(User.Identity.Name.IndexOf("\\") + 1);
repository.InsertOrUpdateServer(server, ADusername, server.Technology.IT360ID.Value, server.IT360SiteID, new bool(), server.Technology);
}
repository.Save();
return Json(new { IsSuccess = true, description = i + " Server/s Transferred Successfully To Rack " + }, JsonRequestBehavior.AllowGet);
}
catch (DbUpdateConcurrencyException e)
{
return Json(new { IsSuccess = false, reload = true, description = "records has been modified by antoehr user" }, JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
return Json(new { IsSuccess = false, reload = true, description = " Server/s Can not Be Transferred to the Selected Rack " }, JsonRequestBehavior.AllowGet);
}
}
return RedirectToAction("Details", new { id = rackTo });
}
and the repository method looks as follow:-
public void InsertOrUpdateServer(TMSServer server, string username, long assetid, long? siteid = 0, bool isTDMHW = false, Technology t = null)
{
server.IT360SiteID = siteid.Value;
tms.Entry(server).State = EntityState.Modified;
var technology = tms.Technologies.Single(a => a.TechnologyID == server.TMSServerID);
technology.IsManaged = t.IsManaged;
tms.Entry(technology).State = EntityState.Modified;
InsertOrUpdateTechnologyAudit(auditinfo);
}
}
but currently if two users selects the same servers and assign them to tow different racks , no concurrency exception will be raised ?
Can anyone advice ? baring in mind that if two users edit single object then one of them will get an concurrent exception message. so my timestamp column is defined correctly.
Thanks

Merge Self-tracking entities

Graph of objects stored in the database and the same object graph is serialized into a binary package. Package is transmitted over the network to the client, then it is necessary to merge data from the package and data from the database.
Source code of merge:
//objList - data from package
var objectIds = objList.Select(row => row.ObjectId).ToArray();
//result - data from Database
var result = SomeService.Instance.LoadObjects(objectIds);
foreach (var OSobj in objList)
{
var obj = result.Objects.ContainsKey(OSobj.ObjectId)
? result.Objects[OSobj.ObjectId]
: result.Objects.CreateNew(OSobj.ObjectId);
var targetObject = result.DataObjects.Where(x => x.ObjectId == OSobj.ObjectId).FirstOrDefault();
targetObject.StopTracking();
var importedProperties = ImportProperties(targetObject.Properties, OSobj.Properties);
targetObject.Properties.Clear();
foreach (var property in importedProperties)
{
targetObject.Properties.Add(property);
}
targetObject.StartTracking();
}
return result;
And code of ImportProperties method:
static List<Properties> ImportProperties(
IEnumerable<Properties> targetProperties,
IEnumerable<Properties> sourceProperties)
{
Func<Guid, bool> hasElement = targetProperties
.ToDictionary(e => e.PropertyId, e => e)
.ContainsKey;
var tempTargetProperties = new List<Properties>();
foreach (var sourceProperty in sourceProperties)
{
if (!hasElement(sourceProperty.PropertyId))
{
sourceProperty.AcceptChanges();
tempTargetProperties.Add(sourceProperty.MarkAsAdded());
}
else
{
sourceProperty.AcceptChanges();
tempTargetProperties.Add(sourceProperty.MarkAsModified());
}
}
return tempTargetProperties;
}
Server save incoming changes like this :
_context.ApplyChanges("OSEntities.Objects", entity);
_context.SaveChanges(SaveOptions.DetectChangesBeforeSave);
When the server tries to save the changes occur exception:
AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
But if I change the code of ImportProperties method, the error does not occur and the changes are saved successfully:
static List<Properties> ImportProperties(
IEnumerable<Properties> targetProperties,
IEnumerable<Properties> sourceProperties)
{
Func<Guid, bool> hasElement = targetProperties.ToDictionary(e => e.PropertyId, e => e).ContainsKey;
var tempTargetProperties = new List<Properties>();
foreach (var sourceProperty in sourceProperties)
{
if (!hasElement(sourceProperty.PropertyId))
{
var newProp = new Properties
{
ElementId = sourceProperty.ElementId,
Name = sourceProperty.Name,
ObjectId = sourceProperty.ObjectId,
PropertyId = sourceProperty.PropertyId,
Value = sourceProperty.Value
};
tempTargetProperties.Add(newProp);
}
else
{
var modifiedProp = new Properties
{
ElementId = sourceProperty.ElementId,
Name = sourceProperty.Name,
ObjectId = sourceProperty.ObjectId,
PropertyId = sourceProperty.PropertyId,
Value = sourceProperty.Value
};
modifiedProp.MarkAsModified();
tempTargetProperties.Add(modifiedProp);
}
}
return tempTargetProperties;
}
Why is there an exception?
When you transport an object graph (Entity with n-level deep navigation properties) to a client application the entities will record any changes made in their respective change trackers. When entity (or object graph) is sent back to the server side of the application basically all you need to do is:
try
{
using(Entities context = new Entities())
{
context.ApplyChanges(someEntity);
context.SaveChanges();
}
}
catch
{
...
}
I don't see the need of all the code above you posted. What are you trying to achieve with that code?

RIA services + entity framework server side Insert Update Deletes - logging

First off, thanks for any help. I am using ria services to insert/update/delete entities and save a history of those operations. I want to perform the operation and save the history in ONE call to the service. Right now I am stuck on the insert because I need the new entities ID that is generated on the insert. I might be taking the wrong approach all together (but I hope not). I have overriden the submit method, and am trying to save a snapshot in the history table, I don't want to save a snapshot of the original version:
public override bool Submit( ChangeSet changeSet )
{
//SUBMIT FIRST SO THE OBJECT(S) HAVE AN ID
var success = base.Submit( changeSet );
if ( success )
foreach ( var changeSetEntry in changeSet.ChangeSetEntries )
{
if ( changeSetEntry.Entity is MyBusinessEntity )
{
var newBusinessEntity = (MyBusinessEntity) changeSetEntry.Entity;
RecordModifiedMyBusinessEntity( changeSetEntry.Operation, newBusinessEntity );
}
}
return success;
}
private void RecordModifiedMyBusinessEntity( DomainOperation operation, MyBusinessEntity newBusinessEntity )
{
var hist = new BusinessEntityHistory
{
ChangedBy = new AuthenticationService().GetUser().FriendlyName,
ChangedDate = DateTime.Now,
Operation = operation.ToString(),
BusinessEntityId = newBusinessEntity.Id,
Group = newBusinessEntity.Group,
Priority = newBusinessEntity.Priority,
....
};
InsertBusinessEntityHistory( hist );
//HERE IS WHERE I WANT TO CALL SUBMIT CHANGES AGAIN, BUT 1 - IT'S NOT IN THE CHANGESET,
//AND 2 - THE OBJECT I ALREADY INSERTED IS IN THE CHANGESET (SO IF I SUBMIT AGAIN, IT GETS
//INSERTED TWICE AND NO HISTORY IS SAVED. AND 3 - I CAN'T DO THE HISTORY BEFORE BECAUSE I DON'T
//HAVE THE ID, AND I DON'T WANT TO DO A MAX ID + 1 BECAUSE SOMEONE ELSE MIGHT BE
//INSERTING INTO THE SAME TABLE
}
Here is the solution I ended up going with:
public override bool Submit( ChangeSet changeSet )
{
//submit the changes
var success = base.Submit( changeSet );
if ( success )
{
//make a new list of change set entries
var entries = new List<ChangeSetEntry>();
//each change set entry needs an id (not to be confused with the entity's id)
var maxId = 0;
//iterate through each change and add historical snapshot.
foreach ( var changeSetEntry in changeSet.ChangeSetEntries )
{
var entity = changeSetEntry.Entity;
var operation = changeSetEntry.Operation;
var myEntity = entity as MyEntityType;
if ( myEntity != null )
{
entries.Add( GetHistoryChangeSetEntry( ref maxId, operation, myEntity ) );
continue;
}
}
//make new change set with historical snapshots
var newChangeSet = new ChangeSet( entries );
//submit the new change set
base.Submit( newChangeSet );
}
return success;
}
private ChangeSetEntry GetHistoryChangeSetEntry( ref int maxId, DomainOperation operation, MyEntityType myEntity )
{
return new ChangeSetEntry
{
Id = ++maxId,
//We are inserting this change set entry
Operation = DomainOperation.Insert,
Entity = new MyEntityTypesHistory
{
ChangedBy = ServiceContext.User.Identity.Name,
ChangedDate = DateTime.Now,
//The operation performed on the original entity
Operation = operation.ToString(),
MyEntityId = myEntity.EntityId,
MyEntityField1 = myEntity.EntityField1,
MyEntityField2 = myEntity.EntityField2
}
};
}
I had to make a new change set, and new change set entries for it, and submit changes with the new change set.