LINQ-to-SQL Sugar
Yes, yes, LINQ-to-SQL is probably, one day, going to be consumed or deprecated by it’s big brother the Entity Framework. However, for the current .NET platform (3.5) and likely for the next (4.0) it is still a viable ORM option in the Visual Studio box. There are lots of reasons to use LINQ-to-SQL and lots of reasons not to use it…that is for another post.
What I do want to share is a set of extension methods that (for me) made using LINQ-to-SQL just a little bit nicer. The title is indeed descriptive…these are “sugar” added to LINQ-to-SQL and don’t fundamentally improve how it works, just how the programmer works with the library. I’m sure developers using LINQ-to-SQL have created scads of other such saccarine extensions to the framework…if you have some I’d be interested in hearing about them.
So here’s my LinqToSqlExtensions class:
public static class LinqToSqlExtensions { public static TEntity GetById<TEntity>(this Table<TEntity> @this, int id) where TEntity : class { return @this.SingleOrDefault(x => ((IIdentified)x).ID == id); } public static TEntity Upsert<TEntity>(this Table<TEntity> @this, int id) where TEntity : class, new() { var entity = @this.SingleOrDefault(x => ((IIdentified)x).ID == id); if (entity == null) { entity = new TEntity(); @this.InsertOnSubmit(entity); } if (entity is IAuditable) ((IAuditable) entity).ModifiedDate = DateTime.Now; return entity; } public static TEntity Upsert<TEntity>(this Table<TEntity> @this, Func<TEntity, bool> predicate) where TEntity : class, new() { var entity = @this.SingleOrDefault(predicate); if (entity == null) { entity = new TEntity(); @this.InsertOnSubmit(entity); } if (entity is IAuditable) ((IAuditable) entity).ModifiedDate = DateTime.Now; return entity; } public static void Delete<TEntity>(this Table<TEntity> @this, TEntity entity) where TEntity : class { @this.DeleteOnSubmit(entity); } public static void DeleteById<TEntity>(this Table<TEntity> @this, int id) where TEntity : class { var entity = @this.GetById(id); @this.Delete(entity); } public static void DeleteAll<TEntity>(this Table<TEntity> @this, IEnumerable<TEntity> entities) where TEntity : class { entities.ToList().ForEach(e => @this.Delete(e)); } public static void CommitChanges(this DataContext @this, RepositoryContext context) { var changeSet = @this.GetChangeSet(); for (var i = 0; i < changeSet.Inserts.Count; i++) if (changeSet.Inserts[i] is IAuditable) UpdateAuditProperties((IAuditable) changeSet.Inserts[i], context, true); for (var i = 0; i < changeSet.Updates.Count; i++) if (changeSet.Updates[i] is IAuditable) UpdateAuditProperties((IAuditable) changeSet.Updates[i], context, false); @this.SubmitChanges(); } private static void UpdateAuditProperties<TEntity>(TEntity entity, RepositoryContext context, bool isInsert) where TEntity : IAuditable { entity.ModifiedDate = DateTime.Now; entity.ModifiedBy = context.UserID; if (isInsert) { entity.CreatedDate = DateTime.Now; entity.CreatedBy = context.UserID; } } }
Here’s a rundown of the methods it creates and how to use them:
GetById
Simple enough, allows you to avoid using the LINQ syntax to find an element by it’s identifier.
var thing = DataContext.Things.GetById(3);
Upsert
This is probably the most convenient addition. It simply checks to see whether the object exists (by it’s ID) if so it returns it if not it creates a new one and returns that. It just avoids a lot of query-for-an-object-and-if-its-null-create-a-new-one-and-insert-it code.
var thing = DataContext.Things.Upsert(2); var anotherThing = DataContext.Things.Upsert(x => x.Name == "That Thing");
Delete and DeleteById
Pretty obvious…
DataContext.Things.Delete(thing); DataContext.Things.DeleteById(3);
CommitChanges
This is just a replacement for SubmitChanges that allows us to update some audit columns automagically.
Some of this magic is provided via a set of partial classes on those that LINQ-to-SQL generates that applies some additional interfaces to those classes. So, I have a Partials.cs file that contains the following:
public interface IAuditable { public DateTime CreatedDate { get; set; } public DateTime UpdatedDate { get; set; } public int CreatedBy { get; set; } public int UpdatedBy { get; set; } } public interface IIdentified { public int ID { get; set; } } partial class Thing : IIdentified, IAuditable { } public class OtherThing : IIdentified { } public class LastThing : IAuditable { }
As long as your generated class (and it’s related table) have properties of the same names as the IAuditable and IIdentified interfaces the LINQ-to-SQL extension methods can update them appropriately and in the background. I could have accomplished much of this by just inheriting a new DataContext and adding some additional functionality, but doing it this way you can add these methods to even a closed assembly.