I am using EF4 with WPF. I am databinding to the DataGrid in a Master-Detail style. Think of the Northwind Customers -> Orders -> OrderDetails.
What I am finding is that when I use POCO objects, the Orders and OrderDetails grids are read-only. If I revert to using the designer generated entities they become editable.
The binding XAML looks like this:
<Window.Resources>
<CollectionViewSource x:Key="CustomersViewSource" d:DesignSource="{d:DesignInstance my:Customer, CreateList=True}" />
<CollectionViewSource x:Key="CustomersOrdersViewSource" Source="{Binding Path=Orders, Source={StaticResource CustomersViewSource}}" />
</Window.Resources>
<Grid DataContext="{StaticResource CustomersViewSource}">
<DataGrid ItemsSource="{Binding}" >
<DataGrid ItemsSource="{Binding Source={StaticResource CustomersOrdersViewSource}}" >
(I've removed attributes not relevant to databinding, of course.)
Then there's the standard form load event to bind the context instance:
Dim NorthwindEntities As BindTest.NorthwindEntities = New BindTest.NorthwindEntities()
Dim CustomersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersViewSource"), System.Windows.Data.CollectionViewSource)
CustomersViewSource.Source = NorthwindEntities.Customers
The grids populate, but the second is readonly if I'm using my POCO objects, editable if they are the standard EF generated objects.
The key seems to be in the navigation properties of the entities. My POCO objects use:
Public Overridable Property Orders() As ICollection(Of Order)
Get
If _Orders Is Nothing Then _Orders = New HashSet(Of Order)
Return _Orders
End Get
Set(ByVal value As ICollection(Of Order))
_Orders = value
End Set
End Property
Whereas the EF objects are much more complicated:
<XmlIgnoreAttribute()>
<SoapIgnoreAttribute()>
<DataMemberAttribute()>
<EdmRelationshipNavigationPropertyAttribute("NorthwindModel", "FK_Order_Details_Orders", "Orders")>
Public Property Order() As Order
Get
Return CType(Me, IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of Order)("NorthwindModel.FK_Order_Details_Orders", "Orders").Value
End Get
Set
CType(Me, IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of Order)("NorthwindModel.FK_Order_Details_Orders", "Orders").Value = value
End Set
End Property
For the lack of some better wording, there seems to be some magic in either the attributes for the EntityCollection type. ICollection isn't a readonly interface and a HashSet isn't readonly either.
Any ideas about how to get POCO to work here or am I stuck with EF derived objects? (Makes unit testing difficult.)
Thanks.
The problem is likely that the Orders and OrderDetails collections are of type ICollection<T> / HashSet<T> in your POCO example. The WPF datagrid internally does not work with the collection directly but rather with an associated "collection view". When you bind the collection to the DataGrid the WPF binding engine creates this internal collection view based on the type of the collection.
If your collection implements only IEnumerable or only ICollection the type of the created collection view is CollectionView, a class which does not implement IEditableCollectionView. That's the reason why you can't edit the DataGrid when you bind a HashSet to it.
The DataGrid needs a collection view which implements IEditableCollectionView to allow editing. This is for example the ListCollectionView (which also derives from CollectionView). WPF creates this type of collection view if your source collection implements the IList interface.
So, to fix the problem you should change the type of the Orders property of your POCO to IList:
Public Overridable Property Orders() As IList(Of Order)
Get
If _Orders Is Nothing Then _Orders = New List(Of Order)
Return _Orders
End Get
Set(ByVal value As IList(Of Order))
_Orders = value
End Set
End Property
Edit
According to #Allon Guralnek's comment below it is necessary to implement the non-generic IList interface to get an editable DataGrid. This is the case for List(Of T), therefore the code above will still work. Other implementations which only implement the generic IList(Of T) but not the non-generic IList won't make the DataGrid editable.
Related
I am working on an asp.net mvc core 2.0 project and entityframework.
I am working with viewmodel concept.
This mean i do not pass entities instances directly to my view, i pass a viewmodel instance which only contains what the view needs.
in one case, i need to pass the entire entity fields plus other informations.
What i've done is a derivated my ViewModel class from my Entity Class. and i add the extra fields:
public MyViewModel: Person
{
// I will automaticly have Person fields in MyViewModel
public bool IsSelected {get;set;}
public String otherinformation {get;set;}
...
}
I am fed up with assigning each fields of my viewmodel from my entity instance.
myviewmodelinstance.field1 = myentity.field1;
myviewmodelinstance.field2 = myentity.field2;
myviewmodelinstance.field3 = myentity.field3;
myviewmodelinstance.IsSelected = false;
...
And i need to do the inverse operation in the postback.
Is there a way to "copy" or "clone" every fields, like this:
myentity.CopyTo(myviewmodelinstance);
myviewmodelinstance.IsSelected = false;
Thanks
You could use the AutoMapper to achieve it. It is the ideal way to perform it. Another option is that you can serialize and deseriliaze the object as json.
var myviewmodelinstance = JsonConvert.DeserializeObject<MyViewModel>(JsonConvert.SerializeObject(myentity));
Note : The code I provided required Json.Net
ASP.NET MVC2 has strong support for using attributes on entities (validation, and extending Html helper class and more).
If I generated my Model from the Database using VS2010 EF4 Entity Data Model (edmx and it's cs class), And I want to add attributes
on some of the entities. what would be the best practice ? how should I cope with updating the model (adding more fields / tables to the database and merging them into the edmx) - will it keep my attributes or generate a new cs file erasing everything ?
(Manual changes to this file may cause
unexpected behavior in your
application.)
(Manual changes to this
file will be overwritten if the code
is regenerated.)
Generally you'd create what is called partial classes to extend your auto-generated objects.
Adding Attributes to Generated Classes
With the "buddy class" concept, linked above, and data annotations I use this extention method. I forget where I got it, so kudos to the original author.
We use it like
List<ValidationResult> errorList = new List<ValidationResult>();
bool bValid = client.IsValid<Client, ClientMetadata>(ref errorList, false);
public static bool IsValid<T, U>(this T obj, ref List<ValidationResult> errors, bool validateAllProperties = true) where T : IValidatableObject
{
//If metadata class type has been passed in that's different from the class to be validated, register the association
if (typeof(T) != typeof(U))
{
TypeDescriptor.AddProviderTransparent(new AssociatedMetadataTypeTypeDescriptionProvider(typeof(T), typeof(U)), typeof(T));
}
var validationContext = new ValidationContext(obj, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(obj, validationContext, validationResults, validateAllProperties);
errors = validationResults;
if (validationResults.Count > 0)
return false;
else
return true;
}
We use partial classes, but if you need them persisted and handled by EF, the "Update Model from Database" option is your best friend.
In my MVC2 AdminArea I'd like to create an overview table for each of my domain models.
I am using DataAnnotations like the following for the properties of those domain model objects:
[DisplayName("MyPropertyName")]
public string Name { get; set; }
Now my question is: How can I access the DisplayName Attribute if my view receives a collection of my domain models? I need this to build the table headers which are defined outside of the usual
<% foreach (var item in Model) { %>
loop. Inside this loop I can write
<%: Html.LabelFor(c => item.Name) %>
but is there any way to access this information using the collection of items instead of a concrete instance?
Thanks in advance!
There is a ModelMetaData class that has a static method called FromLambdaExpression. If you call it and pass in your property, along with your ViewData, it will return an instance of ModelMetaData. That class has a DisplayName property that should give you what you need. You can also get other meta data information from this object.
For example, you can create an empty ViewDataDictionary object to get this information. It can be empty because the ModelMetaData doesn't actually use the instance, it just needs the generic class to define the type being used.
//This would typically be just your view model data.
ViewDataDictionary<IEnumerable<Person>> data = new ViewDataDictionary<IEnumerable<Person>>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.First().Name, data);
string displayName = result.DisplayName;
The First() method call doesn't break even if you have no actual Person object because the lambda is simply trying to find the property you want the meta data about. Similarly, you could d this for a single Person object:
//This would typically be just your view model data.
ViewDataDictionary<Person> data = new ViewDataDictionary<Person>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.Name, data);
You could clean this up significantly with a helper or extension method, but this should put you on the right path.
Alright, I followed sgriffinusa's advise (thanks again!) and created a strongly typed HtmlHelper:
public static MvcHtmlString MetaDisplayName<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
return MvcHtmlString.Create(metadata.GetDisplayName());
}
Of course TModel still is a collection of domain models like stated in my inital question but we can call the helper in the view like this:
<%: Html.MetaDisplayName(p => p.First().Name) %>
Basically I want to use a dynamic data website to maintain data in an EF4 model where the entities are in their own assembly. Model and context are in another assembly.
I tried this Entity Framework 4 + Self-Tracking Entities + ASP.NET Dynamic Data = Error
but get an "ambiguous match" error from reflection:
System.Reflection.AmbiguousMatchException was unhandled by user code
Message=Ambiguous match found.
Source=mscorlib
StackTrace:
at System.RuntimeType.GetPropertyImpl(String name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
at System.Type.GetProperty(String name)
at System.Web.DynamicData.ModelProviders.EFTableProvider..ctor(EFDataModelProvider dataModel, EntitySet entitySet, EntityType entityType, Type entityClrType, Type parentEntityClrType, Type rootEntityClrType, String name)
at System.Web.DynamicData.ModelProviders.EFDataModelProvider.CreateTableProvider(EntitySet entitySet, EntityType entityType)
at System.Web.DynamicData.ModelProviders.EFDataModelProvider..ctor(Object contextInstance, Func1 contextFactory)
at System.Web.DynamicData.ModelProviders.SchemaCreator.CreateDataModel(Object contextInstance, Func1 contextFactory)
at System.Web.DynamicData.MetaModel.RegisterContext(Func`1 contextFactory, ContextConfiguration configuration)
at WebApplication1.Global.RegisterRoutes(RouteCollection routes) in C:\dev\Puffin\Puffin.Prototype.Web\Global.asax.cs:line 42
at WebApplication1.Global.Application_Start(Object sender, EventArgs e) in C:\dev\Puffin\Puffin.Prototype.Web\Global.asax.cs:line 78
InnerException:
I came across a similar problem to this recently. It had to do with inheritance in my model. I had a Resource entity that had derived types of Person, Equipment, etc. and in those I had overridden a couple properties, but by mistake gave them different signatures. I'll describe my scenario and hopefully it will help.
To be able to debug deep enough into the framework, and see all the variable values, you will have to disable optimizations:
Link
I was seeing the Ambiguous Match error when registering the Context in Global.asax as you were:
public static void RegisterRoutes(RouteCollection routes)
{
// IMPORTANT: DATA MODEL REGISTRATION
// Uncomment this line to register an ADO.NET Entity Framework model for ASP.NET Dynamic Data.
// Set ScaffoldAllTables = true only if you are sure that you want all tables in the
// data model to support a scaffold (i.e. templates) view. To control scaffolding for
// individual tables, create a partial class for the table and apply the
// [ScaffoldTable(true)] attribute to the partial class.
// Note: Make sure that you change "YourDataContextType" to the name of the data context
// class in your application.
DefaultModel.RegisterContext(typeof(EntityModelContainer), new ContextConfiguration() { ScaffoldAllTables = true });
Stepping into the RegisterContext method, I got to System.Web.DynamicData.ModelProviders.EFDataModelProvider there is section of code that loads all the Entities in the model by traversing the inheritance hierarchy in the constuctor for EFDataModelProvider.
while (objectStack.Any()) {
EntityType entityType = objectStack.Pop();
if (entityType != null) {
// Update the entity set when we are at another root type (a type without a base type).
if (entityType.BaseType == null) {
currentEntitySet = entitySetLookup[entityType];
}
var table = CreateTableProvider(currentEntitySet, entityType);
tables.Add(table);
}
foreach (EntityType derivedEntityType in derivedTypesLookup[entityType]) {
// Push the derived entity types on the stack
objectStack.Push(derivedEntityType);
}
}
I put a breakpoint in here and was able to see that Ambiguous Match was occurring for me when calling CreateTableProvider on my Equipment entity (which was derived from Resource).
Looking back at the Stack Trace from the original exception (which I should have done in the first place!) I put a breakpoint in the constructor for System.Web.DynamicData.ModelProviders.EFTableProvider.IsPublicProperty and watched to see which property/method/whatever was causing the ambiguous match -- for me this ended up being a navigation property called Resources (Resources are themselves a hierarchy) that I had overridden in Equipment.
private static bool IsPublicProperty(Type entityClrType, string propertyName) {
var property = entityClrType.GetProperty(propertyName);
return property != null && property.GetGetMethod() != null;
}
In the partial class for Equipment, I had:
public partial class Equipment
{
public new IEnumerable<Resource> Resources
{
but in the parent class, Resource, Resources was defined as:
public virtual ICollection<Resource> Resources
{
When these Properties are being loaded by the .GetProperty(propertyName) in IsPublicProperty, they have the same name but different signatures (because I had given them different return type by mistake) so it isn't clear which shoudl be loaded based on name alone. I corrected my mistake and made Resources in my Equipment class return an ICollection, and boom -- no more ambiguous match.
Not sure if this will help or not, but if you step through in a similar way you should be able to find exactly what is causing the ambiguous match.
I have an object that has been populated with the contents of four different related entities. However i have another entity in which i cannot include as part of the query due to it not being related in the navigation properites directly to the IQueryable table i am pulling. The entity i am trying to include is related to one of the four different entities that have been included successfully.
Is there a way to include(during db hit or afterwards) this entity as part of the overall object i am creating?
Here is an example of what my calls look like to build the CARTITEM object:
public List<CARTITEM> ListCartItem(Guid cartId)
{
//Create the Entity object
List<CARTITEM> itemInfo = null;
using (Entities webStoreContext = new Entities())
{
//Invoke the query
itemInfo = WebStoreDelegates.selectCartItems.Invoke(webStoreContext).ByCartID(cartId).ToList();
}
//Return the result set
return itemInfo;
}
here is the selectCartItems filter(Where i would normally do the includes):
public static Func<Entities, IQueryable<CARTITEM>> selectCartItems =
CompiledQuery.Compile<Entities, IQueryable<CARTITEM>>(
(cart) => from c in cart.CARTITEM.Include("ITEM").Include("SHIPPINGOPTION").Include("RELATEDITEM").Include("PROMOTION")
select c);
from this i have my CARTITEM object. Problem is i want to include the PROMOTIONTYPE table in this object, but since the CARTIEM entity doesn't have a navigation property directly to the PROMOTIONTYPE table i get an error.
Let me know if you need any more clarification.
Thanks,
Billy
You can use join and if it is the same database and server it should generate the join in SQL and do it all in one call...
LinqToEnties join example