Jeg har kæmpet lidt med at få UPDATE vha. Table<Entity>.Attach til at virke med LINQ to SQL. Lad os tage udgangspunkt i en databasetabel User. Vha. LINQ to SQL har vi fået genereret en User klasse, som logisk set har følgende udseende:
public class User
{
public int Id { get; set; }
public bool IsMale { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}
Vi vil nu gerne opdatere en række i databasen, som har Id = 1. Det data, der skal gemmes, har en bruger indtastet i en form på en ASP.NET side. Så vi forsøger os med følgende:
using (DataClassesDataContext db = new DataClassesDataContext())
{
User lt = new User()
{
Id = 1
};
db.Users.Attach(lt);
lt.IsMale = true;
lt.Age = 42;
lt.Name = "!";
db.SubmitChanges();
}
Vores update går fint igennem og alt er godt.
En dag kommer der en rigtig provokerende bruger forbi vores website. Da han skal redigere sine eksisterende brugeroplysninger, sætter han alder til 0 og udfylder ikke sit navn. Vi får følgende værdier:
using (DataClassesDataContext db = new DataClassesDataContext())
{
User lt = new User()
{
Id = 1
};
db.Users.Attach(lt);
lt.IsMale = true;
lt.Age = 0;
lt.Name = null;
db.SubmitChanges();
}
Til vores skræk bliver alder og navn ikke opdateret i databasen, og den dag en kvinde finder på ikke at udfylde alder og navn går det helt galt: Intet bliver opdateret. Årsagen er, at en property på klassen User ikke bliver ændret og at diverse property changed events ikke bliver kaldt, hvis den nye værdi ikke er anderledes end den eksisterende. Når man som ovenfor opretter en instans af User, bliver alle properties sat til deres default værdier: False for bools, 0 for integers, null for strings etc. Derfor mener LINQ to SQL ikke, at det er nødvendigt at sende en UPDATE til databasen, hvis man sætter de forskellige properties til deres default værdier. Bemærk i øvrigt, at dette har intet at gøre med "Update Check" i .dbml filer.
På nettet er der diverse "løsninger" til problemet: Indlæs først den pågældende entity fra databasen via samme data context og undgå dermed Attach, gem alle entities i Session eller gem alle entities i ViewState. Jeg er ikke begejstret for nogen af løsningerne. ViewState løsningen er slet ikke mulig i ASP.NET MVC.
En anden mulig løsning er at sætte alle properties til noget snedigt før man laver sin Attach. Det er ikke kønt, så jeg er modtagelig overfor andre forslag:
public static void Update(int id, bool isMale, int age, string name)
{
using (DataClassesDataContext db = new DataClassesDataContext())
{
// Grimt - men det virker
User lt = new User()
{
Id = id,
IsMale = !isMale,
Age = age == 0 ? 1 : age,
Name = name == null ? "" : name
};
db.Users.Attach(lt);
lt.IsMale = isMale;
lt.Age = age;
lt.Name = name;
db.SubmitChanges();
}
}