Добрый день.
У меня имеется некая система позволяющая автоматически сохранять изменения свойств сущностей. Все на тестах простых работало. Потом когда написали много полезного кода и отправли проект на тестирование начали возникать ошибки ствязанные с тем, что на сущностях (в остновном где есть нуловые вторичные ключи) не сохраняются значения свойств с типом int?. Ошибка возникает на столько редко, что единичные тесты ничего не показывают (где-то раз на 3000 итераций), и это очень печально потому что простым дебагом нельзя добиться 100% воспроизведения ошибки. Может кто-то сталкивался с подобными проблемами, надеюсь знающие люди помогут.
Полного кода всех модулей позволяющего воспроизвести ошибку не могу выложить в топик, но прилагаю код тех методов где я использую BLToolkit и где возникает ошибка.
Часть кода которая производит апдейт сощностей, здесь важным является формирование sql запроса (метод GetUpdQuery(_entity,out strlog)
var dbm = _dbManager.DbManager;
var query = dbm.GetTable<T>().Where(e => e.Key == _entity.Key).GetUpdQuery(_entity,out strlog);
if (query != null)
{
query.Update();
Log.InfoFormat("Entity was updated in db ({0}[{1}])", _entity.TypeKey, _entity.Key);
if (_entity.TypeKey == TypeKeys.ArmorPart)
{
Log.Info("DBParticle.ArmorUpdated - " + strlog);
Log.Fatal("LastUqery:{0}", dbm.LastQuery);
}
}
Сам метод формирующий запрос
public static IUpdateable<T> GetUpdQuery<T>(this IQueryable<T> query, T entity, out string strLog)where T : class, IGPropertyContainer
{
strLog = string.Format("GetUpdQuery({0}[{1}]) fields: ",entity.TypeKey,entity.Key);
IUpdateable<T> updQuery = null;// new Extensions.Updateable<T>() { Query = query };
for (int i = 0; i < entity.Properties.Count; i++)
{
IGProperty prop = entity.Properties[i];
if (!((IEditable)prop).IsDirty) continue;
var t = TypeAccessor.GetAccessor(typeof(T));
var ma = ExprMemberAccessor.GetMemberAccessor(t, prop.Name);
var metaMember = ExtMetadataHelper.GetMetaMemberAccessor(ma);
var attr1 = metaMember.GetAttribute<SqlIgnoreAttribute>();
if (attr1 != null) continue;
var attr2 = metaMember.GetAttribute<NonUpdatableAttribute>();
if (attr2 != null) continue;
strLog += string.Format("{0}={1}, ", prop.Name,prop.Value);
if (updQuery == null)
updQuery = query.AddSet(prop);
else
updQuery = updQuery.AddSet(prop);
}
return updQuery;
}
if (updQuery == null)
updQuery = query.AddSet(prop);
else
updQuery = updQuery.AddSet(prop);
- такой код связан сособенностями реализации в BLToolkit для разных интефейсов для IQueryable<T> и IUpdateable<T>
Собственно сами методы которые добавляют выражение Set в запрос для разных сорсов IQueryable<T> и IUpdateable<T>
public static IUpdateable<T> AddSet<T>(this IUpdateable<T> query, IGProperty prop)
where T : class, IGPropertyContainer
{
ParameterExpression entityParam = Expression.Parameter(typeof(T), "entity");
IEnumerable<MethodInfo> methods =
from method in typeof(Extensions).GetMethods(BindingFlags.Public | BindingFlags.Static)
let parameters = method.GetParameters()
let genParams = method.GetGenericArguments()
where method.Name == "Set" &&
method.ContainsGenericParameters &&
parameters.Length == 3 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IUpdateable<>) &&
parameters[2].ParameterType.BaseType == typeof(object)
select method;
MethodInfo miSet = methods.FirstOrDefault().MakeGenericMethod(new[] { typeof(T), prop.ValueType });
MemberExpression propExpr = Expression.PropertyOrField(entityParam, prop.Name);
Expression propertyAccess = Expression.Lambda(propExpr, entityParam);
var result = (IUpdateable<T>)miSet.Invoke(null, new[] { query, propertyAccess, prop.Value });
return result;
}
public static IUpdateable<T> AddSet<T>(this IQueryable<T> query, IGProperty prop)
where T : class, IGPropertyContainer
{
ParameterExpression entityParam = Expression.Parameter(typeof(T), "entity");
IEnumerable<MethodInfo> methods =
from method in typeof(Extensions).GetMethods(BindingFlags.Public | BindingFlags.Static)
let parameters = method.GetParameters()
let genParams = method.GetGenericArguments()
where method.Name == "Set" &&
method.ContainsGenericParameters &&
parameters.Length == 3 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>) &&
parameters[2].ParameterType.BaseType == typeof(object)
select method;
MethodInfo miSet = methods.FirstOrDefault().MakeGenericMethod(new[] { typeof(T), prop.ValueType });
MemberExpression propExpr = Expression.PropertyOrField(entityParam, prop.Name);
Expression propertyAccess = Expression.Lambda(propExpr, entityParam);
var result = (IUpdateable<T>)miSet.Invoke(null, new[] { query, propertyAccess, prop.Value });
return result;
}
эти методы отличаются лиш одной строчкой
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IUpdateable<>) &&
.
Код метода теста который воспроизводит ошибку
public void UpdateOneNullableIntPropertyTest(int index)
{
var dbm = new DBAccess.GenericDBManager<TestEntity>();
var testEntity = dbm.Table.FirstOrDefault(te => te.StringField == "TestString");
var intValue = (new Random()).Next(1000);
//testEntity.IntField = intValue;
if ((intValue % 2) == 1)
testEntity.IntNullField = null;
else
testEntity.IntNullField = testEntity.IntField;
DBParticle<TestEntity> dbParticle = new DBParticle<TestEntity>(testEntity, dbm);
dbParticle.Store();
Console.Out.WriteLine(dbm.DbManager.LastQuery);
Debug.WriteLine(dbm.DbManager.LastQuery);
var existendEntity = Get<TestEntity>(testEntity.Key);
Assert.IsTrue(testEntity.Equals(existendEntity),"Iteration="+intValue.ToString());
}
если раскомментировать строчку
//testEntity.IntField = intValue;
ошибка не воспроизводится.
Еще приложу две вырезки из лога когда одна и таже сущность сохранялась в базу с двумя измененными свойствами и с одним.
К примеру вот часть кода
//armorPart.CurrentDurability--; armorPart.CurrentDurability++;
armorPart.ArmorID = Key;
если раскоментировать первую строчку лог последнего sql запроса выдает результат
UPDATE
[e]
SET
[ArmorID] = @p1,
[CurrentDurability] = 48
FROM
[ConcreteArmorParts] [e]
WHERE
[e].[CArmorPartID] = @Key1
при закоментированной первой строчке sql имеет следующий вид
UPDATE
[e]
SET
[ArmorID] = NULL
FROM
[ConcreteArmorParts] [e]
WHERE
[e].[CArmorPartID] = @Key1
Извините за столь обширное описание проблемы.