2009-03-12 7 views
17

Así que me gustaría escribir una función Upsert genérica para LINQ to SQL y estoy teniendo problemas para conceptualizar cómo hacerlo. Me gustaría que funcione algo como esto:¿Cómo escribirías un Upsert para LINQ to SQL?

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

Por lo tanto, tendría que ser genérica de alguna manera y que supongo y método de extensión en la tabla. He sido capaz de llegar tan lejos en la determinación de la clave primaria de la tabla subyacente:

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

Estoy asumiendo que será necesario tener presente para componer una consulta para saber si el artículo está en el PP ya sea o no, pero realmente no sé qué hacer con él en este punto.

Respuesta

7

Hago algo similar, pero con un enfoque diferente. Cada entidad implementa IEntity. Una de las propiedades de IEntity es un estado si el objeto es nuevo o existe. a continuación, implemento que para cada entidad, como:

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

Entonces, un genérico Upsert podría ser (en una clase de tipo de repositorio genérico):

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

Si su fijación de otra DataContext, también asegurarse tienes una marca de tiempo en tus objetos.

+1

Bueno ... el punto entero de un upsert es que usted don' t saber si existe o no, solo quiere que la base de datos tenga el nuevo contenido; el upsert le ahorra el 'viaje de ida y vuelta' para determinarlo. Tu solución no responde eso; lo que se necesita es un sql 'MERGE' como se describe aquí: http://stackoverflow.com/questions/2479488/syntax-for-single-row-merge-upsert-in-sql-server. – atlaste

+0

Esta respuesta se aplica a Entity Framework. Vea mi respuesta para una solución LINQ-to-SQL. – orad

1

respuesta corta:

Grab esto: EntityExtensionMethods.cs

Explicación

Para ello UPSERT en LINQ to SQL sin consultar los registros en primer lugar, puede hacer lo siguiente. Todavía llegará a la db vez para comprobar si existe registro, pero no se tire el registro:

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

Método de extensión

me ocurrió la siguiente método de extensión para el mismo.

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

Se usa como a continuación:

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

he añadido por encima de método de extensión con más cosas a este GIST: EntityExtensionMethods.cs