20

I have the following code:

public void DeleteAccountsForMonth(int year, int month)
{
    var result = from acm in this._database.AccountsOnMonth
                 where ((acm.Year == year) && (acm.Month == month))
                 select acm.Id;
    var query = (ObjectQuery<int>)result;

    string sql = string.Format(
        "DELETE FROM [AccountsOnMonth] WHERE [AccountsOnMonth].[Id] IN ({0})",
        query.ToTraceString()
    );

    var parameters = new List<System.Data.SqlClient.SqlParameter>();
    foreach (ObjectParameter parameter in query.Parameters)
    {
        parameters.Add(new System.Data.SqlClient.SqlParameter {
            ParameterName = parameter.Name,
            Value = parameter.Value
        });
    }

    this._database.Database.ExecuteSqlCommand(sql, parameters.ToArray());
}

Basically, what I'm trying to do is to delete a bulk of data from a context (get a query result, get SQL and execute it). But I'm having a problem when casting result to ObjectQuery. The exception that gives is

Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery1[System.Int32]' to type 'System.Data.Objects.ObjectQuery1[System.Int32]'.

Can anybody give any hint to solve this? Thanks!

EDIT: Ladislav first solution helped me solve the problem, but it happenned a little problem with the SQL parameters of the generated SQL query, i.e. the SQL query generated by query.ToString() was this:

DELETE FROM [SncAccountOnMonths] WHERE [SncAccountOnMonths].[Id] IN (
    SELECT [Extent1].[Id] AS [Id]
    FROM [dbo].[SncAccountOnMonths] AS [Extent1]
    WHERE ([Extent1].[Year] = @p__linq__0) AND ([Extent1].[Month] = @p__linq__1))

The problem was that the variables @p__linq__0 and @p__linq__1 where not declared and so the query gave the error "Must declare the scalar variable @p_linq_0" (I sure it would give the same error for variable @p__linq__1). To "declare" them I need to pass them as arguments of the ExecuteSqlCommand(). And so, the final solution for the initial answer is the code below:

public void DeleteAccountsForMonth(int year, int month)
{
    var result = (this._database.AccountsOnMonth
        .Where(acm => (acm.Year == year) && (acm.Month == month)))
        .Select(acm => acm.Id);
    var query = (DbQuery<int>)result;

    string sql = string.Format(
        "DELETE FROM [AccountsOnMonth] WHERE [AccountsOnMonth].[Id] IN ({0})",
        query.ToString()
    );

    this._database.Database.ExecuteSqlCommand(sql,
        new SqlParameter("p__linq__0", year),
        new SqlParameter("p__linq__1", month)
    );
}

By the way, I assume the variables generated always have the format @p__linq__, unless Microsoft's Entity Framework Team changes it in any future EF update...

jmpcm
  • 1,814
  • 2
  • 19
  • 27

2 Answers2

23

That is because your _database is derived from DbContext and your AccountsOfMonth is DbSet<>. In such case you cannot use ObjectQuery directly because DbSet<> produces DbQuery<> which is not convertible to ObjectQuery<>.

You must either use DbQuery<> directly:

var result = from acm in this._database.AccountsOnMonth
             where ((acm.Year == year) && (acm.Month == month))
             select acm.Id;
var query = (DbQuery<int>)result;

string sql = string.Format(
    "DELETE FROM [AccountsOnMonth] WHERE [AccountsOnMonth].[Id] IN ({0})",
    query.ToString()
);

Or you must first convert your context to ObjectContext and create ObjectSet<>:

var objectContext = ((IObjectContextAdapter)_database).ObjectContext;
var set = objectContext.CreateObjectSet<AccountsOnMonth>();
var resut = from acm in set
            where ((acm.Year == year) && (acm.Month == month))
            select acm.Id;

The problem with first approach is that DbQuery doesn't offer Parameters collection - just another example of simplification in DbContext API which only makes it harder to use.

Pragmateek
  • 13,174
  • 9
  • 74
  • 108
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • thank you for your help! Check the edit my post (I added the final solution) because it happenned a little problem with the SQL parameters. Either way, it's solved! :) – jmpcm Aug 27 '11 at 23:03
1

In my case I really needed the parameters and I found this workaround:

var query = (DbQuery<int>)result;

FieldInfo internalQueryField = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(f => f.Name.Equals("_internalQuery")).FirstOrDefault();
var internalQuery = internalQueryField.GetValue(query);
FieldInfo objectQueryField = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(f => f.Name.Equals("_objectQuery")).FirstOrDefault();
ObjectQuery<int> objectQuery = objectQueryField.GetValue(internalQuery) as ObjectQuery<int>;

foreach (ObjectParameter objectParam in objectQuery.Parameters)
{
    SqlParameter sqlParam = new SqlParameter(objectParam.Name, objectParam.Value);
    // Etc...
}

I'm implementing SqlCacheDependency with Entity Framework. :-)

Juan Carlos Puerto
  • 2,632
  • 1
  • 26
  • 22
  • 1
    In EF6 this code still works, however you MUST FIRST force the query to be compiled and cached before you try to inspect the parameters or the parameters collection will be empty! Call `.ToString()` on the original query first! – Chris Schaller Mar 21 '19 at 13:49