Проблема с сохранением одного свойства
От: mad_net  
Дата: 15.05.12 10:05
Оценка:
Добрый день.
У меня имеется некая система позволяющая автоматически сохранять изменения свойств сущностей. Все на тестах простых работало. Потом когда написали много полезного кода и отправли проект на тестирование начали возникать ошибки ствязанные с тем, что на сущностях (в остновном где есть нуловые вторичные ключи) не сохраняются значения свойств с типом 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

Извините за столь обширное описание проблемы.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.