Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiled query fails - Query was compiled for a different mapping source than the one associated with the specified DataContext

I have the following code for a compiled Linq2sql query to count rows in a table. The query throws an exception despite the same uncompiled query running smoothly:

public static Func<ServiceCustomContext, int> CompiledCount
    = CompiledQuery.Compile((ServiceCustomContext db) => db.Current.Count());
public static int Count()
{
    using (ServiceCustomContext db = new ServiceCustomContext(Constants.NewSqlConnection))
        return CompiledCount(db);
}

ServiceCustomContext inherits from DataContext and has only (besides a constructor) Tables including a table named Current used in the example above.

And I get the following exception:

'Query was compiled for a different mapping source than the one associated with the specified DataContext.'

This is only when using, as above, a compiled query. As long as I have a simple:

return db.Current.Count();

in the Count() method, everything is fine.

I don't understand what's wrong. I thought it might be that I need to keep a reference to the DataContext (ServiceCustomContext) although that seemed counter intuitive, but even the Microsoft examples don't do that. The only explanation I've found, is here which is basically that compiled queries as mentioned in the Microsoft examples in the link above are really wrong. I doubt that's true though.

like image 242
ispiro Avatar asked Dec 09 '18 20:12

ispiro


1 Answers

The code would run just fine if only you used Count() only once during the lifetime of your application. And the error means exactly what it says. If you look at the code of CompiledQuery:

    private object ExecuteQuery(DataContext context, object[] args) {
        if (context == null) {
            throw Error.ArgumentNull("context");
        }
        if (this.compiled == null) {
            lock (this) {
                if (this.compiled == null) {
                    this.compiled = context.Provider.Compile(this.query);
                    this.mappingSource = context.Mapping.MappingSource;
                }
            }
        }
        else {
            if (context.Mapping.MappingSource != this.mappingSource)
                throw Error.QueryWasCompiledForDifferentMappingSource();
        }
        return this.compiled.Execute(context.Provider, args).ReturnValue;
    }

You can see that what it does, it actually compiles the query only when it's first invoked. It also saves the reference to your DataContext.Mapping.MappingSource and makes sure you use the same MappingSource on every subsequent call.

By default, every time you create a new DataContext, a new MappingSource is created along. This is done in constructor and there is no way to override it later as both DataContext.Mapping and MetaModel.MappingSource properties only have a public getter. However, there is a constructor overload of DataContext which takes a MappingSource as an argument which (lucky you) allows you to reuse a single mapping source throughout the lifetime of your application.

Not sure what are the exact constructors of your ServiceCustomContext class, but the following code should give you a hint of the solution to prevent the error from happening:

public class ServiceCustomContext : DataContext
{
    private static readonly MappingSource mappingSource = new AttributeMappingSource();

    public ServiceCustomContext(string connection) : base(connection, mappingSource)
    {
    }
}

I assumed you are using attributes for declaring mapping, however you can use XmlMappingSource in case you're using an XML file for your database mapping.

like image 74
Imantas Avatar answered Oct 18 '22 17:10

Imantas