1

有沒有人找到一個好方法來獲取自動增量主鍵在測試服務層時模擬上下文中工作?c#嘲笑EF6上下文並處理自動增量主鍵

在大多數情況下,將主鍵種子作爲要測試的數據的一部分是可能的。但是,許多服務層方法處理創建多個對象或將其他進程鏈接在一起,如果您不負責傳遞所有創建的數據,則這些方法會很快失敗。我想添加一個Callback()到SaveChangesAsync()來查看數據創建並自動生成主鍵,但實現起來並不簡單。

var organization = new PrivateOrganization(); 
organization.Name = "New Test Organization"; 
organization.Description = "New Test Organization description"; 
organization.OrganizationTypeId = ITNOrganizationTypes.Agency; 
organization.OrganizationStatusTypeId = (int)ITNOrganizationStatusTypes.Enabled; 
organization.ShortCode = "Test"; 

var newOrg = await _service.InsertPrivateOrganizationAsync(organization); 

_mockPrivateOrganizationsSet.Verify(m => m.Add(It.IsAny<PrivateOrganization>()), Times.Once()); 
MockTenantContext.Verify(m => m.SaveChangesAsync(), Times.Once()); 

// validation passes, but contains no auto-generated primary key. 
+0

你有沒有找到任何答案嗎? – garfbradaz

+0

我爲此寫了一個解決方案,我很快就會發布。 –

回答

1

我必須開發自己的解決方案的具體步驟如下:

/// <summary> 
/// A helper class for managing custom behaviors of Mockable database contexts 
/// </summary> 
public static partial class EFSaveChangesBehaviors 
{ 
    /// <summary> 
    /// Enable auto-incrementing of primary key values upon SaveChanges/SaveChangesAsync 
    /// </summary> 
    /// <typeparam name="T">The type of context to enable auto-incrementing on</typeparam> 
    /// <param name="context">The context to enable this feature</param> 
    public static void EnableAutoIncrementOnSave<T>(this Mock<T> context) where T : DbContext 
    { 
     context.Setup(m => m.SaveChangesAsync()) 
      .Callback(() => 
      { 
       EFSaveChangesBehaviors.SaveChangesIncrementKey(context.Object); 
      }) 
      .Returns(() => Task.Run(() => { return 1; })) 
      .Verifiable(); 

     context.Setup(m => m.SaveChanges()) 
      .Callback(() => 
      { 
       EFSaveChangesBehaviors.SaveChangesIncrementKey(context.Object); 
      }) 
      .Returns(() => { return 1; }) 
      .Verifiable(); 
    } 

    /// <summary> 
    /// Implements key incrementing of data records that are pending to be added to the context 
    /// </summary> 
    /// <param name="context"></param> 
    public static void SaveChangesIncrementKey(DbContext context) 
    { 
     var tablesWithNewData = GetUnsavedRows<DbContext>(context); 
     for (int i = 0; i < tablesWithNewData.Count; i++) 
     { 
      long nextPrimaryKeyValue = 0; 
      var tableWithDataProperty = tablesWithNewData[i]; 
      var tableWithDataObject = tableWithDataProperty.GetValue(context); 
      if (tableWithDataObject != null) 
      { 
       var tableWithDataQueryable = tableWithDataObject as IQueryable<object>; 

       // 1) get the highest value in the DbSet<> (table) to continue auto-increment from 
       nextPrimaryKeyValue = IterateAndPerformAction(context, tableWithDataQueryable, tableWithDataProperty, nextPrimaryKeyValue, (primaryExistingKeyValue, primaryKeyRowObject, primaryKeyProperty) => 
       { 
        if (primaryExistingKeyValue > nextPrimaryKeyValue) 
         nextPrimaryKeyValue = Convert.ToInt64(primaryExistingKeyValue); 
        return nextPrimaryKeyValue; 
       }); 

       // 2) increase the value of the record's primary key on each iteration 
       IterateAndPerformAction(context, tableWithDataQueryable, tableWithDataProperty, nextPrimaryKeyValue, (primaryKeyExistingValue, primaryKeyRowObject, primaryKeyProperty) => 
       { 
        if (primaryKeyExistingValue == 0) 
        { 
         nextPrimaryKeyValue++; 
         Type propertyType = primaryKeyProperty.PropertyType; 
         if (propertyType == typeof(Int64)) 
          primaryKeyProperty.SetValue(primaryKeyRowObject, nextPrimaryKeyValue); 
         else if (propertyType == typeof(Int32)) 
          primaryKeyProperty.SetValue(primaryKeyRowObject, Convert.ToInt32(nextPrimaryKeyValue)); 
         else if (propertyType == typeof(Int16)) 
          primaryKeyProperty.SetValue(primaryKeyRowObject, Convert.ToInt16(nextPrimaryKeyValue)); 
         else if (propertyType == typeof(byte)) 
          primaryKeyProperty.SetValue(primaryKeyRowObject, Convert.ToByte(nextPrimaryKeyValue)); 
         else 
          throw new System.NotImplementedException($"Cannot manage primary keys of type: {propertyType.FullName}"); 
        } 
        return nextPrimaryKeyValue; 
       }); 
      } 
     } 
    } 

    /// <summary> 
    /// Get a list of properties for a data table that are indicated as a primary key 
    /// </summary> 
    /// <param name="t"></param> 
    /// <param name="context"></param> 
    /// <returns></returns> 
    /// <remarks>Reflection must be used, as the ObjectContext is not mockable</remarks> 
    public static PropertyInfo[] GetPrimaryKeyNamesUsingReflection(Type t, DbContext context) 
    { 
     var properties = t.GetProperties(); 
     var keyNames = properties 
      .Where(prop => Attribute.IsDefined(prop, typeof(System.ComponentModel.DataAnnotations.KeyAttribute))) 
      .ToArray(); 

     return keyNames; 
    } 

    /// <summary> 
    /// Iterates a table's data and allows an action to be performed on each row 
    /// </summary> 
    /// <param name="context">The database context</param> 
    /// <param name="tableWithDataQueryable"></param> 
    /// <param name="tableWithDataProperty"></param> 
    /// <param name="nextPrimaryKeyValue"></param> 
    /// <param name="action"></param> 
    /// <returns></returns> 
    private static long IterateAndPerformAction(DbContext context, IQueryable<object> tableWithDataQueryable, PropertyInfo tableWithDataProperty, long nextPrimaryKeyValue, Func<long, object, PropertyInfo, long> action) 
    { 
     foreach (var primaryKeyRowObject in tableWithDataQueryable) 
     { 
      // create a primary key for the object 
      if (tableWithDataProperty.PropertyType.GenericTypeArguments.Length > 0) 
      { 
       var dbSetType = tableWithDataProperty.PropertyType.GenericTypeArguments[0]; 
       // find the primary key property 
       var primaryKeyProperty = GetPrimaryKeyNamesUsingReflection(dbSetType, context).FirstOrDefault(); 
       if (primaryKeyProperty != null) 
       { 
        var primaryKeyValue = primaryKeyProperty.GetValue(primaryKeyRowObject) ?? 0L; 
        nextPrimaryKeyValue = action(Convert.ToInt64(primaryKeyValue), primaryKeyRowObject, primaryKeyProperty); 
       } 
      } 
     } 
     return nextPrimaryKeyValue; 
    } 

    /// <summary> 
    /// Get a list of objects which are pending to be added to the context 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="context"></param> 
    /// <returns></returns> 
    private static IList<PropertyInfo> GetUnsavedRows<T>(T context) 
    { 
     // get list of properties of type DbSet<> 
     var dbSetProperties = new List<PropertyInfo>(); 
     var properties = context.GetType().GetProperties(); 
     foreach (var property in properties) 
     { 
      var setType = property.PropertyType; 
      var isDbSet = setType.IsGenericType && (typeof(IDbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()) || setType.GetInterface(typeof(IDbSet<>).FullName) != null); 
      if (isDbSet) 
      { 
       dbSetProperties.Add(property); 
      } 
     } 

     return dbSetProperties; 
    } 


} 

用法:

// enable auto-increment in our in-memory database 
MockTenantContext.EnableAutoIncrementOnSave();