I'm migrating from WinForms world to WPF with MVVM.
My base system works with POCO classes (NHibernate) and we use some DynamicProxy to map this POCO classes into some bindable so the ModelView can expose a proxy of POCO and save a lot of code:
public class OrderViewModel
{
public OrderPOCO Order { get; private set; }
}
public class OrderView
{
public OrderView()
{
DataContext = DynamicProxy(new OrderViewModel(new OrderPOCO()));
}
}
public class OrderPOCO
{
public virtual int Number { get; set; };
public virtual IList<OrderItemPOCO> Items { get; set; };
}
public class OrderItemPOCO
{
public virtual decimal Qty { get; set; }
public virtual decimal Price { get; set; }
public virtual decimal Amount { get; set; }
}
The collection of OrderItemPOCO is binded into a grid. The Amount is a calculated property that depends of some complex rules (I can't put it in the POCO as it's not a simple Amount = Qty * Price).
Sure I can expose in the ViewModel a custom OrderItemViewModel and a collection of OrderItemViewModel but I will need to recode my POCO classes. How I can code this kind of situation in MVVM without recode all my Model?
You are right, you need an OrderItemViewModel. But you don't have to rewrite you model classes, it will remain the same. What you need is something like this:
public class OrderViewModel
{
public OrderViewModel(OrderPOCO order)
{
Order = order;
Items = new ObservableCollection<OrderItemViewModel>(order.Items.Select(o => new OrderItemViewModel(o)).ToArray());
Items.CollectionChanged += OnItemsCollectionChanged;
}
public OrderPOCO Order { get; private set; }
public ObservableCollection<OrderItemViewModel> Items { get; private set; }
private void OnItemsCollectionChanged(object sender, CollectionChangedEventArgs e)
{
// Synchronize this.Items with order.Items
}
}
public class OrderItemViewModel
{
public OrderItemPOCO OrderItem { get; private set; }
}
public class OrderPOCO
{
public virtual int Number { get; set; };
public virtual IList<OrderItemPOCO> Items { get; set; };
}
public class OrderItemPOCO
{
public virtual decimal Qty { get; set; }
public virtual decimal Price { get; set; }
public virtual decimal Amount { get; set; }
}
Related
I am using EF Core and I tried to create a one-to-one relationship between three tables (Car, ElectricCar and PetrolCar)
public class Car
{
public int Id { get; set; }
public string RegistrationNumber { get; set; }
public ElectricCar Company { get; set; }
public PetrolCar Trust { get; set; }
}
public class ElectricCar
{
public int ElectricCarId { get; set; }
public double BatteryCapacityWattage{ get; set; }
public int CarId { get; set; }
public Car Car { get; set; }
}
public class PetrolCar
{
public int PetrolCarId { get; set; }
public double TankCapacity { get; set; }
public int CarId { get; set; }
public Car Car { get; set; }
}
public partial class CarDbContext : Microsoft.EntityFrameworkCore.DbContext
{
public CarDbContext()
{
}
public CarDbContext(DbContextOptions<CarDbContext> options)
: base(options)
{
}
public DbSet<ElectricCar> ElectricCar { get; set; }
public DbSet<Car> Car { get; set; }
public DbSet<PetrolCar> PetrolCar { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=DESKTOP-PC\\SQLLOCAL;Database=OneToOneEFCoreCar;Trusted_Connection=True;");
}
}
}
and the code that inserts the data:
CarDbContext context = new CarDbContext();
context.Car.Add(new Car
{
RegistrationNumber = "EL123",
Company = new ElectricCar() { BatteryCapacityWattage = 2000 }
});
context.Car.Add(new Car
{
RegistrationNumber = "PETR123",
Trust = new PetrolCar() { TankCapacity = 50 }
});
context.SaveChanges();
That works without any issue and creates the following data
When I go to the PetrolCar I insert a new row with CarId = 1 and it accepts it without giving any error although that CarId is used in the ElectricCar table as CarId.
Is there any way to restrict this?
If you're entirely set on keeping your object models / data structure the same as it is above then a unique constraint across the two tables isnt really natively achievable.
One possible in code solution (though its not particularly clean, so I would suggest restructuring your data over this, though that seems to be something you would like to avoid) is to override the SaveChanges method.
something along the lines of:
public override SaveChanges()
{
var petrolCars = ChangeTracker.Entries().Where(e is PetrolCar).ToList();
foreach(var pCar in petrolCars)
{
if(query the database for electric cars to see if car id exists)
{
do some sort of error processing and avoid saving;
}
}
base.SaveChanges();
}
it does mean creating a context class that inherits from the default context, though it adds a lot of flexibility in terms of doing something like this (obviously you would want to handle the other cases too of cars having the same id in the other direction)
How to include/populate a navigation property with custom(1-to-1) query in EF?
e.g.
public class Item {
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Id")]
public ItemCost LatestCost {get; set; }
}
public class ItemCost {
public int Id { get; set; }
public DateTime From { get; set; }
public DateTime? To { get; set; }
public decimal Cost { get; set; }
}
Goal is to populate the LatestCost property of the Item with it's latest cost from ItemCosts. How is this being accomplished with EF or what's your take on this?
Is it possible to do a custom query within .Include/.ThenInclude methods?
e.g.
.ThenInclude(a => { a.LatestCost = (from a _db.ItemCosts
where... select a).SingleOrDefault() })...
You could use a virtual get-only property. Your nav property should really be an ICollection<ItemCost>. In this example I'm assuming the Id property in the ItemCost class is the id of the related Item, but it's not clear. Tip: using nameof(property) instead of hard-coding the property name will allow the compiler to catch errors with the name if you were to change it for some reason. The [NotMapped] attribute tells Entity Framework to not try and map the property to a database field.
public class Item {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ItemCost> ItemCosts {get; set; }
[NotMapped]
public virtual ItemCost LatestCost
{
get
{
return ItemCosts.OrderByDescending(x => x.From).FirstOrDefault();
}
}
}
public class ItemCost {
public int Id { get; set; }
public DateTime From { get; set; }
public DateTime? To { get; set; }
public decimal Cost { get; set; }
[ForeignKey(nameof(Id))]
public virtual Item Item { get; set; }
}
I'm using the following technologies: WinForms, Entity Framework 4.4 (5.0 on .NET 4.0), DBContext
I have (what I think is) a very simple Master/Details form, that actually worked just fine before I upgraded from EF 4.0 to EF 4.4. Now, for some reason, the Details DataGridView simply doesn't populate with any data!
Here's my auto-generated schema code:
public partial class RoadMapping
{
public RoadMapping()
{
this.RoadCharacteristics = new HashSet<RoadCharacteristic>();
}
public int RoadMappingID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public virtual ICollection<RoadCharacteristic> RoadCharacteristics { get; set; }
}
public partial class RoadCharacteristic
{
public RoadCharacteristic()
{
}
public int RoadCharacteristicID { get; set; }
public int RoadMappingID { get; set; }
public string Value { get; set; }
public string Description { get; set; }
public virtual RoadMapping RoadMapping { get; set; }
}
Here's my code that was working with EF 4.0:
SATContext = new SafetyAssessmentToolEntities();
dataGridViewMappings.DataSource = bindingSourceMappings;
dataGridViewDetails.DataSource = bindingSourceDetails;
bindingSourceMappings.DataSource = SATContext.RoadMappings;
bindingSourceDetails.DataSource = bindingSourceMappings;
bindingSourceDetails.DataMember = "RoadCharacteristics";
Here's the code that isn't working with EF 4.4:
SATContext = new SafetyAssessmentToolEntities();
SATContext.RoadMappings.Load();
SATContext.RoadCharacteristics.Load();
dataGridViewMappings.DataSource = bindingSourceMappings;
dataGridViewDetails.DataSource = bindingSourceDetails;
bindingSourceMappings.DataSource = SATContext.RoadMappings.Local.ToBindingList();
bindingSourceDetails.DataSource = bindingSourceMappings;
bindingSourceDetails.DataMember = "RoadCharacteristics";
Please note that bindingSourceMappings and bindingSourceDetails are declared by the form designer.
I know there are a lot of more advanced and code-intensive ways to make this work, but I can't understand why this very simple way of doing it won't work anymore.
Any suggestions?
public partial class SafetyAssessmentToolEntities : DbContext
{
public SafetyAssessmentToolEntities()
: base("name=SafetyAssessmentToolEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<RoadCharacteristic> RoadCharacteristics { get; set; }
public DbSet<RoadMapping> RoadMappings { get; set; }
}
When I build my model to be returned by WCF RIA Services to silverlight, the list properties are not shown in the silverlight client.
Class:
public class Batch
{
[DataMember]
public DateTime Time { get; set; }
[DataMember]
public List<BasicInfoModel> Accepted { get; set; }
[DataMember]
public List<BasicInfoModel> UnAccepted { get; set; }
}
public class Batch
{
[Key]
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
}
When in the client, it seems the Lists are not generated, for example, this is my loaded callback event:
private void Callback(LoadOperation<Batch> loadOperation)
{
//there is no such property as `Accepted`
var acceptedList = loadOperation.Entities.FirstOrDefault().Accepted;
}
Am I doing something wrong?
I suppose, you've missed DataContract attribute, e.g.
[DataContract]
public class Batch
{
[DataMember]
public DateTime Time { get; set; }
[DataMember]
public List<BasicInfoModel> Accepted { get; set; }
[DataMember]
public List<BasicInfoModel> UnAccepted { get; set; }
}
http://msdn.microsoft.com/en-us/library/ms733127.aspx
I query data with NHibernate in the server side, then I create a WCF service which is the one that publishes these NHibernate objects, they are correctly serialized to Silverlight, I modify them in my application but when I send them back to the server they get serlialized again, and Generic Lists get converted to Array so I cannot modify them anymore in the server side...
this is my class definition
public class BIMenu
{
public virtual Guid ID { get; set; }
public virtual String DisplayName { get; set; }
public virtual String ProgramToCall { get; set; }
public virtual IList<BIMenu> Children { get; set; }
public virtual IList<BISecurityProfile> SecurityProfiles { get; set; }
public virtual Boolean IsApplication
{
get
{
if (Children.Count < 1 && ProgramToCall != null)
return true;
return false;
}
}
public virtual Boolean IsFolder
{
get
{
return !IsApplication;
}
}
public BIMenu()
{
Children = new List<BIMenu>();
SecurityProfiles = new List<BISecurityProfile>();
}
}
and this is my contract
[ServiceContract]
public interface IBISecurityService
{
[OperationContract]
BIMenu GetMenu(String Name);
[OperationContract]
void SaveMenu(BIOnline.Model.BIMenu Menu);
[OperationContract]
void DeleteMenu(BIOnline.Model.BIMenu Menu);
}
Is your BIMenu class marked [DataContract]? I would expect it to be:
[DataContract]
public class BIMenu
{
[DataMember]
public virtual Guid ID { get; set; }
[DataMember]
public virtual String DisplayName { get; set; }
[DataMember]
public virtual String ProgramToCall { get; set; }
[DataMember]
public virtual IList<BIMenu> Children { get; set; }
[DataMember]
public virtual IList<BISecurityProfile> SecurityProfiles { get; set; }
Also, if your IList<BIMenu> Children and IList<BISecurityProfile> SecurityProfiles properties are being set to instances of the Array type, then that is perfectly valid, since Array implements IList. If you want to keep them as actual List<> instances, then just define the properties as List<> instead of IList<>, like this:
// Defined as actual Lists, not IList interfaces.
[DataMember]
public virtual List<BIMenu> Children { get; set; }
[DataMember]
public virtual List<BISecurityProfile> SecurityProfiles { get; set; }