using System; using System.Collections.Generic; using System.Linq; using System.Data.Linq; using System.Linq.Expressions; namespace System.Data.Linq { /// /// Simple class to provide CRUD functionality for LINQ-to-SQL objects /// public abstract class DatabaseBase where TContext : DataContext, new() { protected DatabaseBase() { } #region DataContext Helpers public static List Query(Func> f) { using (var dc = new TContext { ObjectTrackingEnabled = false }) return f(dc).ToList(); } public static T Query(Func f) { using (var dc = new TContext { ObjectTrackingEnabled = false }) return f(dc); } public static void Use(Action f) { using (var dc = new TContext()) f(dc); } #endregion #region Tables protected static TableHelper CreateTable(Func> tableSelector) where TItem : class { return new TableHelper(tableSelector); } /// Provides an easy way to create a specific TableBase. /// A func that selects the needed table. /// dc => dc.Accounts /// An expression to select the table's key. /// a => a.AccountId protected static TableHelper CreateTable(Func> tableSelector, Expression> keySelectorExp) where TItem : class { return new TableHelper(tableSelector, keySelectorExp); } protected static TableHelper CreateTable(Func> tableSelector, Expression> key1SelectorExp, Expression> key2SelectorExp) where TItem : class { return new TableHelper(tableSelector, key1SelectorExp, key2SelectorExp); } /// /// Class to provide CRUD functionality for a specific table. This is the base for TableHelper `TItem`TKey and TableHelper `TItem`TKey`TKey /// public class TableHelper where TItem : class { public TableHelper(Func> tableSelector) { this.tableSelector = tableSelector; } protected Func> tableSelector; #region Basic querying public List Where(Expression> predicate) { return DatabaseBase.Query(dc => tableSelector(dc).Where(predicate)); } public List Query(Func, IQueryable> f) { return DatabaseBase.Query(dc => f(tableSelector(dc))); } public TItem SingleOrDefault(Expression> predicate) { return DatabaseBase.Query(dc => tableSelector(dc).SingleOrDefault(predicate)); } #endregion #region Insert/Update protected static List listApply(IEnumerable items, Func f) { return items.Select(f).ToList(); } public List Insert(IEnumerable items) { return listApply(items, Insert); } public TItem Insert(TItem item) { return DatabaseBase.Query(dc => { dc.ObjectTrackingEnabled = true; tableSelector(dc).InsertOnSubmit(item); dc.SubmitChanges(); return item; }); } public List Update(IEnumerable items) { return listApply(items, Update); } public TItem Update(TItem item) { return DatabaseBase.Query(dc => { dc.ObjectTrackingEnabled = true; tableSelector(dc).Attach(item, true); dc.SubmitChanges(); return item; }); } #endregion } public class TableHelper : TableHelper where TItem : class { public TableHelper( Func> tableSelector, Expression> keySelectorExp ) : base(tableSelector) { this.keySelectorExp = keySelectorExp; this.compiledKeySelector = keySelectorExp.Compile(); } Expression> keySelectorExp; Func compiledKeySelector; // Here we add the single key specific stuff private Expression> createKeyPredicate(TKey key) { var itemParam = Expression.Parameter(typeof(TItem), "item"); var equalExp = Expression.Equal( Expression.Constant(key, typeof(TKey)), Expression.Invoke(keySelectorExp, itemParam)); return Expression.Lambda>(equalExp, itemParam); } public TItem SelectByKey(TKey key) { var pred = createKeyPredicate(key); return SingleOrDefault(pred); } public void Delete(IEnumerable keys) { foreach (var key in keys) Delete(key); } public IList Delete(IEnumerable items) { return listApply(items, Delete); } public TItem Delete(TItem item) { TKey key = compiledKeySelector(item); Delete(key); return item; } public void Delete(TKey key) { // We have to select first to make sure the item exists // This provides the same semantics as update // A transaction is needed to enforce this guarantee DatabaseBase.Use(dc => { using (var txScope = new System.Transactions.TransactionScope()) { var table = tableSelector(dc); var pred = createKeyPredicate(key); var item = table.SingleOrDefault(pred); if (item == null) throw new ChangeConflictException("Row not found."); table.DeleteOnSubmit(item); dc.SubmitChanges(); txScope.Complete(); } }); } } public class TableHelper : TableHelper where TItem : class { public TableHelper( Func> tableSelector, Expression> key1SelectorExp, Expression> key2SelectorExp ) : base(tableSelector) { this.key1SelectorExp = key1SelectorExp; this.compiledKey1Selector = key1SelectorExp.Compile(); this.key2SelectorExp = key2SelectorExp; this.compiledKey2Selector = key2SelectorExp.Compile(); } Expression> key1SelectorExp; Func compiledKey1Selector; Expression> key2SelectorExp; Func compiledKey2Selector; // Dual key specific stuff private Expression> createDualKeyPredicate(TKey1 key1, TKey2 key2) { var itemParam = Expression.Parameter(typeof(TItem), "item"); var key1EqExp = Expression.Equal( Expression.Constant(key1, typeof(TKey1)), Expression.Invoke(key1SelectorExp, itemParam)); var key2EqExp = Expression.Equal( Expression.Constant(key2, typeof(TKey2)), Expression.Invoke(key2SelectorExp, itemParam)); var and = Expression.And(key1EqExp, key2EqExp); return Expression.Lambda>(and, itemParam); } public TItem SelectByKey(TKey1 key1, TKey2 key2) { var pred = createDualKeyPredicate(key1, key2); return SingleOrDefault(pred); } public void Delete(IEnumerable> keys) { foreach (var key in keys) Delete(key.A, key.B); } public IList Delete(IEnumerable items) { return listApply(items, Delete); } public TItem Delete(TItem item) { var key1 = compiledKey1Selector(item); var key2 = compiledKey2Selector(item); Delete(key1, key2); return item; } public void Delete(TKey1 key1, TKey2 key2) { // We have to select first to make sure the item exists // This provides the same semantics as update // A transaction is needed to enforce this guarantee DatabaseBase.Use(dc => { using (var txScope = new System.Transactions.TransactionScope()) { var table = tableSelector(dc); var pred = createDualKeyPredicate(key1, key2); var item = table.SingleOrDefault(pred); if (item == null) throw new ChangeConflictException("Row not found."); table.DeleteOnSubmit(item); dc.SubmitChanges(); txScope.Complete(); } }); } } #endregion } }