Как перехватить и изменить запрос SQL в Linq to SQL - PullRequest
9 голосов
/ 11 сентября 2009

Мне было интересно, есть ли способ перехватить и изменить sql, сгенерированный из linq в Sql перед отправкой запроса?

По сути, у нас есть уровень безопасности записей, который при запросе типа «выбрать * из записей» изменит запрос на «выбор * из записей WHERE [somesecurityfilter]»

Я пытаюсь найти лучший способ перехватить и изменить sql перед его выполнением поставщиком linq to sql.

Ответы [ 4 ]

3 голосов
/ 26 ноября 2010

Хорошо, сначала, чтобы прямо ответить на ваш вопрос (но читайте слова предостережения;)), есть способ, хотя и привередливый, делать то, что вы хотите.

// IQueryable<Customer> L2S query definition, db is DataContext (AdventureWorks)
var cs = from c in db.Customers 
         select c;
// extract command and append your stuff
DbCommand dbc = db.GetCommand(cs);
dbc.CommandText += " WHERE MiddleName = 'M.'";
// modify command and execute letting data context map it to IEnumerable<T>
var result = db.ExecuteQuery<Customer>(dbc.CommandText, new object[] { });

Теперь, предостережения.

  1. Вы должны знать, какой запрос генерируется, чтобы знать, как его изменить, это продлевает разработку.
  2. Он выпадает из структуры L2S и, таким образом, создает возможную зияющую дыру для устойчивого развития, если кто-нибудь изменит Linq, это повредит.
  3. Если ваш Linq вызывает параметры (имеет расширение where или другое, вызывающее появление раздела WHERE с константами), это усложняет ситуацию, вам придется извлечь и передать эти параметры в ExecuteQuery

В общем, возможно, но очень хлопотно. При этом вам следует рассмотреть возможность использования расширения .Where (), как предложено Yaakov . Если вы хотите централизованно управлять безопасностью на уровне объектов, используя этот подход, вы можете создать расширение для его обработки для вас

static class MySecurityExtensions
{
    public static IQueryable<Customer> ApplySecurity(this IQueryable<Customer> source)
    {
        return source.Where(x => x.MiddleName == "M.");
    }
} 

//...
// now apply it to any Customer query
var cs = (from c in db.Customers select c).ApplySecurity();

, поэтому, если вы измените ApplySecurity, он будет автоматически применен ко всем запросам linq объекта Customer.

2 голосов
/ 26 ноября 2010

Если вы хотите перехватить SQL, сгенерированный L2S, и поиграться с ним, лучше всего создать классы-обертки для SqlConnection, SqlCommand, DbProviderFactory и т. Д. Дать упакованный экземпляр SqlConnection перегрузке конструктора LatS-Datacontext, которая принимает соединение дб. В подключенном соединении вы можете заменить DbProviderFactory своим собственным классом, производным от DbProviderFactory, который возвращает упакованные версии SqlCommand и т. Д.

например:.

//sample wrapped SqlConnection:
public class MySqlConnectionWrapper : SqlConnection
{
  private SqlConnecction _sqlConn = null;
  public MySqlConnectionWrapper(string connectString)
  {
    _sqlConn = new SqlConnection(connectString);
  }

  public override void Open()
  {
    _sqlConn.Open();
  }

  //TODO: override everything else and pass on to _sqlConn...

  protected override DbProviderFactory DbProviderFactory
  {
    //todo: return wrapped provider factory...
  }
}

При использовании:

using (SomeDataContext dc = new SomeDataContext(new MySqlConnectionWrapper("connect strng"))
{
  var q = from x in dc.SomeTable select x;
  //...etc...
}

Тем не менее, вы действительно хотите идти по этому пути? Вам нужно будет уметь анализировать операторы SQL и запросы, сгенерированные L2S, чтобы правильно их изменить. Если вместо этого вы можете изменить запросы linq, добавив к ним все, что хотите, это, вероятно, лучшая альтернатива.

Помните, что запросы Linq являются составными, поэтому вы можете добавлять «дополнительные» в отдельный метод, если у вас есть что-то, что вы хотите добавить ко многим запросам.

2 голосов
/ 23 ноября 2010

Первое, что приходит мне в голову, - это изменить запрос и вернуть результат в формате, отличном от LINQ.

//Get linq-query as datatable-schema
        public DataTable ToDataTable(System.Data.Linq.DataContext ctx, object query)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            IDbCommand cmd = ctx.GetCommand((IQueryable)query);
            System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();
            adapter.SelectCommand = (System.Data.SqlClient.SqlCommand)cmd;
            DataTable dt = new DataTable("sd");

            try
            {
                cmd.Connection.Open();
                adapter.FillSchema(dt, SchemaType.Source);
                adapter.Fill(dt);
            }
            finally
            {
                cmd.Connection.Close();
            }
            return dt;
        }

попробуйте добавить ваше условие в команду selectCommand и посмотрите, поможет ли это.

1 голос
/ 23 ноября 2010

Попробуйте настроить представление в БД, которое при необходимости применяет фильтр безопасности к записям, а затем при извлечении записей через L2S. Это гарантирует, что нужные вам записи не будут возвращены.

В качестве альтернативы, добавьте .Where () к запросу до его отправки, который будет применять фильтр безопасности. Это позволит вам применить фильтр программно (на случай, если его нужно будет изменить в зависимости от сценария).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...