Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphism in Entity Framework

The concrete classes (BankAccount and CreditCard) are not visible on controller.

I'm stuck with this issue.

I'm using the example from this site:

http://weblogs.asp.net/manavi/archive/2010/12/28/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt.aspx

The view

The CreateUser:

If the CreditCard was selected it should be associated to the User class.

Create User

The diagram

Class Diagram

The code

UserController:

    [HttpPost]
    public ActionResult Create(User user)//The Watch above came from this user instance
    {
        if (ModelState.IsValid)
        {

            context.User.Add(user);
            context.SaveChanges();
            return RedirectToAction("Index");  
        }

        ViewBag.PossibleBillingDetail = context.BillingDetail;
        return View(user);
    }

User\_CreateOrEdit.cshtml:

enter image description here

User\Create.cshtml:

    @model TPTMVC.Models.User
    @using TPTMVC.Models;

<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.5.js" type="text/javascript"></script> 
<script type="text/javascript">

    $(document).ready(function () {
        $('.divbank').hide();
        $('input[type=radio]').live('change', function () { updateweather(); });
    });

    function updateweather() {
        //alert();
        if ($('input[type=radio]:checked').val() == 'Bank') {
            $('.divcard').fadeOut(1000);
            $('.divcard').hide();
            $('.divbank').fadeIn(1000);
        }
        else {
            $('.divbank').fadeOut(1000);
            $('.divbank').hide();
            $('.divcard').fadeIn(1000);
            }

    }
        </script>
    <div id="json"></div>

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<fieldset>
    <legend>User</legend>

        @Html.Partial("_CreateOrEdit", Model)

        <div ='none' class="divcard">
            <div class="editor-label">
                @Html.LabelFor(model => ((CreditCard)model.billingDetail).ExpiryMonth)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => ((CreditCard)model.billingDetail).ExpiryMonth)
                @Html.ValidationMessageFor(model => ((CreditCard)model.billingDetail).ExpiryMonth)
            </div>

             <div class="editor-label">
                @Html.LabelFor(model => ((CreditCard)model.billingDetail).ExpiryYear)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => ((CreditCard)model.billingDetail).ExpiryYear)
                @Html.ValidationMessageFor(model => ((CreditCard)model.billingDetail).ExpiryYear)
            </div> 
        </div>

        <div='none' class="divbank">
            <div class="editor-label">
                @Html.LabelFor(model => ((BankAccount)model.billingDetail).BankName)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => ((BankAccount)model.billingDetail).BankName)
                @Html.ValidationMessageFor(model => ((BankAccount)model.billingDetail).BankName)
            </div>

             <div class="editor-label">
                @Html.LabelFor(model => ((BankAccount)model.billingDetail).Swift)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => ((BankAccount)model.billingDetail).Swift)
                @Html.ValidationMessageFor(model => ((BankAccount)model.billingDetail).Swift)
            </div> 
        </div>  
    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Classes code:

namespace TPTMVC.Models{
public class BillingDetail
{
    [Key]
    [ForeignKey("user")]
    public int UserID { get; set; }
    public string Owner { get; set; }
    public string Number { get; set; }
    public virtual User user { get; set; }
}}

namespace TPTMVC.Models{
public class User
{
    public int UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public virtual BillingDetail billingDetail { get; set; }
}}
namespace TPTMVC.Models{
    [Table("BankAccounts")]
    public class BankAccount:BillingDetail
    {
        public string BankName { get; set; }
        public string Swift { get; set; }
    }}
namespace TPTMVC.Models{
[Table("CreditCards")]
public class CreditCard:BillingDetail
{
    public int CardType { get; set; }
    public string ExpiryMonth { get; set; }
    public string ExpiryYear { get; set; }
}}

The problem

When I click the create button, I get this result:

Watch

I selected a CreditCard but the result was BillingDetail. I tried to make a casting but I got a error, as you can see.

:(

Why only BillingDetail appear on UserController?

My first solution

[HttpPost]
        public ActionResult Create(User user, CreditCard card, BankAccount bank, String Radio)
        {
            //String teste=formCollection["Radio"];
            if (ModelState.IsValid)
            {
                switch (Radio)
                {
                    case "CredCard":
                        user.billingDetail = card;
                        break;
                    case "Bank":
                        user.billingDetail = bank;
                        break;
                }
                context.User.Add(user);
                context.SaveChanges();
                return RedirectToAction("Index");  
            }

            ViewBag.PossibleBillingDetail = context.BillingDetail;
            return View(user);
        }
like image 414
Eduardoxvii Avatar asked Apr 26 '26 03:04

Eduardoxvii


1 Answers

You are passing a User object to your View. This has a navigation property to BillingDetail which can be a CreditCard or a BankAccount. You cast it like this in the View (CreditCard)model and (BankAccount)model. It will work when your creating because you are casting an instance that is null, but it will cause a run-time error when you have a non-null instance because one of the casts will fail.

To fix that you can use model as CreditCard and model as BankAccount then check they are not null before you render the appropriate editors. But you'll need to work out what to do when your user wants to change the payment method.

When the form is returned to the controller, because your Create method signature takes a User parameter, the default ModelBinder knows that it should instantiate a User. It is quite capable of that, but it is not able to work out what to do with the values that appear in the FormCollection that relate to the BillingDetail.

With inheritance you can't rely on the default ModelBinder. You need to work out what suits you best. Here's some references I found useful:

Get an understanding of ModelBinding

Custom model binders - one person's opinion

The solution I went with - but look at all the other solutions here too!

Here's some example code from my project that should give you an idea:

public ActionResult CreateOrEdit(FormCollection values)
{
    //The FormCollection is either a Property or a Block
    BaseProperty model;
    if (values["PropertyTypeID"] != null)
    {
        //it must be a Property!
        Property property = new Property();
        TryUpdateModel(property);
        _Uow.PropertyRepository.InsertOrUpdate(property);
        model = property;
    }
    else
    {
        Block block = new Block();
        TryUpdateModel(block);
        _Uow.BlockRepository.InsertOrUpdate(block);
        model = block;
    }
    //etc....
like image 93
Colin Avatar answered Apr 28 '26 21:04

Colin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!