2009-05-18 7 views
3

J'ai mis en place une sorte de Repository classe et il a a GetByID, DeleteByID méthodes et ainsi de suite, mais je vais avoir du mal à mettre en œuvre la méthode UpdateByID.C# LINQ-SQL: Une méthode UpdateByID pour le modèle référentiel

je fait quelque chose comme ceci:

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(); 
    } 
} 

mais pas la mise à jour de l'entité passée.

Est-ce que quelqu'un a implémenté une telle méthode?


Pour référence, here est la méthode GetByID


[Mise à jour]

Comme Marc a suggéré correctement, je change simplement les valeurs de la variable locale. Alors, comment pensez-vous que je devrais aller sur cette méthode? Utilisez la réflexion et copiez les propriétés de entity à item?

+0

En fait, je pense que la méthode GetByID est également fausse ;-p Travailler sur un exemple qui corrige les deux (et fonctionne pour POCO ainsi que attribué) –

+0

Hmm mais le GetByID a toujours fonctionné (si je ne me trompe pas). Quelle est votre opinion Marc? –

+0

Eh bien, je ne peux pas voir comment GetPrimaryKey() est implémenté (il n'est pas montré), mais je suppose qu'il regarde les attributs. Ceci est une erreur**. Ce n'est pas ** une exigence de LINQ-to-SQL d'utiliser des attributs; il est également possible d'utiliser un fichier de mappage externe. Dans ce cas, l'attribut (s) n'existera pas, et il échouera. Si vous utilisez le méta-modèle, cela fonctionne pour n'importe quelle implémentation (puisque c'est ainsi que LINQ-to-SQL demande "quelle est la clé primaire"). –

Répondre

8

Tout ce que vous avez mis à jour est une variable locale; pour que cela fonctionne, vous devez copier les valeurs de membre de entity à item - pas tout à fait si simple.


Quelque chose comme ci-dessous; la seule raison pour laquelle j'ai utilisé TKey était que j'ai testé sur Northwind.Customer, qui a une clé de chaîne ;-p

L'avantage d'utiliser le méta-modèle est qu'il fonctionne même si vous utilisez des classes POCO (et le xml -based mapping), et il n'essaie pas de mettre à jour quoi que ce soit sans rapport avec le modèle. Pour les besoins de l'exemple, j'ai passé dans le contexte de données, et vous devez ajouter un SubmitChanges à un certain point, mais le reste devrait être directement comparable. BTW - si vous êtes heureux de prendre l'ID de l'objet transmis, ce serait facile aussi - et vous pourriez prendre en charge les tables d'identité composites.

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? 
    } 
+0

Ah oui je le pensais ... Alors, comment puis-je implémenter une telle méthode de dépôt? Pensez-vous que je devrais utiliser la réflexion pour transférer les champs du paramètre (membre) à l'élément? –

+0

Oui, probablement vous auriez besoin d'utiliser la réflexion. –

+0

Marc, j'ai essayé ta méthode et ça marche, merci. Maintenant, j'ai besoin de prendre le temps de vraiment comprendre votre code; –

0

Eh bien j'ai quelque chose comme ça (à partir du haut de ma tête):

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

Il fonctionne bien pour les entités simples. Bien sûr, si vous avez plusieurs entités, vous pouvez utiliser la réflexion.

+0

Oui, je sais que je peux le faire (c'est-à-dire copier les champs manuellement du paramètre vers le local) mais ce que j'essaye de faire est de créer une méthode générique qui s'occupe de ceci par elle-même. Je suppose que je devrais utiliser la réflexion pour un tel ... –

0

Si je comprends bien, vous ne devriez pas avoir besoin de réflexion pour cela. Pour cela, pour une entité spécifique, vous devez prendre votre entité et l'attacher au contexte de la base de données. Une fois connecté, LINQ-to-SQL déterminera ce qui doit être mis à jour. Quelque chose du genre:

Cela correspond à la mise à jour d'un membre dans la table Membres. Le vrai argument dit que vous l'avez modifié.Sinon, si vous aviez l'original qui traînait, vous pouviez le passer comme second argument et laisser le contexte DB effectuer le diff pour vous. C'est une partie importante de l'implémentation du contexte DB (qui implémente le pattern "Unit of Work").

Pour généraliser cela, vous devez remplacer le type de membre avec T, et remplacer les .Members avec .GetTable:

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

En supposant que l'ID est déjà configuré correctement sur l'entité (et qu'elle est marquée en tant que clé primaire dans le modèle), vous n'avez même pas besoin de le rechercher en premier. Si vous en ressentez le besoin, vous pouvez le rechercher par ID, puis le passer dans la méthode Attach, mais cela ne provoque probablement qu'une recherche supplémentaire inutile.

EDIT: Vous avez besoin de set UpdateCheck to Never on your columns in the model, sinon il essaie d'effectuer une vérification de simultanéité. Vous obtiendrez une dernière mise à jour gagne si vous le définissez sur Jamais. Sinon, vous ajoutez des champs tmestamp à vos tables et la vérification de la concurrence déterminera si l'entité est obsolète ou non.

UpdateCheck.Never combiné avec Attach (entité, bool) sera le moyen le plus simple et le plus performant pour résoudre ce problème avec LINQ-to-SQL.

+0

Cela ne fonctionnera pas universellement ... cela ne fonctionnera que si vous avez défini des champs TimeStamp sur vos entités – andy

+0

Il suffit de mettre UpdateCheck à Jamais sur vos champs et vous aurez obtenir la dernière mise à jour gagne, sans avoir besoin d'un champ d'horodatage. http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.columnattribute.updatecheck.aspx – mckamey

+0

Cela ne peut-il pas entraîner des problèmes de simultanéité? Attacher (entité, entité) est d'abord fastidieux, mais fonctionne tout le temps sans modifications personnalisées de vos modèles. – andy

0

Hey dreas, j'ai aussi lutté avec cela et trouvé une solution très élégante.

Vous devez essentiellement utiliser la méthode DataContext.Attach (EntityToUpdate, OriginalEntity).

Il y a quelques pièges ... donc, read this information, it will explain everything.

Une fois que vous l'avez lu, revenez me voir si vous avez des questions. J'ai écrit une classe EntitySaver vraiment utile basée sur cette information, donc si vous en avez besoin, nous pouvons passer par votre classe une fois que vous avez obtenu les gotchas.

acclamations

EDIT: Voici ma classe en plein, au cas où vous voulez l'essayer. Il gère réellement les mises à jour et les insertions automatiquement. laissez-moi savoir que vous avez des questions.

Entité 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); 
      } 
     } 
    } 
} 

Vous aurez besoin de ces aides aussi:

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); 
     } 
    } 
} 

et ces extensions rendent la réflexion plus facile:

<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

Andy, merci pour le lien; va vérifier –

+0

cool, laissez-moi savoir comment ça se passe. J'ai mis à jour mes réponses avec un tas de code qui peut aider. – andy

3

un regard nouveau ici, les réponses précédentes à la question ont fait diverses suppositions au sujet de l'application.

La concurrence au sein d'une application est quelque chose qui doit être pensé à l'avance et est quelque chose pour lequel il n'y a pas vraiment de solution unique. Points à prendre en compte lors du choix de votre application:

  • LINQ to SQL/Entity Framework est très configurable car il n'existe pas de taille unique.
  • Vous ne verrez pas l'effet de la simultanéité jusqu'à ce que vous ayez une certaine charge sur votre application (c.-à-d.vous, seul, à votre propre machine pourrait jamais le voir)
  • À quelle fréquence votre application permet-elle à 2 utilisateurs (ou plus) d'éditer la même entité?
  • Comment voulez-vous gérer le cas où deux modifications se chevauchent?
  • Votre application sérialise-t-elle les données dans une autre couche (par exemple Ajax)? Si oui, comment savez-vous si l'entité éditée a été modifiée entre la lecture/mise à jour? Horodatage? Champ de version?
  • Vous souciez-vous si les modifications se chevauchent? Portez une attention particulière aux relations FK. L'intégrité des données est l'endroit où vous pouvez vous faire mordre par le dernier gagne.

Différentes solutions ont des implications de performances très différentes! Vous ne remarquerez pas que vous développez, mais votre application peut tomber quand 25 personnes l'utilisent simultanément. Surveillez beaucoup de copie en arrière et pour beaucoup SQL lit comme suit:

  • Ne pas appeler SQL dans une boucle (montre pour quand vous passez dans une liste d'entités)
  • Ne pas utiliser réflexion pour ceci lorsque vous avez déjà vérifié la simultanéité via LINQ
  • Réduire au minimum la copie en avant et en arrière des champs (peut être nécessaire lors du franchissement des limites N-Tier).
  • Ne créez pas de requête distincte pour rechercher l'ancienne entité (utilisez-la uniquement si vous l'avez déjà trouvée). Laissez LINQ faire cela car elle est plus optimisée pour le faire en SQL.

Voici quelques bons liens pour la lecture plus profonde de décider vos besoins spécifiques:

Ma solution recommandée:

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 spécifie une des opérations suivantes:

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

Vous devrez également faire quelques considérations sur votre modèle:

va probablement sans dire mais vous devra indiquer à LINQ quel champ est votre clé primaire pour mettre à jour les entités. Vous n'avez pas besoin de le passer en paramètre (comme dans votre méthode d'origine) car LINQ sait déjà qu'il s'agit du PK.

Vous obtenez (plutôt que "devoir") décider quels champs sont réellement vérifiés. Par exemple, un champ de clé étrangère est très important pour avoir une vérification de simultanéité, alors que le champ de description mérite probablement un dernier-gagnant. Vous contrôlez cela via l'attribut UpdateCheck. La valeur par défaut est UpdateCheck.Always.De MSDN:

Seuls les membres cartographiés Always ou WhenChanged participent à des contrôles optimistes de concurrence . Aucune vérification n'est effectuée pour les membres marqués Never pour . Pour plus d'informations, voir UpdateCheck.

Pour activer l'accès concurrentiel optimiste, vous devez spécifier un champ à utiliser comme le jeton d'accès concurrentiel (par exemple d'horodatage ou version) et ce domaine doit toujours être présent lors de la sérialisation avant et en arrière. Mark this column with IsVersion=true.

Si vous ne souhaitez pas la vérification de la concurrence, vous devez tout marquer comme UpdateCheck.Never.

0

Je ne connais pas très bien les modèles de référentiel, mais que se passe-t-il si vous supprimez l'ancienne entité de la base de données et que vous placez la nouvelle entité dans la base de données avec le même ID? quelque chose comme ceci:

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(); 
} 
1

J'ai eu des problèmes similaires et a fini par aller avec PLINQO, beaucoup d'améliorations au code LINQ-TO-SQL généré. Cependant, il nécessite un achat de CodeSmith (bien que libre d'évaluation pendant 30 jours) si vous ne l'avez pas déjà.

Questions connexes