سؤال

لقد نفذت نوعا من 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 طريقة


[تحديث]

وكما اقترح مارك بشكل صحيح، فأنا أقوم فقط بتغيير قيم المتغير المحلي.إذًا كيف تعتقد أنني يجب أن أتبع هذه الطريقة؟استخدم الانعكاس وانسخ الخصائص من entity ل item ?

هل كانت مفيدة؟

المحلول

كل ما قمت بتحديثه هو متغير محلي.لكي يعمل ذلك، يجب عليك نسخ ملف قيم الأعضاء من 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?
    }

نصائح أخرى

بإلقاء نظرة جديدة هنا، قدمت الإجابات السابقة على السؤال افتراضات مختلفة حول التطبيق.

يعد التزامن داخل التطبيق أمرًا يجب التفكير فيه مقدمًا وهو أمر لا توجد له إجابة واحدة تناسب الجميع.الأشياء التي يجب مراعاتها عند اختيار طلبك:

  • تعد LINQ إلى SQL / Entity Framework قابلة للتكوين للغاية لأنه لا يوجد مقاس واحد يناسب الجميع.
  • لن ترى تأثير التزامن حتى يكون لديك حمل معين على تطبيقك (على سبيل المثال.أنت وحدك، على جهازك الخاص أبداً أراه)
  • كم مرة يسمح تطبيقك لمستخدمين (أو أكثر) بتحرير نفس الكيان؟
  • كيف تريد التعامل مع الموقف عندما يتداخل تعديلان؟
  • هل يقوم تطبيقك بتسلسل البيانات ذهابًا وإيابًا عبر طبقة أخرى (على سبيل المثال؟اياكس)؟إذا كان الأمر كذلك، فكيف يمكنك معرفة ما إذا كان الكيان المحرر قد تم تعديله بين القراءة/التحديث؟الطابع الزمني؟مجال الإصدار؟
  • هل تهتم إذا تداخلت التعديلات؟انتبه بشكل خاص إلى علاقات FK.تكامل البيانات هو المكان الذي يمكن أن تتعرض فيه للعض من خلال الانتصارات الأخيرة.

الحلول المختلفة لها آثار مختلفة جدًا على الأداء!لن تلاحظ ذلك أثناء تطويرك ولكن تطبيقك قد ينهار عندما يستخدمه 25 شخصًا في وقت واحد.راقب الكثير من النسخ ذهابًا وإيابًا والعديد من قراءات SQL:

  • لا تتصل بـ SQL في حلقة (راقب هذا عند تمرير قائمة الكيانات)
  • لا تستخدم الانعكاس لهذا عندما يكون لديك بالفعل التحقق من التزامن عبر LINQ
  • قلل من نسخ الحقول ذهابًا وإيابًا (قد يكون ضروريًا عند عبور حدود N-Tier).
  • لا تقم بإجراء استعلام منفصل للبحث عن الكيان القديم، (استخدمه فقط إذا كان موجودًا لديك بالفعل) اسمح لـ 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.

واجهت بعض المشكلات المماثلة وانتهى بي الأمر بالذهاب معها بلينكو, ، الكثير من التحسينات على التعليمات البرمجية التي تم إنشاؤها LINQ-TO-SQL.ومع ذلك، فهو يتطلب شراء CodeSmith (على الرغم من أنه مجاني للتقييم لمدة 30 يومًا) إذا لم يكن لديك بالفعل.

حسنًا، لدي شيء كهذا (من أعلى رأسي):

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

إنه يعمل بشكل جيد للكيانات البسيطة.بالطبع إذا كان لديك العديد من الكيانات فيمكنك استخدام الانعكاس.

مرحبًا يا عزيزتي، لقد عانيت أيضًا من هذا الأمر ووجدت حلاً أنيقًا للغاية.

يجب عليك بشكل أساسي استخدام طريقة 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

إذا كنت أفهم بشكل صحيح، لا ينبغي أن تحتاج إلى التفكير في هذا.

للقيام بذلك لكيان معين، يتعين عليك أخذ الكيان الخاص بك وإرفاقه بسياق قاعدة البيانات.بمجرد إرفاقه، سيحدد LINQ-to-SQL ما يجب تحديثه.شيء على غرار:

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

// commit to database
dbcontext.SubmitChanges();

سيكون ذلك لتحديث عضو في جدول الأعضاء.الحجة الحقيقية تقول أنك قمت بتعديلها.وبدلاً من ذلك، إذا كان لديك النسخة الأصلية، فيمكنك تمرير ذلك كوسيطة ثانية والسماح لسياق قاعدة البيانات بإجراء الفرق نيابةً عنك.يعد هذا جزءًا رئيسيًا من تنفيذ سياق قاعدة البيانات (الذي ينفذ نمط "وحدة العمل").

لتعميم ذلك، يمكنك استبدال نوع العضو بـ T، واستبدال .Members بـ .GetTable:

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

بافتراض أن المعرف قد تم تعيينه بشكل صحيح بالفعل على الكيان (وأنه تم وضع علامة عليه كمفتاح أساسي في النموذج)، فلن تحتاج حتى إلى البحث عنه أولاً.إذا شعرت بالحاجة، يمكنك البحث عنه بواسطة المعرف ثم تمريره إلى طريقة الإرفاق، ولكن من المحتمل أن يؤدي هذا إلى إجراء بحث إضافي غير مطلوب.

يحرر: أنت بحاجه إلى قم بتعيين UpdateCheck على Never على أعمدتك في النموذج, وإلا فإنه يحاول إجراء فحص التزامن.سوف تحصل على آخر تحديث يفوز إذا قمت بتعيينه على "أبدًا".وإلا فإنك تضيف حقول tmestamp إلى جداولك وسيحدد التحقق من التزامن ما إذا كان الكيان قديمًا أم لا.

سيكون UpdateCheck.Never مع Attach(entity, bool) الطريقة الأبسط والأكثر أداءً لحل هذه المشكلة باستخدام LINQ-to-SQL.

لست على دراية بأنماط المستودع، ولكن ماذا لو قمت بحذف الكيان القديم من قاعدة البيانات، ووضع الكيان الجديد في قاعدة البيانات بنفس المعرف؟شيء من هذا القبيل:

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();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top