3

我已经实现了一种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如下


[更新]

正如 Marc 正确建议的那样,我只是在更改局部变量的值。那么您认为我应该如何使用这种方法?使用反射并将属性从复制entityitem

4

7 回答 7

8

您更新的只是一个局部变量;为此,您必须将成员值entityto 复制到item- 不是那么简单。


像下面这样的东西;我使用的唯一原因TKey是我在 Northwind.Customer 上进行了测试,它有一个字符串键;-p

使用元模型的优点是即使您使用 POCO 类(和基于 xml 的映射)它也可以工作,并且它不会尝试更新与模型无关的任何内容。

出于示例的目的,我已经传入了数据上下文,您需要SubmitChanges在某些时候添加 a,但其余部分应该可以直接比较。

顺便说一句 - 如果您乐于从传入的对象中获取 ID,那也很容易 - 然后您可以支持复合身份表。

    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?
    }
于 2009-05-18T22:20:03.333 回答
3

在这里重新审视一下,以前对问题的回答对应用程序做出了各种假设。

应用程序中的并发性是需要预先考虑的事情,并且实际上并没有一刀切的答案。为您的应用选择时需要考虑的事项:

  • LINQ to SQL / Entity Framework 是非常可配置的,因为没有万能的。
  • 在您的应用程序有一定的负载之前,您不会看到并发的效果(即,您一个人,在您自己的机器上可能永远看不到它)
  • 您的应用程序多久允许 2 个(或更多)用户编辑同一个实体?
  • 您想如何处理两个编辑重叠的情况?
  • 您的应用程序是否在另一层(例如 Ajax)之间来回序列化数据?如果是这样,那么您如何知道已编辑的实体是否在读取/更新之间被修改?时间戳?版本字段?
  • 您是否关心编辑是否重叠?特别注意 FK 关系。数据完整性是您可能被最后一场胜利所困扰的地方。

不同的解决方案具有非常不同的性能影响!在开发过程中您不会注意到,但当 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:

只有成员映射为AlwaysWhenChanged参与乐观并发检查。对标记为 的成员不进行检查Never。有关详细信息,请参阅UpdateCheck

要启用乐观并发,您需要指定一个用作并发标记的字段(例如时间戳或版本),并且在来回序列化时必须始终存在该字段。 用 标记此列IsVersion=true

如果您不想进行并发检查,则必须将所有标记为 UpdateCheck.Never。

于 2009-06-08T16:06:05.707 回答
1

我遇到了一些类似的问题,最终选择了PLINQO,对 LINQ-TO-SQL 生成的代码进行了很多增强。但是,如果您还没有它,它确实需要购买 CodeSmith(尽管可以免费评估 30 天)。

于 2009-06-12T02:39:15.720 回答
0

好吧,我有这样的事情(从我的头顶):

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.....
        }
    }

它适用于简单的实体。当然,如果你有很多实体,你可以使用反射。

于 2009-05-19T14:39:28.427 回答
0

如果我理解正确,您不需要对此进行反思。

要为特定实体执行此操作,您需要获取您的实体并将其附加到数据库上下文。附加后,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(并且它在模型中被标记为主键),您甚至不需要先查找它。如果您觉得有必要,您可以通过 ID 查找它,然后将其传递给 Attach 方法,但这可能只会导致不需要的额外查找。

编辑:您需要在模型中的列上将 UpdateCheck 设置为 Never,否则它会尝试执行并发检查。如果您将其设置为从不,您将获得最后一次更新胜利。否则,您将 tmestamp 字段添加到表中,并发检查将确定实体是否已过期。

UpdateCheck.Never 与 Attach(entity, bool) 结合使用将是使用 LINQ-to-SQL 解决此问题的最简单和最高效的方法。

于 2009-06-06T23:17:00.613 回答
0

嘿,dreas,我也为此苦苦挣扎,并找到了一个非常优雅的解决方案。

您基本上必须使用 DataContext.Attach(EntityToUpdate,OriginalEntity) 方法。

有一些陷阱......所以,阅读此信息,它将解释一切

读完之后,有任何问题都可以回来找我。我已经根据该信息编写了一个非常有用的 EntitySaver 类,所以如果你需要,一旦你得到了陷阱,我们就可以通过你的类。

干杯

编辑:这是我的完整课程,以防您想尝试一下。它实际上自动处理更新和插入。让我知道你有任何问题。

实体保护程序:

    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
于 2009-06-06T23:31:30.337 回答
0

我对存储库模式不是很熟悉,但是如果您从数据库中删除旧实体,然后将新实体放入具有相同 ID 的数据库中怎么办?像这样的东西:

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();
}
于 2009-06-09T21:24:52.943 回答