2014-06-09 2 views
3

웹 감사 프로젝트에서 내 컨트롤러를 통해 CRUD 작업을 수행하면 이전 및 새로운 poco를 직렬화하고 나중에 검색 할 수 있도록 값을 저장할 수있는 데이터베이스 감사 내역을 구현하는 중이었습니다 (역사, 롤백 등).Entity Framework 코드 첫 번째 및 SQL Server 2012 시퀀스

내가 제대로 작동하게되면 POST 중에 컨트롤러가 보이게 만드는 방법이 마음에 들지 않았습니다. SaveChanges()을 두 번 호출해야했기 때문에 삽입 된 엔티티의 ID를 얻은 다음 다시 감사를 커밋해야합니다. 그 ID를 알 필요가있는 레코드.

프로젝트를 (아직 유아기 인) IDC 대신 시퀀스를 사용하도록 변환하려고했습니다. 이것은 SQL Server에서 나를 추상화하는 보너스가 추가되었지만 실제로는 문제가 아니지만 커밋 수를 줄이고 컨트롤러에서 해당 논리를 끌어 와서 서비스 계층에 채울 수 있습니다. 내 컨트롤러를 리포지토리에서 추상화하고이 "심"레이어에서이 감사와 같은 작업을 수행 할 수있게합니다. 단순히 아이디, 단일 및 범위를 얻을 수있는 두 가지 방법을 제공

public class SequentialIdProvider : ISequentialIdProvider 
{ 
    private readonly IService<SequenceValue> _sequenceValueService; 

    public SequentialIdProvider(IService<SequenceValue> sequenceValueService) 
    { 
     _sequenceValueService = sequenceValueService; 
    } 

    public int GetNextId() 
    { 
     var value = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = 1 }).ToList(); 
     if (value.First() == null) 
     { 
      throw new Exception("Unable to retrieve the next id's from the sequence."); 
     } 

     return value.First().FirstValue; 
    } 

    public IList<int> GetNextIds(int numberOfIds) 
    { 
     var values = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = numberOfIds }).ToList(); 
     if (values.First() == null) 
     { 
      throw new Exception("Unable to retrieve the next id's from the sequence."); 
     } 

     var list = new List<int>(); 
     for (var i = values.First().FirstValue; i <= values.First().LastValue; i++) 
     { 
      list.Add(i); 
     } 

     return list; 
    } 
} 

다음 Sequence 객체가 생성되고 저장 프로 시저를 노출

되면, 나는 다음과 같은 클래스를 만들었습니다.

이 모든 것은 단위 테스트의 첫 번째 세트에서 훌륭하게 작동했지만 실제 시나리오에서 테스트를 시작하자마자 GetNextId()에 대한 단일 호출이 해당 컨텍스트의 수명 동안 동일한 값을 반환한다는 것을 알게되었습니다. SaveChanges()이 호출 될 때까지 실질적인 이익을 무효화합니다.

두 번째 컨텍스트 (옵션 아님)를 만들거나 오래된 학교 ADO.NET을 만들고 직접 SQL 호출을하고 AutoMapper를 사용하여 같은 결과를 얻는 방법이 있는지 확실하지 않습니다. 이들 중 어느 것도 나에게 어필 할 수 없으므로 다른 누군가가 생각하기를 바라고 있습니다.

+0

감사 레코드는 어떻게 보이나요? 모든 테이블에 대해 하나, 모든 테이블에 대해 하나? 어떤 정보가 감사에 저장됩니까? 어떤 작업을 감사합니까? 일반 데이터 레이어가 있습니까? – JotaBe

+0

다음 데이터가 포함 된 단일 감사 테이블 : 이전 또는 새 데이터 (또는 PUT의 경우 모두), 영향을받는 테이블, 해당 테이블의 레코드 ID, 변경된 열 목록 및 수행 된 작업에 대한 일련 화 된 POCO입니다. 감사 생성 (삽입, 삭제, 업데이트). –

+0

그러면 할 방법이 없습니다. 두 SaveChanges 옵션은 유일한 솔루션처럼 보입니다. 두 가지 이유로 두 가지를 염려해서는 안됩니다. 1) .Net은 연결 풀링을 사용합니다. 2) EF는 쿼리를 일괄 적으로 보내지 않고 하나씩 차례로 보냅니다. 따라서 두 SaveChanges 사용 여부와 차이점은 무시할 수 있습니다.유일한 다른 옵션은 PK로 GUID를 사용하는 것입니다 : http://stackoverflow.com/questions/18200817/how-to-set-newid-for-guid-inentity-framework 그러나 이것은 더 많은 단점 (불량 인덱스 성능)을 추가합니다. GUID가 너무 깁니다. – JotaBe

답변

0

이것이 도움이 될지 모르겠으나 코드를 사용하여 감사 로그를 추적하는 방법입니다. 다음은 DbContext를 상속하는 클래스로 코딩됩니다. 나는

IObjectContextAdapter objectContextAdapter = (this as IObjectContextAdapter); 
objectContextAdapter.ObjectContext.SavingChanges += SavingChanges; 

다음 한 내 생성자

이 내 변경을 보존 방법은

void SavingChanges(object sender, EventArgs e) { 
     Debug.Assert(sender != null, "Sender can't be null"); 
     Debug.Assert(sender is ObjectContext, "Sender not instance of ObjectContext"); 

     ObjectContext context = (sender as ObjectContext); 
     IEnumerable<ObjectStateEntry> modifiedEntities = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified); 
     IEnumerable<ObjectStateEntry> addedEntities = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added); 

     addedEntities.ToList().ForEach(a => { 
      //Assign ids to objects that don't have 
      if (a.Entity is IIdentity && (a.Entity as IIdentity).Id == Guid.Empty) 
       (a.Entity as IIdentity).Id = Guid.NewGuid(); 

      this.Set<AuditLogEntry>().Add(AuditLogEntryFactory(a, _AddedEntry)); 
     }); 

     modifiedEntities.ToList().ForEach(m => { 
      this.Set<AuditLogEntry>().Add(AuditLogEntryFactory(m, _ModifiedEntry)); 
     }); 
    } 

그리고 이러한 방법은 감사 로그 정보를 구축와 previosly 사용 이전에 유선입니다

private AuditLogEntry AuditLogEntryFactory(ObjectStateEntry entry, string entryType) { 
     AuditLogEntry auditLogEntry = new AuditLogEntry() { 
      EntryDate = DateTime.Now, 
      EntryType = entryType, 
      Id = Guid.NewGuid(), 
      NewValues = AuditLogEntryNewValues(entry), 
      Table = entry.EntitySet.Name, 
      UserId = _UserId 
     }; 

     if (entryType == _ModifiedEntry) auditLogEntry.OriginalValues = AuditLogEntryOriginalValues(entry); 

     return auditLogEntry; 
    } 

    /// <summary> 
    /// Creates a string of all modified properties for an entity. 
    /// </summary> 
    private string AuditLogEntryOriginalValues(ObjectStateEntry entry) { 
     StringBuilder stringBuilder = new StringBuilder(); 

     entry.GetModifiedProperties().ToList().ForEach(m => { 
      stringBuilder.Append(String.Format("{0} = {1},", m, entry.OriginalValues[m])); 
     }); 

     return stringBuilder.ToString(); 
    } 

    /// <summary> 
    /// Creates a string of all modified properties' new values for an entity. 
    /// </summary> 
    private string AuditLogEntryNewValues(ObjectStateEntry entry) { 
     StringBuilder stringBuilder = new StringBuilder(); 

     for (int i = 0; i < entry.CurrentValues.FieldCount; i++) { 
      stringBuilder.Append(String.Format("{0} = {1},", 
       entry.CurrentValues.GetName(i), entry.CurrentValues.GetValue(i))); 
     } 

     return stringBuilder.ToString(); 
    } 

이 정보는 도움이 될 수있는 방향을 제시합니다. 너의 문제를 풀어 라.

+0

이 접근법에서 내가 뭘 발견했는지 (나는 모두 유선화하고 작업했다) "CurrentValues"와 "OriginalValues "는 분리 된 코드 첫 번째 저장소 패턴 접근 방식에서 동일합니다. 예를 들어 내 RESTful 웹 API에서 업데이트가 들어 오면 ID가 업데이트 됨 (분리 됨)에서 필요한 사항을 변경 한 다음 저장소로 업데이트를 전달합니다. EF가 엔티티를 부착하면 원본 값과 현재 값이 동일합니다. 이것은 프레임 워크의 알려진 제한 사항입니다 (당시에는 해당되지 않았습니다). 여기에 기능 투표가 표시됩니다. http://entityframework.codeplex.com/workitem/864 –

+0

@ JDBuckSavage에 대한 의견을 주셔서 감사합니다. 제 앱이 제대로 작동하는지 확인하기 위해 단위 테스트를 확인해야합니다. – 3dd

관련 문제