We work on a project with EF5 (DbContext) and MVVM WPF Light Toolkit.
So, to load data on the RadGridView, we try to use QueryableEntityCollectionView, instead of RadEntityFrameworkDataSource, it seems to be the good approach ?
But when we want to include graph object when we load entities (with relatedObjectsToInclude in the ctor) when have a problem with the inheritance of the model because we have 2 levels. The message is :
An include path specified is not valid. The EntityType 'OPY.Entities.Sinistre' does not declare a navigation property with the name 'Agent'.
The model :
[Sinistre]
|
[SinistrePS]
[----------]
[ AgentId ] ---------- [Agent]
|
[Others class]
The code :
this
.SinistreDataView =
new
QueryableEntityCollectionView<SinistrePS>(
((IObjectContextAdapter)_bdd).ObjectContext,
"Sinistres"
,
new
List<
string
>() {
"Agent"
});
As you can see, we want to load "SinitrePS" with all derived but we are forced to use "Sinistres" (top level) as entitySetName because the SinistrePS class don't have it own entityset on the model.
How can we do ?
Thx
13 Answers, 1 is accepted
I am afraid that with the QECV you cannot load more than the first level of related entities, i.e. the Include attribute is not drill-down.
You can bind RadGridView directly to ObjectContext.Sinistres without using a QECV at all. Since it is an IQueryable, your queries will still be executed on the server.
Rossen Hristov
Telerik
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
In the application, we have a pager binded on the grid and the user can filter the grid on "Reference" property with a textbox.
Here the code :
this
.SinistreDataView.FilterDescriptors.Add(
new
FilterDescriptor("Reference", FilterOperator.Contains, textboxValue));
How can we do this without QECV ?
I am afraid that you can not.
By the way, can you show me how do you include those related Agents when not using any Telerik controls at all, i.e. when you work directly with the ObjectContext.
We don't do anything special -- we simply append an Include clause based on the string that you provide in the ctor. Something like this:
var result = this.ObjectContext.Sinistres.Include(s => s.Agent);
That is what our code does when it sees your input string in the constructor. Unfortunately, you cannot do this on several levels since there is no way to express this desire.
Rossen Hristov
Telerik
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
My Context :
public
partial
class
BddOpyContext : DbContext
{
public
DbSet<Sinistre> Sinistres {
get
;
set
; }
// First level
public
DbSet<SinistrePS> SinistrePSs {
get
;
set
; }
// Second level
}
The code
BddOpyContext _bdd
retval = _bdd.SinistrePSs
.Include("Agent"
)
.OrderByDescending(at => at.Reference);
Indeed we use DBContext and not ObjectContext ... and i don't know how to create the query to use ObjectContext ... (it's the realy problem)
You could perhaps build the view from a DbContext ?
The code would be :
theDbContext.Set<T>().Include(
"includes"
)
What do you think?
I am somewhat confused.
Well, the query that you just wrote would mean that you should pass "SinistrePSs" as the entity set name in the QECV constructor. Then we will append the Include clause on SinistrePS.
This:
retval = _bdd.SinistrePSs
.Include("Agent"
)
;translates to
this
.SinistreDataView =
new
QueryableEntityCollectionView<SinistrePS>(
((IObjectContextAdapter)
_bdd
).ObjectContext,
"SinistresPSs"
,
new
List<
string
>() {
"Agent"
});
We take the string in yellow and look for such a query property on the ObjectContext with reflection. Then we append different clauses such as Where, OrderBy, Include, Skip and Take to the query property that we found.
You are correctly passing in the ObjectContext. Even if there was a ctor accepting a DbContext, we do exactly the same -- obtain the ObjectContext by using the IObjectContextAdapter adapter.
Regards,
Rossen Hristov
Telerik
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
As i said previousely : we are forced to use "Sinistres" (top level) as entitySetName because the SinistrePS class don't have it own entityset on the model.
In EF5, you don't have an EntitySetName for derived entity so if I use your code I have this message :
Unable to resolve "SinistrePSs" in the current scope or context. Make sure that all referenced variables are in scope, that required schemas are loaded, and that namespaces are referenced correctly. Close simple identifier, row 1, column 1.
You can see the model properties of table SinistrePS in the attached file
http://msdn.microsoft.com/en-us/library/gg671236(v=vs.103).aspx
This will perhaps less work than adapt the code completely for DbContext ?
(just an idea, never used it)
Can you send me a very little dummy sample project with you exact setup. I want to try several hacks to see whether something can be done about this scenario.
The problem with Microsoft is that they started releasing EF out-of-band (independently from the .NET Framework). Each version they introduce breaking changes (like in the latest EF 6 RC alpha). You can imagine what this means for us as a component vendor.
Once upon a time there was only ObjectContext and EF was part of the .NET Framework. We were able to build and test our component against something which is part of the standard .NET framework and does not change overnight. Suddenly, NuGet came along and MS started releasing all kinds of stuff like EF and WCF Data Service out-of-band from the .NET Framework. Now it is very hard for us to target all of their versions. Some customers use ObjectContext, other with EF 5 and higher use DbContext and the mess is complete. Furthermore, we have to build our assemblies against one of their versions -- which should we choose? It is really complicated.
That is why when DbContext came about, we introduced this little hack for .NET Framework 4.5 only. We added a new property to RadEntityFrameworkDataSource called DbContext for the .NET 45 build only. If the developer supplies a DbContext, we simply obtain its ObjectContext by doing the same trick as you -- calling IObjectContextAdapater and then all the code remains the same because it is working with the legacy ObjectContext instance.
Here is the method of QECV that takes care of the Include clauses:
private
static
ObjectQuery<T> AppendRelatedObjects(ObjectQuery<T> original, IEnumerable<
string
> relatedObjectsToInclude)
{
var result = original;
foreach
(var relatedObject
in
relatedObjectsToInclude)
{
result = result.Include(relatedObject);
}
return
result;
}
The ObjectQuery<T> parameter that you see is taken by calling:
objectContext.CreateQuery<T>(entitySetName);
which in your case is objectContext.CreateQuery<T>("Sinistres");
If I make this method AppendRelatedObject protected virtual, do you think you can override it in such a manner as to include the correct related objects?
For the time being we do not plan on officially supporting DbContext in all its glory, since Entity Framework is no longer part of the .NET Framework and we can't really target a platform which is delivered through NuGet and has breaking changes in half of its nightly builds (read: this is a nightmare for a control vendor like us which has to be build and test everything half an year before an official release, which was the case with each new version of the NET Framework).
By the way, isn't there any way to tell EF to skip lazy loading for certain properties, i.e. to tell EF directly to load the related Agents of each SinistrePS? In other way -- solve this problem at the EF level instead of trying to append an Include through our class, which is obviously not suited for the new DbContext paradigm. I think that if your collection property is not virtual if doing code-first approach, it will not be lazy loaded since EF will not be able to override it in a derived proxy and inject its lazy-loading logic. That is just an idea.
Regards, Rossen Hristov
Telerik
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
It seems to be nightmares
For now, I remove the Include on the view and the grid do the work with the lazyloading, but for X lines, we have X call to the database : so bad perf !
I will try your idea to override the method and I will create for you a small sample of my context
And if all of this doesn't work, I'll have to develop my own DataView :(
we keep in touch
thank you for your help
I upload a zip file witch contains a small sample project with the bdd script
here : http://speedy.sh/UpYMV/PocEfDataSource.zip (clik on the file name on the top of the page)
I work with sql server 10.50, telerik 2013Q1
I will try to override the datasource soon (we have your sources) and your help would be greatly appreciated
Thanks
I have another idea. We already have a general purpose "data view".
We have a class called Telerik.Windows.Data.QueryableCollectionView. It accepts IEnumerable in its constructor, but if it is an IQueryable it will directly do queries over it. So you can try something like this:
var query = this.dbContext.SinistrePSs.Include(s => s.Agents);
var qcv = new QueryableCollectionView(query);
Then bind both RadGridView and RadDataPager to this qcv instance. The pager will append Skip and Take's to your query, while the grid will append Where and OrderBy's when the user filters and sorts.
In other words, the grid and the pager will communicate with this QCV and it will translate their orders to LINQ queries on your original query which is this.dbContext.SinistrePSs.Include(s => s.Agents)
It is like doing this: this.radGridView.ItemsSource = this.dbContext.SinistrePSs.Include(s => s.Agents);
but since you need a pager, you place this QCV in the middle so you can bind both the grid and the pager to it.
Alternatively, you can do the following:
this.radGridView.ItemsSource = this.dbContext.SinistrePSs.Include(s => s.Agents);
this.radDataPager.Source = this.radGridView.Items;
And the pager will page the grid which will reach your DbContext eventually.
Can you please try these two approaches and let me know?
Rossen Hristov
Telerik
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
It seems to work fine :) here my code :
var query = _bdd.SinistrePSs.Include(
"Agent"
);
this
.SinistreDataView =
new
QueryableCollectionView(query);
this
.SinistreDataView.SortDescriptors.Add(
new
SortDescriptor() { Member =
"Reference"
, SortDirection = ListSortDirection.Descending });
<tlk:RadGridView AutoGenerateColumns=
"False"
GroupRenderMode=
"Flat"
ItemsSource=
"{Binding SinistreDataView}"
IsSynchronizedWithCurrentItem=
"False"
SelectedItem=
"{Binding SinistreCourant}"
IsReadOnly=
"True"
ValidatesOnDataErrors=
"None"
>
<tlk:RadDataPager Grid.Row=
"1"
PageSize=
"20"
Source=
"{Binding SinistreDataView}"
DisplayMode=
"FirstLastPreviousNextNumeric, Text"
IsTotalItemCountFixed=
"True"
/>
I don't understand one thing : on my small poc : the load query is launch 2 times :
- after instanciate of QCV
- when the grid is show
But in my reality application, just one 1 time after instanciate ... Fortunately that is not the opposite !!
So work done ! Thanks a lot Rossen
Do you have seen my other post here : http://www.telerik.com/community/forums/wpf/autocompletebox/autocomplete-and-database-query.aspx ?
Bye