샘플: 엔터티 데이터 변경 감사
게시 날짜: 2017년 1월
적용 대상: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online
이 샘플 코드는 Microsoft Dynamics 365(온라인 및 온-프레미스)용입니다.Microsoft Dynamics CRM SDK 패키지를 다운로드합니다. 샘플은 다운로드 패키지의 다음 위치에서 확인할 수 있습니다.
SampleCode\CS\DataManagement\Auditing\Program.cs
요구 사항
이 SDK에서 제공된 샘플 코드를 실행하기 위한 요구 사항에 대한 자세한 내용은 샘플 및 도우미 코드 사용을 참조하십시오. 이 샘플을 사용하려면 로그온한 사용자에게 시스템 관리자 역할이 있어야 합니다.
보여 주기
엔터티 및 해당 특성에 대해 감사를 활성화 및 비활성화하는 방법을 보여 주고 감사된 엔터티의 데이터 변경 기록을 검색하고 감사 레코드를 삭제합니다.
예제
using System;
using System.ServiceModel;
using System.Collections.Generic;
using System.Linq;
// These namespaces are found in the Microsoft.Xrm.Sdk.dll assembly
// located in the SDK\bin folder of the SDK download.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
// These namespaces are found in the Microsoft.Crm.Sdk.Proxy.dll assembly
// located in the SDK\bin folder of the SDK download.
using Microsoft.Crm.Sdk.Messages;
namespace Microsoft.Crm.Sdk.Samples
{
public class Auditing
{
#region Class Level Members
private OrganizationServiceProxy _serviceProxy;
private IOrganizationService _service;
private Guid _newAccountId;
#endregion Class Level Members
#region How To Sample Code
/// <summary>
/// This method first connects to the organization service. Next, auditing
/// is enabled on the organization and an account entity. The account record
/// is updated and the audit history printed out.
/// </summary>
/// <param name="serverConfig">Contains server connection information.</param>
/// <param name="promptforDelete">When True, the user will be prompted to delete all
/// created entities.</param>
public void Run(ServerConnection.Configuration serverConfig, bool promptforDelete)
{
using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri, serverConfig.Credentials, serverConfig.DeviceCredentials))
{
// Enable early-bound type support.
_serviceProxy.EnableProxyTypes();
// You can access the service through the proxy, but this sample uses the interface instead.
_service = _serviceProxy;
#region Enable Auditing for an Account
Console.WriteLine("Enabling auditing on the organization and account entities.");
// Enable auditing on the organization.
// First, get the organization's ID from the system user record.
Guid orgId = ((WhoAmIResponse)_service.Execute(new WhoAmIRequest())).OrganizationId;
// Next, retrieve the organization's record.
Organization org = _service.Retrieve(Organization.EntityLogicalName, orgId,
new ColumnSet(new string[] { "organizationid", "isauditenabled" })) as Organization;
// Finally, enable auditing on the organization.
bool organizationAuditingFlag = org.IsAuditEnabled.Value;
org.IsAuditEnabled = true;
_service.Update(org);
// Enable auditing on account entities.
bool accountAuditingFlag = EnableEntityAuditing(Account.EntityLogicalName, true);
#endregion Enable Auditing for an Account
CreateRequiredRecords();
#region Retrieve the Record Change History
Console.WriteLine("Retrieving the account change history.\n");
// Retrieve the audit history for the account and display it.
RetrieveRecordChangeHistoryRequest changeRequest = new RetrieveRecordChangeHistoryRequest();
changeRequest.Target = new EntityReference(Account.EntityLogicalName, _newAccountId);
RetrieveRecordChangeHistoryResponse changeResponse =
(RetrieveRecordChangeHistoryResponse)_service.Execute(changeRequest);
AuditDetailCollection details = changeResponse.AuditDetailCollection;
foreach (AttributeAuditDetail detail in details.AuditDetails)
{
// Display some of the detail information in each audit record.
DisplayAuditDetails(detail);
}
#endregion Retrieve the Record Change History
#region Retrieve the Attribute Change History
// Update the Telephone1 attribute in the Account entity record.
Account accountToUpdate = new Account();
accountToUpdate.AccountId = _newAccountId;
accountToUpdate.Telephone1 = "123-555-5555";
_serviceProxy.Update(accountToUpdate);
Console.WriteLine("Updated the Telephone1 field in the Account entity.");
// Retrieve the attribute change history.
Console.WriteLine("Retrieving the attribute change history for Telephone1.");
var attributeChangeHistoryRequest = new RetrieveAttributeChangeHistoryRequest
{
Target = new EntityReference(
Account.EntityLogicalName, _newAccountId),
AttributeLogicalName = "telephone1"
};
var attributeChangeHistoryResponse =
(RetrieveAttributeChangeHistoryResponse)_service.Execute(attributeChangeHistoryRequest);
// Display the attribute change history.
details = attributeChangeHistoryResponse.AuditDetailCollection;
foreach (var detail in details.AuditDetails)
{
DisplayAuditDetails(detail);
}
// Save an Audit record ID for later use.
Guid auditSampleId = details.AuditDetails.First().AuditRecord.Id;
#endregion Retrieve the Attribute Change History
#region Retrieve the Audit Details
Console.WriteLine("Retrieving audit details for an audit record.");
// Retrieve the audit details and display them.
var auditDetailsRequest = new RetrieveAuditDetailsRequest
{
AuditId = auditSampleId
};
var auditDetailsResponse =
(RetrieveAuditDetailsResponse)_service.Execute(auditDetailsRequest);
DisplayAuditDetails(auditDetailsResponse.AuditDetail);
#endregion Retrieve the Audit Details
#region Revert Auditing
// Set the organization and account auditing flags back to the old values
org.IsAuditEnabled = organizationAuditingFlag;
_service.Update(org);
EnableEntityAuditing(Account.EntityLogicalName, accountAuditingFlag);
#endregion Revert Auditing
DeleteRequiredRecords(promptforDelete);
}
}
#endregion How To Sample Code
#region Class methods
/// <summary>
/// Displays audit change history details on the console.
/// </summary>
/// <param name="detail"></param>
private static void DisplayAuditDetails(AuditDetail detail)
{
// Write out some of the change history information in the audit record.
Audit record = (Audit)detail.AuditRecord;
Console.WriteLine("\nAudit record created on: {0}", record.CreatedOn.Value.ToLocalTime());
Console.WriteLine("Entity: {0}, Action: {1}, Operation: {2}",
record.ObjectId.LogicalName, record.FormattedValues["action"],
record.FormattedValues["operation"]);
// Show additional details for certain AuditDetail sub-types.
var detailType = detail.GetType();
if (detailType == typeof(AttributeAuditDetail))
{
var attributeDetail = (AttributeAuditDetail)detail;
// Display the old and new attribute values.
foreach (KeyValuePair<String, object> attribute in attributeDetail.NewValue.Attributes)
{
String oldValue = "(no value)", newValue = "(no value)";
//TODO Display the lookup values of those attributes that do not contain strings.
if (attributeDetail.OldValue.Contains(attribute.Key))
oldValue = attributeDetail.OldValue[attribute.Key].ToString();
newValue = attributeDetail.NewValue[attribute.Key].ToString();
Console.WriteLine("Attribute: {0}, old value: {1}, new value: {2}",
attribute.Key, oldValue, newValue);
}
foreach (KeyValuePair<String, object> attribute in attributeDetail.OldValue.Attributes)
{
if (!attributeDetail.NewValue.Contains(attribute.Key))
{
String newValue = "(no value)";
//TODO Display the lookup values of those attributes that do not contain strings.
String oldValue = attributeDetail.OldValue[attribute.Key].ToString();
Console.WriteLine("Attribute: {0}, old value: {1}, new value: {2}",
attribute.Key, oldValue, newValue);
}
}
}
Console.WriteLine();
}
/// <summary>
/// Enable auditing on an entity.
/// </summary>
/// <param name="entityLogicalName">The logical name of the entity.</param>
/// <param name="flag">True to enable auditing, otherwise false.</param>
/// <returns>The previous value of the IsAuditEnabled attribute.</returns>
private bool EnableEntityAuditing(string entityLogicalName, bool flag)
{
// Retrieve the entity metadata.
RetrieveEntityRequest entityRequest = new RetrieveEntityRequest
{
LogicalName = entityLogicalName,
EntityFilters = EntityFilters.Attributes
};
RetrieveEntityResponse entityResponse =
(RetrieveEntityResponse)_service.Execute(entityRequest);
// Enable auditing on the entity. By default, this also enables auditing
// on all the entity's attributes.
EntityMetadata entityMetadata = entityResponse.EntityMetadata;
bool oldValue = entityMetadata.IsAuditEnabled.Value;
entityMetadata.IsAuditEnabled = new BooleanManagedProperty(flag);
UpdateEntityRequest updateEntityRequest = new UpdateEntityRequest { Entity = entityMetadata };
UpdateEntityResponse updateEntityResponse =
(UpdateEntityResponse)_service.Execute(updateEntityRequest);
return oldValue;
}
/// <summary>
/// Creates any entity records that this sample requires.
/// </summary>
public void CreateRequiredRecords()
{
Console.Write("Creating an account, ");
// Account entity category codes.
var Categories = new { PreferredCustomer = 1, Standard = 2 };
// Create a new account entity.
Account newAccount = new Account { Name = "Example Account" };
_newAccountId = _service.Create(newAccount);
Console.WriteLine("then updating the account.");
// Set the values of some other attributes.
newAccount.AccountId = _newAccountId;
newAccount.AccountNumber = "1-A";
newAccount.AccountCategoryCode = new OptionSetValue(Categories.PreferredCustomer);
newAccount.Telephone1 = "555-555-5555";
_service.Update(newAccount);
}
/// <summary>
/// Deletes any entity records that were created for this sample.
/// <param name="prompt">Indicates whether to prompt the user
/// to delete the records created in this sample.</param>
/// </summary>
public void DeleteRequiredRecords(bool prompt)
{
bool deleteRecords = true;
if (prompt)
{
Console.WriteLine("\nDo you want to delete the account record? (y/n) [y]: ");
String answer = Console.ReadLine();
deleteRecords = (answer.StartsWith("y") || answer.StartsWith("Y") || answer == String.Empty);
}
if (deleteRecords)
{
_service.Delete(Account.EntityLogicalName, _newAccountId);
Console.WriteLine("The account record has been deleted.");
}
if (prompt)
{
Console.WriteLine("\nDo you want to delete ALL audit records? (y/n) [n]: ");
String answer = Console.ReadLine();
deleteRecords = (answer.StartsWith("y") || answer.StartsWith("Y"));
}
if (deleteRecords)
{
// Get the list of audit partitions.
RetrieveAuditPartitionListResponse partitionRequest =
(RetrieveAuditPartitionListResponse)_service.Execute(new RetrieveAuditPartitionListRequest());
AuditPartitionDetailCollection partitions = partitionRequest.AuditPartitionDetailCollection;
// Create a delete request with an end date earlier than possible.
DeleteAuditDataRequest deleteRequest = new DeleteAuditDataRequest();
deleteRequest.EndDate = new DateTime(2000, 1, 1);
// Check if partitions are not supported as is the case with SQL Server Standard edition.
if (partitions.IsLogicalCollection)
{
// Delete all audit records created up until now.
deleteRequest.EndDate = DateTime.Now;
}
// Otherwise, delete all partitions that are older than the current partition.
// Hint: The partitions in the collection are returned in sorted order where the
// partition with the oldest end date is at index 0.
else
{
for (int n = partitions.Count - 1; n >= 0; --n)
{
if (partitions[n].EndDate <= DateTime.Now && partitions[n].EndDate > deleteRequest.EndDate)
{
deleteRequest.EndDate = (DateTime)partitions[n].EndDate;
break;
}
}
}
// Delete the audit records.
if (deleteRequest.EndDate != new DateTime(2000, 1, 1))
{
_service.Execute(deleteRequest);
Console.WriteLine("Audit records have been deleted.");
}
else
Console.WriteLine("There were no audit records that could be deleted.");
}
}
#endregion Class methods
#region Main method
/// <summary>
/// Standard Main() method used by most SDK samples.
/// </summary>
/// <param name="args"></param>
static public void Main(string[] args)
{
try
{
// Obtain the target organization's Web address and client logon
// credentials from the user.
ServerConnection serverConnect = new ServerConnection();
ServerConnection.Configuration config = serverConnect.GetServerConfiguration();
Auditing app = new Auditing();
app.Run(config, true);
}
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> ex)
{
Console.WriteLine("The application terminated with an error.");
Console.WriteLine("Timestamp: {0}", ex.Detail.Timestamp);
Console.WriteLine("Code: {0}", ex.Detail.ErrorCode);
Console.WriteLine("Message: {0}", ex.Detail.Message);
Console.WriteLine("Trace: {0}", ex.Detail.TraceText);
Console.WriteLine("Inner Fault: {0}",
null == ex.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
}
catch (System.TimeoutException ex)
{
Console.WriteLine("The application terminated with an error.");
Console.WriteLine("Message: {0}", ex.Message);
Console.WriteLine("Stack Trace: {0}", ex.StackTrace);
Console.WriteLine("Inner Fault: {0}",
null == ex.InnerException.Message ? "No Inner Fault" : ex.InnerException.Message);
}
catch (System.Exception ex)
{
Console.WriteLine("The application terminated with an error.");
Console.WriteLine(ex.Message);
// Display the details of the inner exception.
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message);
FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> fe = ex.InnerException
as FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>;
if (fe != null)
{
Console.WriteLine("Timestamp: {0}", fe.Detail.Timestamp);
Console.WriteLine("Code: {0}", fe.Detail.ErrorCode);
Console.WriteLine("Message: {0}", fe.Detail.Message);
Console.WriteLine("Trace: {0}", fe.Detail.TraceText);
Console.WriteLine("Inner Fault: {0}",
null == fe.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
}
}
}
// Additional exceptions to catch: SecurityTokenValidationException, ExpiredSecurityTokenException,
// SecurityAccessDeniedException, MessageSecurityException, and SecurityNegotiationException.
finally
{
Console.WriteLine("Press <Enter> to exit.");
Console.ReadLine();
}
}
#endregion Main method
}
}
참고 항목
IsAuditEnabled
IsAuditEnabled
RetrieveRecordChangeHistoryRequest
AttributeAuditDetail
RetrieveAuditPartitionListRequest
DeleteAuditDataRequest
엔터티 데이터 변경 감사
샘플: 사용자 액세스 감사
Audit 엔터티 메시지 및 메서드
Microsoft Dynamics 365
© 2017 Microsoft. All rights reserved. 저작권 정보