Это отстой или нет - Простой DAL с кэшированием с использованием Linq to SQL - PullRequest
1 голос
/ 07 мая 2010

Я создал простой уровень доступа к данным для кэширования, который использует кэширование с использованием блока приложения для кэширования библиотеки Enterprise, а также использует уведомление о запросах SQL, поэтому не поддерживает запросы, которые недопустимы для уведомления о запросах.

Справочная информация: это было введено в действие после разработки приложения, чтобы облегчить нагрузку на базу данных и ускорить работу приложения. Основное использование этого DAL предназначено для извлечения данных, которые, как ожидается, не будут меняться очень часто, например данных в справочных таблицах (представленных в раскрывающихся списках пользовательского интерфейса и т. Д.).

В основном используется как в следующем примере:

var cachingDal = new CachingDataAccessLayer();
var productTypes = cachingDal.LoadData<ProductType>();

Где ProductType - это таблица Linq to SQL. Мне любопытно посмотреть, что люди думают о реализации, которую я придумал, и ужасно ли это или удивительно.

Вот код. Ищите любые предложения, критику и т. Д. Имейте в виду, что я не выбрал технологию и строю поверх существующей системы, поэтому переключение историй доступа к данным на самом деле не мое призвание.

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using MyDatabase;

public class CachingDataAccessLayer
{
    #region Cache Keys
    private const string CacheManagerName = "CachingDataAccessLayer";
    #endregion

    #region Database
    /// <summary>
    /// Instantiate new MyDataContext
    /// </summary>
    /// <returns></returns>
    private MyDataContext DatabaseConnection()
    {
        // instantiate database connection
        var database = new MyDataContext(Constants.DatabaseConnectionString);

        // set transaction isolation level to read committed
        database.ExecuteQuery(typeof(string), "SET TRANSACTION ISOLATION LEVEL READ COMMITTED");

        return database;
    }
    #endregion

    #region Generic Data Access with Caching
    /// <summary>
    /// Calls .Exists on list using predicate and if it evaluates to false, adds records to list using predicate.
    /// </summary>
    /// <typeparam name="TEntity">Database table</typeparam>
    /// <param name="list">List to add records to</param>
    /// <param name="predicate">The delagate that defines the conditions of elements to search for.</param>
    public void AddRecordsIfNeeded<TEntity>(ref List<TEntity> list, Predicate<TEntity> predicate) where TEntity : class
    {
        // check if items are in list based on predicate and if not, add them to the list
        if (!list.Exists(predicate))
        {
            list.AddRange(LoadData<TEntity>(predicate.Invoke));
        }
    }

    /// <summary>
    /// Retrieve all records of type TEntity from the cache if available with filter Active = true (if Active property exists).<br/>
    /// If data is not available in cache go directly to the database.<br/>
    /// In addition, sets up query notification and refreshes cache on database change.
    /// </summary>
    /// <typeparam name="TEntity">Database table to retrieve.</typeparam>
    /// <returns>returns List of TEntity</returns>
    public List<TEntity> LoadData<TEntity>() where TEntity : class
    {
        // default filter is no filter
        Func<TEntity, bool> predicate = delegate { return true; };

        // check for active property
        var activeProperty = typeof (TEntity).GetProperty("Active");

        // if active property exists and is a boolean, set predicate to filter Active == true
        if (activeProperty != null)
            if (activeProperty.PropertyType.FullName == typeof (bool).FullName)
                predicate = (x => (bool) activeProperty.GetValue(x, null));

        // load data & return
        return LoadData(predicate);
    }

    /// <summary>
    /// Retrieve all records of type TEntity from the cache if available.<br/>
    /// If data is not available in cache go directly to the database.<br/>
    /// In addition, sets up query notification and refreshes cache on database change.
    /// </summary>
    /// <typeparam name="TEntity">Database table to retrieve.</typeparam>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <returns>returns List of TEntity</returns>
    public List<TEntity> LoadData<TEntity>(Func<TEntity, bool> predicate) where TEntity : class
    {
        // default is to not refresh cache
        return LoadData(predicate, false);
    }

    /// <summary>
    /// Retrieve all records of type TEntity from the cache if available.<br/>
    /// If data is not available in cache or refreshCache is set to true go directly to the database.<br/>
    /// In addition, sets up query notification and refreshes cache on database change.
    /// </summary>
    /// <typeparam name="TEntity">Database table to retrieve.</typeparam>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <param name="refreshCache">If true, ignore cache and go directly to the database and update cache.</param>
    /// <returns></returns>
    public List<TEntity> LoadData<TEntity>(Func<TEntity, bool> predicate, bool refreshCache) where TEntity : class
    {
        // instantiate database connection
        using (var database = DatabaseConnection())
        {
            // instantiate the cache
            var cache = CacheFactory.GetCacheManager(CacheManagerName);

            // get cache key name
            var cacheKey = typeof(TEntity).Name;

            // if the value is in the cache, return it
            if (cache.Contains(cacheKey) && !refreshCache)
                // get data from cache, filter it and return results
                return (cache.GetData(cacheKey) as List<TEntity>).Where(predicate).ToList();

            // retrieve the data from the database
            var data = from x in database.GetTable<TEntity>()
                       select x;

            // if value is in cache, remove it
            if (cache.Contains(cacheKey))
                cache.Remove(cacheKey);

            // add unfiltered results to cache
            cache.Add(cacheKey, data.ToList());

            Logger.Write(string.Format("Added {0} to cache {1} with key '{2}'", typeof(TEntity).Name, CacheManagerName, cacheKey));

            // set up query notification
            SetUpQueryNotification<TEntity>();

            // return filtered results
            return data.Where(predicate).ToList();
        }
    }
    #endregion

    #region Query Notification
    public void SetUpQueryNotification<TEntity>() where TEntity : class
    {
        // get database connection
        var database = DatabaseConnection();

        // set up query notification
        using (var sqlConnection = new SqlConnection(Constants.DatabaseConnectionString))
        {
            // linq query
            var query = from t in database.GetTable<TEntity>()
                        select t;

            var command = database.GetCommand(query);

            // create sql command
            using (var sqlCommand = new SqlCommand(command.CommandText, sqlConnection))
            {
                // get query parameters
                var sqlCmdParameters = command.Parameters;

                // add query parameters to dependency query
                foreach (SqlParameter parameter in sqlCmdParameters)
                {
                    sqlCommand.Parameters.Add(new SqlParameter(parameter.ParameterName, parameter.SqlValue));
                }

                // create sql dependency
                var sqlDependency = new SqlDependency(sqlCommand);

                // set up query notification
                sqlDependency.OnChange += sqlDependency_OnChange<TEntity>;

                // open connection to database
                sqlConnection.Open();

                // need to execute query to make query notification work
                sqlCommand.ExecuteNonQuery();
            }
        }

        Logger.Write(string.Format("Query notification set up for {0}", typeof(TEntity).Name));
    }

    /// <summary>
    /// Calls LoadData of type TEntity with refreshCache param set to true.
    /// </summary>
    /// <typeparam name="TEntity">Database table to refresh.</typeparam>
    void RefreshCache<TEntity>() where TEntity : class
    {
        // refresh cache
        LoadData<TEntity>(delegate { return true; }, true);
    }

    /// <summary>
    /// Refreshes data in cache for type TEntity if type is Delete, Insert or Update.<br/>
    /// Also re-sets up query notification since query notification only fires once.
    /// </summary>
    /// <typeparam name="TEntity">Database table</typeparam>
    void sqlDependency_OnChange<TEntity>(object sender, SqlNotificationEventArgs e) where TEntity : class
    {
        var sqlDependency = sender as SqlDependency;

        // this should never happen
        if (sqlDependency == null)
            return;

        // query notification only happens once, so remove it, it will be set up again in LoadData
        sqlDependency.OnChange -= sqlDependency_OnChange<TEntity>;

        // if the data is changed (delete, insert, update), refresh cache & set up query notification
        //  otherwise, just set up query notification
        if (e.Info == SqlNotificationInfo.Delete || e.Info == SqlNotificationInfo.Insert || e.Info == SqlNotificationInfo.Update)
        {
            // refresh cache & set up query notification
            Logger.Write(string.Format("sqlDependency_OnChange (Info: {0}, Source: {1}, Type: {2}). Refreshing cache for {3}", e.Info, e.Source, e.Type, typeof(TEntity).Name));
            RefreshCache<TEntity>();
        }
        else
        {
            // set up query notification
            SetUpQueryNotification<TEntity>();
        }
    }
    #endregion
}

Ответы [ 2 ]

3 голосов
/ 07 мая 2010

Лично я бы предложил использовать шаблон Repository, где у вас есть IRepository.

Тогда в реальном выражении вы могли бы использовать контейнер IoC, чтобы предоставить вашему приложению CacheRepository для некоторых статических типов, который использует систему кэширования в первом случае и либо автоматически делегировать в LinqToSqlRepository, где данные не найдены, либо возвращает ноль и позволяет вам самостоятельно заполнять кеш.

1 голос
/ 07 мая 2010

Если данные не должны сильно изменяться и используются для пользовательского интерфейса, например, для раскрывающихся списков и т. Д., Почему бы не кэшировать данные на компьютере клиента? Мы сделали это для приложения, которое мы создали некоторое время назад. Мы кэшировали почти все наши данные типа «поиск» в файлах на клиентском компьютере, а затем создали механизм для аннулирования данных, когда они были изменены в базе данных. Это было очень быстро и хорошо сработало для нас.

Кстати, вы знаете, что L2S имеет собственное кэширование?

...