C# – What’s the best way to expose a Model object in a ViewModel

cdesign-patternsentity-frameworkmvvmwpf

In a WPF MVVM application, I exposed my model object into my viewModel by creating an instance of Model class (which cause dependency) into ViewModel. Instead of creating separate VM properties, I wrap the Model properties inside my ViewModel Property.

My model is just an entity framework generated proxy class:

public partial class TblProduct
{
    public TblProduct()
    {
        this.TblPurchaseDetails = new HashSet<TblPurchaseDetail>();
        this.TblPurchaseOrderDetails = new HashSet<TblPurchaseOrderDetail>();
        this.TblSalesInvoiceDetails = new HashSet<TblSalesInvoiceDetail>();
        this.TblSalesOrderDetails = new HashSet<TblSalesOrderDetail>();
    }

    public int ProductId { get; set; }
    public string ProductCode { get; set; }
    public string ProductName { get; set; }
    public int CategoryId { get; set; }
    public string Color { get; set; }
    public Nullable<decimal> PurchaseRate { get; set; }
    public Nullable<decimal> SalesRate { get; set; }
    public string ImagePath { get; set; }
    public bool IsActive { get; set; }

    public virtual TblCompany TblCompany { get; set; }
    public virtual TblProductCategory TblProductCategory { get; set; }
    public virtual TblUser TblUser { get; set; }
    public virtual ICollection<TblPurchaseDetail> TblPurchaseDetails { get; set; }
    public virtual ICollection<TblPurchaseOrderDetail> TblPurchaseOrderDetails { get; set; }
    public virtual ICollection<TblSalesInvoiceDetail> TblSalesInvoiceDetails { get; set; }
    public virtual ICollection<TblSalesOrderDetail> TblSalesOrderDetails { get; set; }
}

Here is my ViewModel:

public class ProductViewModel : WorkspaceViewModel
{
    #region Constructor
    public ProductViewModel()
    {
        StartApp();
    }

    #endregion //Constructor

    #region Properties

    private IProductDataService _dataService;
    public IProductDataService DataService
    {
        get
        {
            if (_dataService == null)
            {
                if (IsInDesignMode)
                {
                    _dataService = new ProductDataServiceMock();
                }
                else
                {
                    _dataService = new ProductDataService();
                }
            }
            return _dataService;
        }

    }

    //Get and set Model object
    private TblProduct _product;
    public TblProduct Product
    {
        get
        {
            return _product ?? (_product = new TblProduct());
        }
        set
        {
            _product = value;
        }
    }

    #region Public Properties

    public int ProductId
    {
        get
        {
            return Product.ProductId;
        }
        set
        {
            if (Product.ProductId == value)
            {
                return;
            }

            Product.ProductId = value;
            RaisePropertyChanged("ProductId");
        }
    }

    public string ProductName
    {
        get
        {
            return Product.ProductName;
        }
        set
        {
            if (Product.ProductName == value)
            {
                return;
            }

            Product.ProductName = value;
            RaisePropertyChanged(() => ProductName);
        }
    }

    private ObservableCollection<TblProduct> _productRecords;
    public ObservableCollection<TblProduct> ProductRecords
    {
        get { return _productRecords; }
        set
        {
            _productRecords = value;
            RaisePropertyChanged("ProductRecords");
        }
    }

    //Selected Product
    private TblProduct _selectedProduct;
    public TblProduct SelectedProduct
    {
        get
        {
            return _selectedProduct;
        }
        set
        {
            _selectedProduct = value;
            if (_selectedProduct != null)
            {
                this.ProductId = _selectedProduct.ProductId;
                this.ProductCode = _selectedProduct.ProductCode;
            }
            RaisePropertyChanged("SelectedProduct");
        }
    }

    #endregion  //Public Properties

    #endregion  // Properties

    #region Commands

    private ICommand _newCommand;
    public ICommand NewCommand
    {
        get
        {
            if (_newCommand == null)
            {
                _newCommand = new RelayCommand(() => ResetAll());
            }
            return _newCommand;
        }
    }

    private ICommand _saveCommand;
    public ICommand SaveCommand
    {
        get
        {
            if (_saveCommand == null)
            {
                _saveCommand = new RelayCommand(() => Save());
            }
            return _saveCommand;
        }
    }

    private ICommand _deleteCommand;
    public ICommand DeleteCommand
    {
        get
        {
            if (_deleteCommand == null)
            {
                _deleteCommand = new RelayCommand(() => Delete());
            }
            return _deleteCommand;
        }
    }

    #endregion //Commands

    #region Methods

    private void StartApp()
    {
        LoadProductCollection();
    }

    private void LoadProductCollection()
    {
        var q = DataService.GetAllProducts();
        this.ProductRecords = new ObservableCollection<TblProduct>(q);
    }


    private void Save()
    {
        if (SelectedOperateMode == OperateModeEnum.OperateMode.New)
        {
              //Pass the Model object into Dataservice for save
              DataService.SaveProduct(this.Product);
        }
        else if (SelectedOperateMode == OperateModeEnum.OperateMode.Edit)
        {
             //Pass the Model object into Dataservice for Update
              DataService.UpdateProduct(this.Product);
        }
        ResetAll();
        LoadProductCollection();
    }


    #endregion  //Methods
}

Here is my Service class:

class ProductDataService:IProductDataService
{
    /// <summary>
    /// Context object of Entity Framework model
    /// </summary>
    private MaizeEntities Context { get; set; }

    public ProductDataService()
    {
        Context = new MaizeEntities();
    }

    public IEnumerable<TblProduct> GetAllProducts()
    {
        using(var context=new R_MaizeEntities())
        {
           var q = from p in context.TblProducts
                   where p.IsDel == false
                   select p;

            return new ObservableCollection<TblProduct>(q);
        }
    }

    public void SaveProduct(TblProduct _product)
    {
        using(var context=new R_MaizeEntities())
        {
            _product.LastModUserId = GlobalObjects.LoggedUserID;
            _product.LastModDttm = DateTime.Now;
            _product.CompanyId = GlobalObjects.CompanyID;
            context.TblProducts.Add(_product);

            context.SaveChanges();
        }
    }

    public void UpdateProduct(TblProduct _product)
    {
        using (var context = new R_MaizeEntities())
        {
            context.TblProducts.Attach(_product);

            context.Entry(_product).State = EntityState.Modified;
            _product.LastModUserId = GlobalObjects.LoggedUserID;
            _product.LastModDttm = DateTime.Now;
            _product.CompanyId = GlobalObjects.CompanyID;
            context.SaveChanges();
        }
    }

    public void DeleteProduct(int _productId)
    {
        using (var context = new R_MaizeEntities())
        {
            var product = (from c in context.TblProducts
                    where c.ProductId == _productId
                    select c).First();

            product.LastModUserId = GlobalObjects.LoggedUserID;
            product.LastModDttm = DateTime.Now;
            product.IsDel = true;
            context.SaveChanges();
        }
    }


}

I exposed my model object in my viewModel by creating an instance of it using new keyword, also I instantiated my DataService class in VM. I know this will cause a strong dependency.

So:

  1. What's the best way to expose a Model object in a ViewModel?
  2. What's the best way to use DataService in VM?

Best Answer

As a general rule, it is not a good idea to expose a Model object in a ViewModel object. The form of the ViewModel class should be determined by the needs of the View that will use it. The form of the Model class should be determined by the concerns of your data model.

In my experience with Entity Framework and the MVVM pattern, I have found it best to transform the EF data as I populate my ViewModel object. The ViewModel object will then contain the data from the data model but in a different form. If you need to allow the user to change data, then you often want to have methods that transform the modified ViewModel data back into Model data so that you can persist it to your data store.

Related Topic