ObservableCollection of generic ViewModel class - mvvm

I'm creating MVVM application and in Model section I have simple base abstract class Animal and class Dog which derives from it:
public abstract class Animal
{
public int Age { get; set; }
}
public class Dog : Animal
{
public string Name { get; set; }
}
ViewModel section containts UI-friendly VM classes of them:
public abstract class AnimalVM<T> : ViewModelBase where T : Animal
{
protected readonly T animal;
public int Age
{
get { return animal.Age; }
set
{
animal.Age = value;
OnPropertyChanged("Age");
}
}
protected AnimalVM(T animal)
{
this.animal = animal;
}
}
public class DogVM : AnimalVM<Dog>
{
public string Name
{
get { return animal.Name; }
set
{
animal.Name = value;
OnPropertyChanged("Name");
}
}
public DogVM(Dog dog) : base(dog) { }
}
Suppose I have another VM class which contains ObservableCollection<AnimalVM>. The problem is how to create that kind of property which allow me to store there different types of Animal? I want to achieve something like this:
public class AnimalListVM : ViewModelBase
{
// here is a problem, because AnimalVM<Animal> isn't compatible with DogVM
readonly ObservableCollection<AnimalVM<Animal>> animals;
public ObservableCollection<AnimalVM<Animal>> Animals
{
get { return animals; }
}
public AnimalListVM(IList<Animal> animals)
{
//this.animals = ...
}
}
I can change ObservableCollection<AnimalVM<Animal>> property to ICollection property and then create list of AnimalVM using some dictionary Animal -> AnimalVM wrapper and Activator.CreateInstance() - it works but when I try to extend AnimalListVM adding another property SelectedAnimal which will be binded in sample View to e.g. DataGrid control I have another problem with type of that kind of property SelectedItem. It can't be of type AnimalVM<Animal> because when I have DogVM object in my Collection it won't fit with this and throw an exception.
Everything will be clear if only I had non-generic AnimalVM but I don't want to copy and paste similar properties in every DogVM, CatVM, BirdVM class derived from AnimalVM. How can I achieve this?

Ok, I've found a solution and of course it's very simple: just create another, non-generic abstract base class for your generic abstract base class and then derive your generic class from that newly created non-generic class. In that case you also must rewrite properties from non-generic class to generic class (to be more specific override them), but you do this only once, so you don't have to copy and paste the same code in every generic derived ViewModel (in our example in every DogVM, CatVM, BirdVM, etc.).

Related

Entity Framework Set with Generic Class

Ok, I might be punching above my pay grade here, but I'm trying to create a generic CRUD routine for and EF project. I've got most of it working but I'm flailing around on one point.
Normally you do something like this to add an entity through a context-
DBContext.MyClass.Add( p ); // p = instance of MyClass
That works fine, but since in a global method to handle all adds regardless of what class they are I'm passing in a Model as an object it would look more like this-
DBContext<whateverobject>.Add(whateverobject); // my objects is an object param passed into the method
I've tried doing a bunch of typeofs and there where T : class stuff but I'm having no luck. Any pointing in the right direction would help me out.
I'm using EF Core 2 so my options might also be more limited than EF 6.
Thanks.
The method you're looking for is DbContext's Set<T>()
Your generic repository for your generic CRUD would look something like this:
public class Repo<T> where T: class
{
private readonly DbSet<T> _set;
public Repo(DbContext dbContext)
{
_set = dbContext.Set<T>();
}
public void Add(T entity) => _set.Add(entity);
}
This example includes a maybe unusual thing:
where T: class: we have to specify that T has to be a reference type because DbSet<T> expects T to be a reference type
For generic querying you might want to use extension methods.
In order to implement a ById method you'd have to specify that the type T must have an Id property using an interface. That would look something like this:
public interface IEntity
{
int Id { get; set; }
}
public class User : IEntity
{
public int Id { get; set; }
}
public static class DbSetExtensions
{
public static T ById<T>(this DbSet<T> dbSet, int id) where T: class =>
dbSet.FirstOrDefault(entity => entity.Id == id);
}

Map property using OwnsOne method in EF Core 2.0

I'm trying to migrate EF 6 project to EF Core 2.0.
I would like to streess out that I am not allowed to change anything in database structure - must be exactly the same as for EF 6 project.
I have the foloowing entities:
abstract class Vehicle { ... }
abstract class Car : Vehicle
{
public Dimension Dimensions { get; set; }
}
class Audi : Car { ... }
class Mazda : Car { ... }
class Dimension
{
public double Width { get; set; }
public double Height { get; set; }
}
Table mappings as follows:
public VehicleMap(EntityTypeBuilder<Vehicle> entityBuilder)
{
entityBuilder.ToTable("Vehicles");
entityBuilder.HasKey(_ => _.Id);
entityBuilder.HasDiscriminator<string>("Type").HasValue<Truck>(nameof(Truck));
}
public CarMap(EntityTypeBuilder<Car> entityBuilder)
{
entityBuilder.HasDiscriminator<string>("Type")
.HasValue<Mazda>(nameof(Mazda))
.HasValue<Audi>(nameof(Audi));
**entityBuilder.OwnsOne(_ => _.Dimensions);**
}
The problem I have is about Dimensions property defined in Car abstract class. I would like to map it using OwnsOne method to have all its class properties defined in the same table.
I'm getting the following error:
Cannot use table 'Vehicles' for entity type 'Car.Dimensions#Dimension'
since it has a relationship to a derived entity type 'Car'. Either
point the relationship to the base type 'Vehicle' or map
'Car.Dimensions#Dimension' to a different table.
Any idea how to solve it in EF Core 2.0?
The Dimension known as ValueObject. The best practice for this type of objects is here.
To implement this objects in your context first you should define it in base table, since when you create database with this configuration you have just one table with the name of Vehicles and discriminator column with the name of Type.
As the error say:
Either point the relationship to the base type 'Vehicle'
we should define Dimension in base class.
abstract class Vehicle
{
public Dimension Dimensions { get; private set; }
}
Table mappings:
public VehicleMap(EntityTypeBuilder<Vehicle> entityBuilder)
{
entityBuilder.ToTable("Vehicles");
entityBuilder.HasKey(_ => _.Id);
entityBuilder.HasDiscriminator<string>("Type").HasValue<Truck>(nameof(Truck));
// Here
entityBuilder.OwnsOne(p => p.Dimensions);
}
I would recommend a base ConfigurationBuilder for your Car class. Than you can make a configuration class (http://www.entityframeworktutorial.net/code-first/move-configurations-to-seperate-class-in-code-first.aspx)
Than you can do something like this:
public class CarConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : class, Car
{
public virtual void Configure(EntityTypeBuilder<TBase> builder)
{
builder.OwnsOne(e => e.Dimensions);
}
}
After that you can use a configurationClass that derives from CarConfiguration foreach item that derives from car e.g.
public class AudiConfiguration : CarConfiguration<Audi>
{
public override void Configure(EntityTypeBuilder<Audi> builder)
{
base.Configure(builder); //call this to use configuration from Car
builder.....
}
}

Autofac registering all subtypes keyed according to a property

The following scenario:
public enum ChildType
{
Type1,
Type2,
Type3
}
public abstract class MyParentClass
{
public abstract ChildType Id { get; }
}
public class Child1 : MyParentClass
{
public override ChildType Id { get { return ChildType.Type1; } }
}
public class Child2 : MyParentClass
{
public override ChildType Id { get { return ChildType.Type2; } }
}
public class Child3 : MyParentClass
{
public override ChildType Id { get { return ChildType.Type3; } }
}
and i would like to use autofac to register all the subtypes using their id as a key, so something like:
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(type => type.IsSubclassOf(typeof(MyParentClass)))
.Keyed<MyParentClass>(c => c.Id)
.SingleInstance();
now obviously the above doesn't work, but is there some way to achieve that without registering each subclass separately? I want to then be able to access them by the enum, i.e. at runtime when i don't know what the value of the enum will be:
public static MyParentClass GetSubClassByEnum(ChildType id)
{
AutofacHostFactory.Container.ResolveKeyed<MyParentClass>(id);
}
Unfortunately you probably won't be able to get exactly this setup working because it's sort of a chicken/egg problem - you want to resolve an object based on information that won't be available... unless you resolve the object.
One way to get this working is to use attributes rather than properties. Attributes are available before the type is instantiated so you could store the info there and achieve the desired result.
Autofac has attribute metadata support that allows you to create custom attributes to provide this sort of information. You could create an attribute that gets inherited and only allows one instance per class. Apply it with the default value to your base class, then when you need to override apply a new attribute on the derived class.
There is plenty of documentation with examples on the Autofac doc site showing how to work with this.

Entity Framework code first Inheritance Issue

I have code first implementation for flowing hierarchy,
BaseContact{
Public int Id{get;set;}
public string Name{get;set;}
//..
}
Person:BaseContact{
public string Designation{get;set;}
//..
}
Company:BaseContact{
public int NumOfEmployees{get;set;}
//..
}
I want to identify person or company with by using only the Id value? Currently I am using reflection to identify whether it is a person or company. Is there any other way to identify it without doing too much?
Without seeing how you initialised your classes I'm going to assume you have a table per concrete type approach.
You can't do it just from the ID, as you don't know which table the ID belongs to. ID 2 in "Person" table is a different entity to ID 3 in "Company". The only practical way to identify only from an ID is using a Table per Hierarchy approach and inspecting the type descriptor.
Some good references
http://weblogs.asp.net/manavi/archive/2011/01/03/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-3-table-per-concrete-type-tpc-and-choosing-strategy-guidelines.aspx
http://weblogs.asp.net/manavi/archive/2010/12/24/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph.aspx
You can also use a simple is statement instead of reflection. Ie if (entity is Company)
In your BaseContact (assume it is an abstract class) add abstract property which will be implemented by other two classes.Use Enum to identify the property type as follows.
public enum MyType
{
Person,
Company,
};
public abstract class BaseContact{
public abstract MyType ContactType{get;}
}
public class Person:BaseContact
{
public override MyType ContactType
{
get
{
return MyType.Person;
}
}
}
public class Company:BaseContact
{
public override MyType ContactType
{
get
{
return MyType.Company;
}
}
}
Use your BaseContact repository to retrieve entities and use enum for type separation.

How to create an Entity Data Model for inherited generic types?

I have no clue how i can get an existing object structure based on the following classes (simplified) into a database using Entity Framework (EF is a constraint, i have to use it).
public abstract class WahWahProperty
{
public string Name { get; set; }
public abstract Type PropertyType { get; }
}
// ----------------
public class WahWahProperty<T> : WahWahProperty
{
public T Value { get; set; }
public override Type PropertyType
{
get { return typeof(T); }
}
}
// ----------------
public class WahWahContainer
{
public List<WahWahContainer> Children { get {...}; }
public List<WahWahContainer> Parents { get {...}; } // multiple "Parents" allowed
public List<WahWahProperty> Properties { get {...}; }
//... some more props here ...
}
Any ideas?
The EF doesn't support generic Entity types (which seems to be what you are doing).
Although we have made a change in EF 4.0 (not in Beta1) so you will be able to use a non-generic class derived from a generic class as an Entity.
Anyway hope this helps
Alex
Program Manager Entity Framework Team
Entity Framework Tips