2009-03-12 16 views
17

Chciałbym napisać ogólną funkcję Upsert dla LINQ do SQL i mam problem z koncepcją tego, jak to zrobić. Chciałbym go do pracy mniej więcej tak:Jak napisać Upsert dla LINQ do SQL?

var db = new DataContext(); 
db.Customers.UpsertOnSubmit(customer); 

Więc to musi być w jakiś sposób rodzajowy i myślę i metoda rozszerzenie na tablicy. Udało mi się zajść tak daleko w określeniu klucz podstawowy tabeli podstawowej:

var context = source.Context; 
var table = context.Mapping.GetTable(source.GetType()); 
var primaryMember = table.RowType.DataMembers.SingleOrDefault(m => m.IsPrimaryKey); 

Jestem zakładając, konieczne będzie mieć to komponować zapytania, aby powiedzieć, jeśli element jest w DB już lub nie, ale tak naprawdę nie wiem, co z tym zrobić.

Odpowiedz

7

Robię coś podobnego, ale z innym podejściem. Każdy podmiot implementuje IEntity. Jedną z właściwości IEntity jest stan, jeśli obiekt jest nowy lub istniejący. I następnie wdrożyć, że dla każdego podmiotu, jak:

public EntityState EntityState 
{ 
    get 
    { 
     if (_Id > 0) 
      return EntityState.Exisiting; 
     else 
      return EntityState.New; 
    } 
} 

Następnie generic Upsert może być (na rodzajowego klasy typu repozytorium):

public virtual void Upsert<Ta>(Ta entity) 
    where Ta: class 
{ 
    if (!(entity is IEntity)) 
     throw new Exception("T must be of type IEntity"); 

    if (((IEntity)entity).EntityState == EntityState.Exisiting) 
     GetTable<Ta>().Attach(entity, true); 
    else 
     GetTable<Ta>().InsertOnSubmit(entity); 
} 

private System.Data.Linq.Table<Ta> GetTable<Ta>() 
    where Ta: class 
{ 
    return _dataContext.Context.GetTable<Ta>(); 
} 

Jeśli Twój dołączenie innego datacontext, a także upewnić się, masz znacznik czasu na swoich obiektach.

+1

Cóż ... cały punkt o upsert jest to, że don” t wiedzieć, czy istnieje, czy nie, po prostu chcesz, aby baza danych miała nową zawartość; upsert oszczędza ci "dodatkowy objazd", aby to ustalić. Twoje rozwiązanie nie odpowiada na to; potrzebne jest sql 'MERGE' jak opisano tutaj: http://stackoverflow.com/questions/2479488/syntax-for-single-row-merge-upsert-in-sql-server. – atlaste

+0

Ta odpowiedź dotyczy Entity Framework. Zobacz moją odpowiedź na rozwiązanie LINQ-SQL. – orad

1

Krótka odpowiedź:

Grab to: EntityExtensionMethods.cs

Wyjaśnienie

Aby zrobić upsert w LINQ-SQL bez sprawdzania rekordów pierwsze, można wykonać następujące czynności. Będzie on nadal hit db raz, aby sprawdzić, czy rekord istnieje, ale nie będzie ciągnąć rekord:

var blob = new Blob { Id = "some id", Value = "some value" }; // Id is primary key (PK) 

if (dbContext.Blobs.Contains(blob)) // if blob exists by PK then update 
{ 
    // This will update all columns that are not set in 'original' object. For 
    // this to work, Blob has to have UpdateCheck=Never for all properties except 
    // for primary keys. This will update the record without querying it first. 
    dbContext.Blobs.Attach(blob, original: new Blob { Id = blob.Id }); 
} 
else // insert 
{ 
    dbContext.Blobs.InsertOnSubmit(blob); 
} 
dbContext.Blobs.SubmitChanges(); 

metodę rozszerzenia

wymyśliłem następującą metodę rozszerzenia dla niego.

public static class EntityExtensionMethods 
{ 
    public static void InsertOrUpdateOnSubmit<TEntity>(this Table<TEntity> table, TEntity entity, TEntity original = null) 
     where TEntity : class, new() 
    { 
     if (table.Contains(entity)) // if entity exists by PK then update 
     { 
      if (original == null) 
      { 
       // Create original object with only primary keys set 
       original = new TEntity(); 
       var entityType = typeof(TEntity); 
       var dataMembers = table.Context.Mapping.GetMetaType(entityType).DataMembers; 
       foreach (var member in dataMembers.Where(m => m.IsPrimaryKey)) 
       { 
        var propValue = entityType.GetProperty(member.Name).GetValue(entity, null); 
        entityType.InvokeMember(member.Name, BindingFlags.SetProperty, Type.DefaultBinder, 
         original, new[] {propValue}); 
       } 
      } 

      // This will update all columns that are not set in 'original' object. For 
      // this to work, entity has to have UpdateCheck=Never for all properties except 
      // for primary keys. This will update the record without querying it first. 
      table.Attach(entity, original); 
     } 
     else // insert 
     { 
      table.InsertOnSubmit(entity); 
     } 
    } 
} 

Używaj go jak poniżej:

var blob = new Blob { Id = "some id", Value = "some value" }; // Id is primary key (PK) 
dbContext.Blobs.InsertOrUpdateOnSubmit(blob); 
dbContext.Blobs.SubmitChanges(); 

dodałem powyżej metodę rozszerzenia z więcej rzeczy do tego GIST: EntityExtensionMethods.cs