C # Linq-SQL: метод UpdateByID для шаблона репозитория - PullRequest
3 голосов
/ 19 мая 2009

Я реализовал своего рода класс Repository, и у него есть методы GetByID, DeleteByID и т. Д., Но у меня возникают проблемы с реализацией метода UpdateByID.

Я сделал что-то вроде этого:

public virtual void UpdateByID(int id, T entity)
{
        var dbcontext = DB;
        var item = GetByID(dbcontext, id);
        item = entity; 
        dbcontext.SubmitChanges();
}

protected MusicRepo_DBDataContext DB
{
    get
    {
        return new MusicRepo_DBDataContext();
    }
}

Но это не обновляет переданную сущность.

Кто-нибудь реализовывал такой метод?


Для справки здесь - это метод GetByID


[Update]

Как правильно предположил Марк, я просто изменяю значения локальной переменной. Так как вы думаете, я должен идти об этом методе? Используйте отражение и скопируйте свойства из entity в item?

Ответы [ 7 ]

8 голосов
/ 19 мая 2009

Все, что вы обновили, является локальной переменной; чтобы это работало, вам нужно скопировать значения элементов из entity в item - не все так просто.


что-то вроде ниже; единственная причина, по которой я использовал TKey, заключалась в том, что я тестировал на Northwind.Customer, который имеет строковый ключ; -p

Преимущество использования метамодели в том, что она работает, даже если вы используете классы POCO (и отображение на основе xml), и не пытается обновить что-либо, не связанное с моделью.

Для целей примера я передал в контекст данных, и вам нужно добавить SubmitChanges в какой-то момент, но остальные должны быть напрямую сопоставимы.

Кстати, - если вы готовы взять идентификатор из переданного объекта, это тоже будет легко - и тогда вы сможете поддерживать составные таблицы идентификаторов.

    static void Update<TEntity>(DataContext dataContext, int id, TEntity obj)
        where TEntity : class
    {
        Update<TEntity, int>(dataContext, id, obj);
    }
    static void Update<TEntity, TKey>(DataContext dataContext, TKey id, TEntity obj)
        where TEntity : class
    {
        // get the row from the database using the meta-model
        MetaType meta = dataContext.Mapping.GetTable(typeof(TEntity)).RowType;
        if(meta.IdentityMembers.Count != 1) throw new InvalidOperationException("Composite identity not supported");
        string idName = meta.IdentityMembers[0].Member.Name;

        var param = Expression.Parameter(typeof(TEntity), "row");
        var lambda = Expression.Lambda<Func<TEntity,bool>>(
            Expression.Equal(
                Expression.PropertyOrField(param, idName),
                Expression.Constant(id, typeof(TKey))), param);

        object dbRow = dataContext.GetTable<TEntity>().Single(lambda);

        foreach (MetaDataMember member in meta.DataMembers)
        {
            // don't copy ID
            if (member.IsPrimaryKey) continue; // removed: || member.IsVersion
            // (perhaps exclude associations and timestamp/rowversion? too)

            // if you get problems, try using StorageAccessor instead -
            // this will typically skip validation, etc
            member.MemberAccessor.SetBoxedValue(
                ref dbRow, member.MemberAccessor.GetBoxedValue(obj));
        }
        // submit changes here?
    }
3 голосов
/ 08 июня 2009

Взглянув по-новому здесь, предыдущие ответы на вопрос сделали различные предположения о заявке.

Параллельность в приложении - это то, о чем нужно подумать заранее, и для этого нет единого универсального ответа. Что нужно учитывать при выборе вашего приложения:

  • LINQ to SQL / Entity Framework очень настраиваемы, поскольку не существует универсального подхода.
  • Вы не увидите эффекта параллелизма до тех пор, пока не получите определенную нагрузку на свое приложение (т. Е. Вы, один, на своей собственной машине, можете никогда не увидеть его)
  • Как часто ваше приложение позволяет 2 (или более) пользователям редактировать одну и ту же сущность?
  • Как вы хотите справиться с ситуацией, когда два редактирования перекрываются?
  • Выполняет ли ваше приложение сериализацию данных взад-вперед по другому слою (например, Ajax)? Если так, то как узнать, была ли измененная сущность изменена между чтением / обновлением? Отметка? Поле версии?
  • Вас волнует, если правки перекрываются? Обратите особое внимание на отношения ФК. Целостность данных - это то место, где вас могут укусить последние победы.

Различные решения имеют очень разные значения производительности! Вы не заметите, как вы разрабатываете, но ваше приложение может упасть, когда его используют одновременно 25 человек. Следите за большим количеством копий туда-сюда и за многими прочтениями SQL:

  • Не вызывать SQL в цикле (следите за этим при передаче списка объектов)
  • Не используйте отражение для этого, если у вас уже есть проверка параллелизма через LINQ
  • Сведите к минимуму копирование полей вперед и назад (может потребоваться при пересечении границ N-уровня).
  • Не делайте отдельный запрос для поиска старой сущности (используйте его только в том случае, если он уже есть), пусть LINQ сделает это, поскольку он более оптимизирован для выполнения в SQL.

Вот несколько хороших ссылок для более глубокого чтения, чтобы решить ваши конкретные потребности:

Мое рекомендуемое решение:

public virtual void Update(T entity)
{
    var DB = ...;
    DB.GetTable<T>().Attach(entity, true);
    try
    {
        // commit to database
        DB.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        Console.WriteLine(e.Message);
        foreach (ObjectChangeConflict occ in DB.ChangeConflicts)
        {
            occ.Resolve(REFRESH_MODE);
        }
    }
}

Где REFRESH_MODE указывает одно из следующих значений:

  • RefreshMode.KeepChanges
  • RefreshMode.KeepCurrentValues
  • RefreshMode.OverwriteCurrentValues

Вам также нужно будет подумать о вашей модели:

Возможно, само собой разумеется, но вам нужно сообщить LINQ, какое поле является вашим первичным ключом для обновления сущностей. Вам не нужно передавать это как другой параметр (как в исходном методе), потому что LINQ уже знает, что это PK.

Вы можете (, а не "обязан" ) решить, какие поля на самом деле проверяются. Например, поле внешнего ключа очень важно иметь проверку на параллелизм, тогда как поле описания, вероятно, заслуживает последнего-одного-выигрыша. Вы управляете этим через атрибут UpdateCheck. По умолчанию UpdateCheck.Always. Из MSDN:

Только члены отображаются как Always или WhenChanged участвуйте в оптимистичных проверки параллелизма. Нет проверки выполняется для участников, помеченных Never. Для получения дополнительной информации см. UpdateCheck.

Чтобы включить оптимистический параллелизм, вам нужно указать поле, которое будет использоваться в качестве токена параллелизма (например, отметка времени или версия), и это поле всегда должно присутствовать при сериализации вперед и назад. Пометить этот столбец IsVersion=true.

Если вы не хотите проверять параллелизм, вы должны пометить все как UpdateCheck.Never.

1 голос
/ 12 июня 2009

У меня были некоторые похожие проблемы, и в итоге я выбрал PLINQO , множество улучшений к сгенерированному коду LINQ-TO-SQL. Однако для этого требуется покупка CodeSmith (хотя она бесплатна в течение 30 дней), если у вас ее еще нет.

0 голосов
/ 10 июня 2009

Я не очень знаком с шаблонами репозитория, но что, если вы удалите старый объект из базы данных и поместите новый объект в базу данных с тем же идентификатором? как то так:

public virtual void UpdateByID(int id, T entity)
{
    DeleteByID(id);
    var dbcontext = DB;
    //insert item (would have added this myself but you don't say how)
    dbcontext.SubmitChanges();
}
0 голосов
/ 07 июня 2009

Привет, я тоже с этим боролся и нашел очень элегантное решение.

По сути, вам необходимо использовать метод DataContext.Attach (EntityToUpdate, OriginalEntity).

Есть несколько ошибок ... Итак, прочитайте эту информацию, она все объяснит .

Как только вы прочитаете это, просто возвращайтесь ко мне с любыми вопросами. Я написал действительно полезный класс EntitySaver, основанный на этой информации, поэтому, если вам нужно, мы можем пройти ваш класс, как только вы получите ошибки.

ура

EDIT : Вот мой класс полностью, на случай, если вы захотите попробовать. Это на самом деле обрабатывает обновления и вставки автоматически. дайте мне знать, у вас есть какие-либо вопросы.

Entity Saver:

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QDAL.CoreContext;
using QDAL.CoreEntities;
using LinqExtension.CustomExtensions;

namespace QDAL
{
    internal class DisconnectedEntitySaver
    {
        private QDataDataContext ContextForUpdate;

        public DisconnectedEntitySaver() {
            ContextForUpdate = Base.CreateDataContext();
        }

        public List<TEntityType> SaveEntities<TEntityType, TKeyType>(List<TEntityType> EntitiesToSave) {

            string PKName;

            PKName = Base.GetPrimaryKeyName(typeof(TEntityType), ContextForUpdate);

            return SaveEntities<TEntityType, TKeyType>(EntitiesToSave, PKName);
        }

        public List<TEntityType> SaveEntities<TEntityType, TKeyType>(List<TEntityType> EntitiesToSave, string KeyFieldName)
        {
            List<TEntityType> EntitiesToPossiblyUpdate;
            List<TEntityType> EntitiesToInsert;
            List<TEntityType> HandledEntities = new List<TEntityType>();

            bool TimeStampEntity;
            Type ActualFieldType;

            if (EntitiesToSave.Count > 0) {
                TimeStampEntity = Base.EntityContainsTimeStamp(typeof(TEntityType), ContextForUpdate);

                ActualFieldType = EntitiesToSave.FirstOrDefault().GetPropertyType(KeyFieldName);

                if (ActualFieldType != typeof(TKeyType)) {
                    throw new Exception("The UniqueFieldType[" + typeof(TKeyType).Name + "] specified does not match the actual field Type[" + ActualFieldType.Name + "]");
                }

                if (ActualFieldType == typeof(string)) {
                    EntitiesToPossiblyUpdate = EntitiesToSave.Where(ent => string.IsNullOrEmpty(ent.GetPropertyValue<string>(KeyFieldName)) == false).ToList();
                    EntitiesToInsert = EntitiesToSave.Where(ent => string.IsNullOrEmpty(ent.GetPropertyValue<string>(KeyFieldName)) == true).ToList();
                } else {
                    EntitiesToPossiblyUpdate = EntitiesToSave.Where(ent => EqualityComparer<TKeyType>.Default.Equals(ent.GetPropertyValue<TKeyType>(KeyFieldName), default(TKeyType)) == false).ToList();
                    EntitiesToInsert = EntitiesToSave.Where(ent => EqualityComparer<TKeyType>.Default.Equals(ent.GetPropertyValue<TKeyType>(KeyFieldName), default(TKeyType)) == true).ToList();
                }

                if (EntitiesToPossiblyUpdate.Count > 0) {
                    EntitiesToInsert.AddRange(ResolveUpdatesReturnInserts<TEntityType, TKeyType>(EntitiesToPossiblyUpdate, KeyFieldName));

                    HandledEntities.AddRange(EntitiesToPossiblyUpdate.Where(ent => EntitiesToInsert.Select(eti => eti.GetPropertyValue<TKeyType>(KeyFieldName)).Contains(ent.GetPropertyValue<TKeyType>(KeyFieldName)) == false));
                }

                if (EntitiesToInsert.Count > 0) {
                    ContextForUpdate.GetTable(typeof(TEntityType)).InsertAllOnSubmit(EntitiesToInsert);

                    HandledEntities.AddRange(EntitiesToInsert);
                }

                ContextForUpdate.SubmitChanges();
                ContextForUpdate = null;

                return HandledEntities;
            } else {
                return EntitiesToSave;
            }
        }

        private List<TEntityType> ResolveUpdatesReturnInserts<TEntityType, TKeyType>(List<TEntityType> PossibleUpdates, string KeyFieldName)
        {
            QDataDataContext ContextForOrginalEntities;

            List<TKeyType> EntityToSavePrimaryKeys;
            List<TEntityType> EntitiesToInsert = new List<TEntityType>();
            List<TEntityType> OriginalEntities;

            TEntityType NewEntityToUpdate;
            TEntityType OriginalEntity;

            string TableName;

            ContextForOrginalEntities = Base.CreateDataContext();

            TableName = ContextForOrginalEntities.Mapping.GetTable(typeof(TEntityType)).TableName;
            EntityToSavePrimaryKeys = (from ent in PossibleUpdates select ent.GetPropertyValue<TKeyType>(KeyFieldName)).ToList();

            OriginalEntities = ContextForOrginalEntities.ExecuteQuery<TEntityType>("SELECT * FROM " + TableName + " WHERE " + KeyFieldName + " IN('" + string.Join("','", EntityToSavePrimaryKeys.Select(varobj => varobj.ToString().Trim()).ToArray()) + "')").ToList();

            //kill original entity getter
            ContextForOrginalEntities = null;

            foreach (TEntityType NewEntity in PossibleUpdates)
            {
                NewEntityToUpdate = NewEntity;
                OriginalEntity = OriginalEntities.Where(ent => EqualityComparer<TKeyType>.Default.Equals(ent.GetPropertyValue<TKeyType>(KeyFieldName),NewEntityToUpdate.GetPropertyValue<TKeyType>(KeyFieldName)) == true).FirstOrDefault();

                if (OriginalEntity == null)
                {
                    EntitiesToInsert.Add(NewEntityToUpdate);
                }
                else
                {
                    ContextForUpdate.GetTable(typeof(TEntityType)).Attach(CloneEntity<TEntityType>(NewEntityToUpdate), OriginalEntity);
                }
            }

            return EntitiesToInsert;
        }

        protected  TEntityType CloneEntity<TEntityType>(TEntityType EntityToClone)
        {
            var dcs = new System.Runtime.Serialization.DataContractSerializer(typeof(TEntityType));
            using (var ms = new System.IO.MemoryStream())
            {
                dcs.WriteObject(ms, EntityToClone);
                ms.Seek(0, System.IO.SeekOrigin.Begin);
                return (TEntityType)dcs.ReadObject(ms);
            }
        }
    }
}

Вам также понадобятся эти помощники:

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QDAL.CoreContext;
using QDAL.CoreEntities;
using System.Configuration;

namespace QDAL
{
    internal class Base
    {
        public Base() {
        }

        internal static QDataDataContext CreateDataContext() {
            QDataDataContext newContext;
            string ConnStr;

            ConnStr = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

            newContext = new QDataDataContext(ConnStr);

            return newContext;
        }

        internal static string GetTableName(Type EntityType, QDataDataContext CurrentContext) {
            return CurrentContext.Mapping.GetTable(EntityType).TableName;
        }

        internal static string GetPrimaryKeyName(Type EntityType, QDataDataContext CurrentContext) {
            return (from m in CurrentContext.Mapping.MappingSource.GetModel(CurrentContext.GetType()).GetMetaType(EntityType).DataMembers where m.IsPrimaryKey == true select m.Name).FirstOrDefault();
        }

        internal static bool EntityContainsTimeStamp(Type EntityType, QDataDataContext CurrentContext) {
            return (CurrentContext.Mapping.MappingSource.GetModel(CurrentContext.GetType()).GetMetaType(EntityType).DataMembers.Where(dm => dm.IsVersion == true).FirstOrDefault() != null);
        }
    }
}

и эти расширения облегчают отражение:

<System.Runtime.CompilerServices.Extension()> _
    Public Function GetPropertyValue(Of ValueType)(ByVal Source As Object, ByVal PropertyName As String) As ValueType
        Dim pInfo As System.Reflection.PropertyInfo

        pInfo = Source.GetType.GetProperty(PropertyName)

        If pInfo Is Nothing Then
            Throw New Exception("Property " & PropertyName & " does not exists for object of type " & Source.GetType.Name)
        Else
            Return pInfo.GetValue(Source, Nothing)
        End If
    End Function

    <System.Runtime.CompilerServices.Extension()> _
    Public Function GetPropertyType(ByVal Source As Object, ByVal PropertyName As String) As Type
        Dim pInfo As System.Reflection.PropertyInfo

        pInfo = Source.GetType.GetProperty(PropertyName)

        If pInfo Is Nothing Then
            Throw New Exception("Property " & PropertyName & " does not exists for object of type " & Source.GetType.Name)
        Else
            Return pInfo.PropertyType
        End If
    End Function
0 голосов
/ 07 июня 2009

Если я правильно понимаю, вам не нужно размышлять для этого.

Чтобы сделать это для конкретной сущности, вам нужно взять вашу сущность и присоединить ее к контексту БД. После подключения LINQ-to-SQL определит, что необходимо обновить. Что-то вроде:

// update an existing member
dbcontext.Members.Attach(member, true);

// commit to database
dbcontext.SubmitChanges();

Это было бы для обновления члена в таблице участников. Истинный аргумент говорит, что вы изменили его. В качестве альтернативы, если у вас лежал оригинал, вы можете передать его в качестве второго аргумента и позволить контексту DB выполнить различие для вас. Это основная часть реализации контекста БД (которая реализует шаблон «Единица работы»).

Чтобы обобщить это, вы должны заменить тип Member на T и заменить .Members на .GetTable:

public virtual void Update(T entity)
{
        var dbcontext = DB;
        dbcontext.GetTable<T>().Attach(entity, true);
        dbcontext.SubmitChanges();
}

Предполагая, что идентификатор уже установлен правильно на объекте (и что он помечен как первичный ключ в модели), вам даже не нужно сначала его искать. Если вы чувствуете необходимость, вы можете найти ее по ID и затем передать ее в метод Attach, но это, вероятно, просто вызывает дополнительный поиск, который не нужен.

РЕДАКТИРОВАТЬ: Вам необходимо установить UpdateCheck на Никогда для ваших столбцов в модели , в противном случае он пытается выполнить проверку параллелизма. Вы получите последнее обновление побед, если вы установите его на Никогда. В противном случае вы добавите поля tmestamp в свои таблицы, и проверка параллелизма определит, устарела ли сущность или нет.

UpdateCheck. Никогда в сочетании с Attach (entity, bool) это самый простой и эффективный способ решить эту проблему с помощью LINQ-to-SQL.

0 голосов
/ 19 мая 2009

Ну, у меня есть что-то вроде этого (с макушки головы):

public Question UpdateQuestion(Question newQuestion)
    {
        using (var context = new KodeNinjaEntitiesDataContext())
        {
            var question = (from q in context.Questions where q.QuestionId == newQuestion.QuestionId select q).SingleOrDefault();
            UpdateFields(newQuestion, question);
            context.SubmitChanges();                
            return question;
        }
    }

    private static void UpdateFields(Question newQuestion, Question oldQuestion)
    {
        if (newQuestion != null && oldQuestion != null)
        {
            oldQuestion.ReadCount = newQuestion.ReadCount;
            oldQuestion.VotesCount = newQuestion.VotesCount;
            //.....and so on and so on.....
        }
    }

Работает нормально для простых объектов. Конечно, если у вас много сущностей, вы можете использовать отражение.

...