Understanding virtual and calculated properties in Entity Framework - asp.net-mvc

I have a Job model that contains a number of properties, and a set of linked entities called Quotes:
public class Job
{
....
public virtual ICollection<Quote> Quotes { get; set; }
}
In my Job class I have the following calculated property:
public decimal QuotesAwarded
{
get
{
if (Quotes == null || !Quotes.Any())
{
return 0;
}
var totalUnapprovedQuotes = Quotes.Where(x => x.Status != "Approved");
return 1 - (totalUnapprovedQuotes.Count() / Quotes.Count());
}
set
{
}
}
I have 2 questions:
When I debug this property, Quotes is null (even though there are attached quotes to this entity). I thought that using virtual means that this shouldn't occur? How can I ensure that whenever the model is constructed, the related Quote entities are attached?
The reason I'm doing this is that the property value is stored in the database, so it reduces compute time as it's pre-calculated, is this correct?
Follow up:
In most cases I'm not using Include<Quotes> when retrieving the job object. I'm using Include only when I need the QuotesAwarded value.
However if I don't use Include (say db.jobs.find(id)), and Quotes is null, and the QuotesAwarded value will be 0. So this will get saved to the database when I save the job object, I've really confused myself here.

For your first question, the virtual keyword is used as an indication to Entity Framework to lazily load this. However, it appears that you have disabled lazy loading so you always need to .Include(...) it. As your property is reliant on the quotes being loaded, it will always return 0.
What you are doing is almost right, you just need to let Entity Framework know that your property is a computed column. To do this, you just need to annotate it with an attribute:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string QuotesAwarded
{
get
{
if (Quotes == null || !Quotes.Any())
{
return 0;
}
var totalUnapprovedQuotes = Quotes.Where(x => x.Status != "Approved");
return 1 - (totalUnapprovedQuotes.Count() / Quotes.Count());
}
private set
{
//Make this private so there's no temptation to set it
}
}

Related

Exclude property from updating when SaveChanges() is called

There appears to be two ways to update a disconnected Entity Framework entity using the "attach" method.
Method One is to simply set the disconnected entity's state as modified:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
This will save all fields on the "dog" object. But say you are doing this from an mvc web page where you only allow editing of Dog.Name, and the only Dog property contained on the page is Name. Then one could do Method Two:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();
Method Two could get quite verbose when there are a lot of properties to update. This prompted me to attempt Method Three, setting IsModified = false on the properties I don't want to change. This does not work, throwing the runtime error "Setting IsModified to false for a modified property is not supported":
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
I'd much prefer to use Method One everywhere, but there are many instances where my asp.net mvc view does not contain every scalar property of the Dog class.
My questions are:
Are there any attributes I could use on the POCO class that would tell Entity Framework that I never want the property to up updated? Eg, [NeverUpdate]. I am aware of the [NotMapped] attribute, but that is not what I need.
Failing that, is there any way I can use Method One above (myDbContext.Entry(dog).State = EntityState.Modified;
) and exclude fields that I don't want updated?
P.S. I am aware of another way, to not use "attach" and simply fetch a fresh object from the database, update the desired properties, and save. That is what I am doing, but I'm curious if there is a way to use "attach," thus avoiding that extra trip to the database, but do it in a way that is not so verbose as Method Two above. By "fetch a fresh object" I mean:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
The following may work works.
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
// You need to give Foreign Key Property name
// instead of Navigation Property name
entry.RejectPropertyChanges("OwnerID");
}
myDbContext.SaveChanges();
If you want to do it in a single line, use the following extension method:
public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
{
entry.RejectPropertyChanges(propertyName);
}
}
And use it like this
// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();
As you can see, you can modify this solution to fit your needs, e.g. use string[] properties instead of string propertyName as the argument.
Suggested Approach
A better solution would be to use an Attribute as you suggested ([NeverUpdate]). To make it work, you need to use SavingChanges event (check my blog):
void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
ObjectContext context = sender as ObjectContext;
if(context != null)
{
foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var type = typeof(entry.Entity);
var properties = type.GetProperties();
foreach( var property in properties )
{
var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
if(attributes.Length > 0)
entry.RejectPropertyChanges(property.Name);
}
}
}
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{
}
//In your POCO
public class Dogs
{
[NeverUpdate]
public int OwnerID { get; set; }
}
Warning: I did not compile this code. I'm not at home :/
Warning 2: I have just read the MSDN documentation and it says:
ObjectStateEntry.RejectPropertyChanges Method
Rejects any changes made to the property with the given name since the
property was last loaded, attached, saved, or changes were accepted.
The orginal value of the property is stored and the property will no
longer be marked as modified.
I am not sure what its behavior would be in the case of attaching a modified entity. I will try this tomorrow.
Warning 3: I have tried it now. This solution works. Property that is rejected with RejectPropertyChanges() method are not updated in the persistence unit (database).
HOWEVER, if the entity that is updated is attached by calling Attach(), the current context remains dirty after SaveChanges(). Assume that the following row exists in the database:
Dogs
ID: 1
Name: Max
OwnerID: 1
Consider the following code:
var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
The current state of database after SaveChanges():
Dogs:
ID: 1
Name: Achilles
OwnerID: 1
The current state of myDbContext after SaveChanges():
var ownerId = myDog.OwnerID; // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged
So what you should do? Detach it after SaveChanges():
Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;

Want to save selected (i.e., more than 1) enums as string with NHibernate

I cannot for the life of me get this to work with my existing code, but I am trying to save my enum selections as strings in NHibernate. Basically, I have a UI check box and if the user selects multiple check boxes I want to store those selections. Now, I can get NHibernate to store ONE selection (e.g., from a drop down or radio button list, where the user is limited to one choice only).
This is the jist of what I have for an enum:
public enum IncomeType
{
[Display(Name = "Full-Time Employment")]
FullTime,
[Display(Name = "Part-Time Employment")]
PartTime,
[Display(Name = "Self-Employment")]
SelfEmployed,
[Display(Name = "Rental")]
Rental,
[Display(Name = "Social Security Payments")]
SocialSecurity,
[Display(Name = "Retirement / Pension Payments")]
Retirement,
[Display(Name = "Child Support Payments")]
ChildSupport,
[Display(Name = "Spousal Maintenance")]
Maintenance,
[Display(Name = "Other")]
Other
}
I use a method to "select" whether a checkbox list is shown (if my BulkItemThreshold equals the number of options, a checkbox list is displayed). Here is that method:
public static IEnumerable<SelectListItem> GetItemsFromEnumString<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
(Note: some items in there are helpers, but I don't believe they are relevant; also, the selected input is displayed using a template .cshtml file - again, not sure if that's relevant)
Now, I call this thusly:
public class IncomeTypeSelectorAttribute : SelectorAttribute
{
public override IEnumerable<SelectListItem> GetItems()
{
return Selector.GetItemsFromEnumString<IncomeType>();
}
}
And finally, we get to the virtual property (using a proxy) but this is where NHibernate throws a wrench (Note: this was working fine for me before NHibernate, and now I am trying to get many lines of code working with it WITHOUT having to re-do everything; if I re-do everything I will probably triple the code I already have to get it to work):
Property (record):
[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual List<string> IndividualIncomeTypeCheckBox { get; set; }
proxy (part):
public List<string> IndividualIncomeTypeCheckBox
{
get { return Record.IndividualIncomeTypeCheckBox; }
set { Record.IndividualIncomeTypeCheckBox = value; }
}
Again, this is how I was doing things and it was working great before NHibernate. But now I have to use NHibernate. No getting around it.
I am using a service class that it tying the two together in a Create method to save in the DB with NHibernate, and for the above it would ordinarily look like this:
part.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
This would work if it were just one selection.
Well, I've spent a good two (2) months trying to get this to work. It's tough because I have lots of code where the user can make only one selection (such as with a radiobutton list) and it works GREAT - even with NHibernate. Let me give you an example:
public virtual IncomeType? IndividualIncomeTypeCheckBox { get; set; }
If I do the above, it will display a drop down list, and NHibernate will store the ONE allowable option selected by the user in the DB no problem. But more than one option with List<string> does not work.
Now, I have tried everything I could find here or elsewhere and nothing works. Yes, I know it should be IList<IncomeType> or some other variant. But if I use this then NHibernate requires that IncomeType be another table in the DB. This is too much code to write for such a simple thing I believe. We are not talking a many-to-many relation in the sense that this is not a User with Multiple addresses (wherein addresses would have street, city, state, zip, etc.).
I have tried different types of proxy get and set code, but nothing works. I have tried [Flags] and other things working with string only, but to no avail. Those last solutions would "work" but ONLY to save the first item selected out of multiple (i.e., in my scenario, if the user selected "FullTime" and "Rental" as Income Types, then only "FullTime" (string) would be saved or "1" ([Flags]/int), not both items selected.
I have a situation where I re-display the choices using a ReadOnly attribute like this:
[IncomeTypeSelector]
[ReadOnly(true)]
public List<string> IndividualIncomeTypeCheckBoxPost
{
get { return IndividualIncomeTypeCheckBox; }
}
This would display on the UI, but I tried doing something like this with NHibernate and it wouldn't work.
Could anyone please show me, using the above, how I can go about getting NHibernate to store more than one enum in this checkbox list scenario?
UPDATE:
More poking around here and on the web, and I came up with the following (which still does not work).
Property (record):
[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
get { return incomeType; }
set { incomeType= value; }
}
private IList<IncomeTypeRecord> incomeType =
new List<IncomeTypeRecord>();
Proxy (part):
public IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
get { return Record.IndividualIncomeTypeCheckBox; }
set { Record.IndividualIncomeTypeCheckBox= value; }
}
And a change to the enum:
public enum IncomeType : int // removing int & value still gives validate error
{
[Display(Name = "Full-Time Employment")]
FullTime = 1,
[Display(Name = "Part-Time Employment")]
PartTime,
....
}
And I added this class to support IncomeTypeRecord
public class IncomeTypeRecord
{
public virtual int Id { get; set; }
public virtual IncomeType Value { get; set; }
}
HOWEVER, when I get to the UI screen and pick one or more options I get a validation error (value not valid). For example, say I pick FullTime alone, or pick FullTime and Retirement, then the UI will display the following error:
The value 'FullTime' is invalid.
The value 'FullTime,Retirement' is invalid.
(respectively)
Even if I remove the int declaration for the enum and get rid of the value I started with "1", I still get this validation error. I tried messing around with and adding different model binders (which now has me stumped as to whether my original problem still exists and now I have a different problem - but you still get bounty points :) ).
Pulling my hair out. If I could offer more bounty I would. I need a definitive solution. I appreciate any help.
UPDATE
This is what I have so far:
Record:
public virtual string IndividualIncomeTypeCheckBox{ get; set; }
Part:
//If I do IEnumberable<string> my .Select throws a cast error
public IEnumerable<IncomeType> IndividualIncomeTypeCheckBox
{
get
{
return Record
.IndividualIncomeTypeCheckBox
.Split(',')
.Select(r => (IncomeType)Enum.Parse(typeof(IncomeType), r));
}
set { Record.IndividualIncomeTypeCheckBox= value
== null ? null : String.Join(",", value); }
}
Service Class:
public SimplePart CreateSimple(SimplePartRecord record)
{
SimplePart simple = Services.ContentManager.Create<SimplePart>("Simple");
...
//How I would save a FirstName property (example Part / PartRecord below)
//public virtual string FirstName { get; set; } - PartRecord
//public string FirstName - Part
//{
// get { return Record.FirstName ; }
// set { Record.FirstName= value; }
//}
simple.FirstName = record.FristName;
...
//I obviously cannot do the following with the above IncomeType
//Getting cannot convert string to IEnumerable error
//How would I write this:
simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
...
}
And this is how it's called in a controller (this persists to DB): (Updating Controller code)
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("WrapUp");
else if ((nextButton != null) && ModelState.IsValid)
{
_myService.CreateSimple(myData.SimplePartRecord);
return RedirectToAction("Submitted");
}
else
return View(myData);
}
Updating with additional code (serialization and view model):
"myData" is defined in the controller (using Serialization) as:
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null)
{
myData = (MyViewModel)new MvcSerializer().Deserialize
(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
I use Serialization because I set up a multi-step wizard (as seen in the controller action "backButton" "nextButton) on the front-end. I am not using a driver (which can only display Admin or on the front-end but then only on .cshtml files directly under the ~/Views folder (not in a structured folder list like I am using)). No driver = no update view model type code = no mechanism to "create" the data in the DB. If I do not use some "create" type method, the form will submit but all the data will be "NULL".
When you say that the data should be persisted automatically, I am sorry but I do not see how. All the stuff I read or code I review has SOME method of updating the DB with whatever is entered in a form. If I am missing something, my apologies.
"MyViewModel" is pretty straightforward:
[Serializabel]
public class MyViewModel
{
public SimplePartRecord SimplePartRecord { get; set; }
}
And, just in case, here is the relevant portion of the migration (return 1 is a completely separate and unrelated table):
public int UpdateFrom1()
{
SchemaBuilder.CreateTable("SimplePartRecord",
table => table
.ContentPartRecord()
...
.Column("IndividualIncomeTypeCheckBox", DbType.String)
...
);
ContentDefinitionManager.AlterPartDefinition("SimplePart",
part => part
.Attachable(false));
return 2;
}
The error I am getting is
Cannot implicitly convert type 'string' to 'System.Collections.Generic.IEnumerable'"
when I do the following in the "Create" method of my service class:
simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
One additional thought: I tried using the n-n Relation sample to handle this scenario. Aside from it being a lot of extra code for what I thought should be straightforward and simple, because of the way I am using Serialization I had a lot of object reference errors and could not figure out how to properly code my controller to handle it.
There's a lot of info to wade through here so hopefully I haven't missed the point. It appears to me that the goals are:
The business class has a collection property of IList<IncomeType> without requiring an additional table
The values in that collection should be persisted as a delimited string of the enum names
The best approach is to use a custom user type (an implementation of NHibernate.UserTypes.IUserType) to map the property. Below is a generic IUserType that will map an enum of type T from an IList<T> property to a comma delimited string in the database and back again. There's no easy way to restrict T to an enum but the code will only work with enums.
Mapping a property using the custom type is simple with Fluent NHibernate:
public class Person
{
public Person()
{
IncomeTypes = new List<IncomeType>();
}
public virtual int PersonId { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual IList<IncomeType> IncomeTypes { get; protected set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.PersonId).GeneratedBy.Identity();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.IncomeTypes).CustomType<EnumAsDelimitedStringType<IncomeType>>();
}
}
And here's the code for the user type:
public class EnumAsDelimitedStringType<T> : IUserType
{
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xList = x as IList<T>;
var yList = y as IList<T>;
if (xList == null || yList == null)
{
return false;
}
// compare set contents
return xList.OrderBy(xValue => xValue).SequenceEqual(yList.OrderBy(yValue => yValue));
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.AnsiString.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new List<T>();
}
var getValueArray = outValue.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
return Array.ConvertAll(getValueArray, s => (T)Enum.Parse(typeof(T), s)).ToList();
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as IList<T>;
// set to string.Empty if you prefer to store that instead of null when the collection is null or empty
object setValue = null;
if (inValue != null && inValue.Any())
{
var setValueArray = Array.ConvertAll(inValue.ToArray(), v => Enum.GetName(typeof(T), v));
setValue = string.Join(",", setValueArray);
}
NHibernateUtil.AnsiString.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.AnsiString)}; }
}
public Type ReturnedType
{
get { return typeof(IList<T>); }
}
public bool IsMutable
{
get { return false; }
}
}
I think you're on the right track pursuing a [Flags] enum. You may have done this, but just in case -- making an enum flags-worthy is more than adding the attribute. You also have to specify the value for the items in a binary-friendly manner. I've found the easiest way to do this is as follows:
[Flags]
public enum IncomeType : long // you'll need the room with several options
{
FullTime = 1,
PartTime = 1 << 1,
SelfEmployed = 1 << 2
// And so on
}
If you don't do this, then you'll get sequential integer values, which breaks the bitwise comparison that allows you to do multiple values in a single integer.
Your code to create the SelectList looks fine. Your options should construct form values that get posted back with the same name. If you want to use the default modelbinder, that means the associated property on your view model would need to be List<int>. If you're not using a view model (you probably should) you can pull it out of the forms collection.
Once you have this set up, then translating from your view model to your NHibernate entity is simple if a little annoying. You basically have to cycle through the values in the list and |= them onto your NHibernate entity's single enum property.
So let's assume you have a view model like this:
public class MyEditViewModel
{
public string Name { get; set; }
public List<int> IncomeSelections { get; set; }
// You'll probably have this to populate the initial view rendering
public SelectList AllIncomeOptions { get; set; }
}
You'll build your view using your helpers and all that, then build the checkboxes using the SelectList but making sure the input name is IncomeSelections, then when it's posted back you will push the view model data into your NHibernate entity something like this:
var myNHEntity = new NHEntity();
// If you're editing an existing entity, then be sure to reset the enum
// value to 0 before going into the following foreach loop...
foreach (var incomeSelection in viewModel.IncomeSelections)
{
myNHEntity.IncomeSelection |= incomeSelection;
}
There's probably a more clever way to do this, and you might have to cast the int to your enum type, but you'll figure that out (I'd do it for you, but it is Friday and I already have a beer open).
NHibernate should persist it without you having to do anything funky on the NH side.
In summary...
It seems like this is more a problem of how you handle the posted data than the NHibernate side. If you implement something like this, then be sure to use Fiddler or FireBug to inspect the posted values to make sure 1) they're integers and 2) the names are the same so they'll be added to the list.
Good luck!
The problem is simply that it won't be able to map a List without building a full relationship with an intermediate association table. It is way simpler to have the record store the values as a comma-separated string (so your record property is a string, not a list of string) and your part can map back and forth between string and List.
You can find an example of something very close here:
https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPartRecord.cs?at=default
https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPart.cs?at=default
It's not using enum values, instead it's a list of ids, but that should give you a good idea about how to make this work fairly simply: parsing enums you already know how to do.
Let me know if you need more details, but I think that's what you needed to get unblocked.

Value showing up as null even though I assigned value in controller

I'm trying to do some sort of conditional statement to calculate a value. To mock my data I am assigning the value in my controller (temporarily) to see how my UI is coming along. I can perform the calculation in a function block in the view, but it's lengthy and doesn't belong there. So, I am trying now to do the calculation in a model (Calculations.cs).
The code for the calculation is working in that a value is being passed, except that my condition is failing and passing the default value of 0 when it should be passing another value based on my mocked value in the controller.
Here is the Calculations.cs
public class Calculations
{
PriceQuote price = new PriceQuote();
StepFilingInformation filing = new StepFilingInformation();
public decimal Chapter7Calculation
{
get
{
return
price.priceChapter7
+
((ReferenceEquals
(filing.PaymentPlanRadioButton,
Models.StepFilingInformation.PaymentPlan.Yes))
?
price.pricePaymentPlanChapter7
:
0);
}
}
}
I originally had (filing.PaymentPlanRadioButton == Models.StepFilingInformation.PaymentPlan.Yes) checking whether or not the radio button was set to "Yes", but changed it to ReferenceEquals. This doesn't affect the outcome.
I have my controller assigning the value to PaymentPlanRadioButton to "Yes", so pricePaymentPlanChapter7 should be the value being added to priceChapter7, but it is not. Instead "0" is being added as the fall back to the condition. So PaymentPlanRadioButton is null even though I am assigning it in the controller.
I cannot figure out how to fix this. If I assign it in the model and get it to work that will not resolve the issue as when I remove the mocking controller and expect a user to choose a radio button it will still be null and the condition will fail.
Here is the "mock" controller:
public class QuoteMailerController : Controller
{
public ActionResult EMailQuote()
{
Calculations calc = new Calculations();
var total = calc.Chapter7Calculation;
QuoteData quoteData = new QuoteData
{
StepFilingInformation = new Models.StepFilingInformation
{
//"No" is commented out, so "Yes" is assigned
//PaymentPlanRadioButton =
//Models.StepFilingInformation.PaymentPlan.No,
PaymentPlanRadioButton =
Models.StepFilingInformation.PaymentPlan.Yes,
}
};
}
}
And this is where I store prices (PriceQuote.cs):
public class PriceQuote
{
public decimal priceChapter7 { get { return 799; } }
public decimal pricePaymentPlanChapter7 { get { return 100; } }
}
This is my ViewModel:
public class QuoteData
{
public PriceQuote priceQuote;
public Calculations calculations;
public StepFilingInformation stepFilingInformation { get; set; }
public QuoteData()
{
PriceQuote = new PriceQuote();
Calculations = new Calculations();
}
}
So, the way this should work is 799 + 100 = 899, since PaymentPlan.Yes is assigned as the value to the radio button in the controller. But instead I am getting just 799 (799 + 0) because when I debug PaymentPlanRadioButton is coming up null.
Any thoughts/guidance?
Just in case, here is the PaymentPlanRadioButton located within StepFilingInformation.cs (and is one of my models):
public enum PaymentPlan
{
No,
Yes
}
public class PaymentPlanSelectorAttribute : SelectorAttribute
{
public override IEnumerable<SelectListItem> GetItems()
{
return Selector.GetItemsFromEnum<PaymentPlan>();
}
}
[PaymentPlanSelector(BulkSelectionThreshold = 3)]
public PaymentPlan? PaymentPlanRadioButton { get; set; }
Sorry for the length.
Your Calculations class bases it's math on the StepFilingInformation object it contains. However, you never set StepFilingInformation from within Calculations to anything other than a new, empty object.
Your constructor should probably require a parameter of type StepFilingInformation.
public class Calculations
{
StepFilingInformation filing;
public Calculations(StepFilingInformation filing)
{
this.filing = filing;
}
Regardless of how you pass your calculations class a reference to StepFilingInformation, you must set this value before you run your calculation that depends on it.
Also if QuoteData is your ViewModel, then it shouldn't contain a reference to your Calculations class. It should only contains results created in the calculations class that the View must display.
StepFilingInformation filing = new Models.StepFilingInformation
{
PaymentPlanRadioButton = Models.StepFilingInformation.PaymentPlan.Yes,
};
Calculations calc = new Calculations(filing);
var total = calc.Chapter7Calculation;
QuoteData quoteData = new QuoteData //Only include Properties you're going to display in the view model
{
Total = total
};

Entity Framework ASP.NET MVC private model fields

There is a field in our database which really ought to be a boolean, but for some reason the original developers made it a CHAR which will either be set to "1" or "0".
[Column("CHARGEABLE")]
[StringLength(1)]
private string Chargeable { get; set; }
I want my model to represent this field as a boolean so I figured I could add a property to my model to wrap it:
[NotMapped]
public bool ChargeableTrue
{
get
{
return Chargeable == "1" ? true : false;
}
set
{
Chargeable = value ? "1" : "0";
}
}
Now on my View I just display the EditorFor ( ChargeableTrue ), but when I click save it doesn't actually update it.
I think what is happening is that when the model is being updated, it's still attempting to get the value of 'Chargeable' from the View, even though I haven't displayed it there. And since there is no input field, it just gets null and ends up saving that to the database.
if (ModelState.IsValid)
{
db.Entry(call).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
What is one expected to do in this situation?
Based on KMan's answer, here's the extended version just in case you're not familiar with creating view models.
The idea is that your domain object is not really what you want to be updating exactly from your views. Instead, you create a go-between that can also include view-specific items (like a list of objects to populate a drop-down).
public class MyViewModel {
public bool Chargeable { get; set; }
}
Now you can do this:
#* In view *#
Html.EditorFor(m => m.Chargeable)
// In controller
public ActionResult Save(MyViewModel model) {
if (ModelState.IsValid) {
var domainObject = new MyObject() {
Chargeable = model.Chargeable ? "1" : "0"
};
// the rest of your code using domainObject
}
}
I'd consider just creating an overload of your domain object's constructor that accepts your view model to keep the mapping in one place. I typically use a tool like AutoMapper to map objects or manual extension methods.
A view model typically contains a sub-set of your domain object's properties, but can contain all of them or more properties like lists, visbility states, etc. They come in incredibly useful and I've never done a MVC project where I haven't used them.
Use a view model and make your mapping on the controller.

Json and Circular Reference Exception

I have an object which has a circular reference to another object. Given the relationship between these objects this is the right design.
To Illustrate
Machine => Customer => Machine
As is expected I run into an issue when I try to use Json to serialize a machine or customer object. What I am unsure of is how to resolve this issue as I don't want to break the relationship between the Machine and Customer objects. What are the options for resolving this issue?
Edit
Presently I am using Json method provided by the Controller base class. So the serialization I am doing is as basic as:
Json(machineForm);
Update:
Do not try to use NonSerializedAttribute, as the JavaScriptSerializer apparently ignores it.
Instead, use the ScriptIgnoreAttribute in System.Web.Script.Serialization.
public class Machine
{
public string Customer { get; set; }
// Other members
// ...
}
public class Customer
{
[ScriptIgnore]
public Machine Machine { get; set; } // Parent reference?
// Other members
// ...
}
This way, when you toss a Machine into the Json method, it will traverse the relationship from Machine to Customer but will not try to go back from Customer to Machine.
The relationship is still there for your code to do as it pleases with, but the JavaScriptSerializer (used by the Json method) will ignore it.
I'm answering this despite its age because it is the 3rd result (currently) from Google for "json.encode circular reference" and although I don't agree with the answers (completely) above, in that using the ScriptIgnoreAttribute assumes that you won't anywhere in your code want to traverse the relationship in the other direction for some JSON. I don't believe in locking down your model because of one use case.
It did inspire me to use this simple solution.
Since you're working in a View in MVC, you have the Model and you want to simply assign the Model to the ViewData.Model within your controller, go ahead and use a LINQ query within your View to flatten the data nicely removing the offending circular reference for the particular JSON you want like this:
var jsonMachines = from m in machineForm
select new { m.X, m.Y, // other Machine properties you desire
Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire
}};
return Json(jsonMachines);
Or if the Machine -> Customer relationship is 1..* -> * then try:
var jsonMachines = from m in machineForm
select new { m.X, m.Y, // other machine properties you desire
Customers = new List<Customer>(
(from c in m.Customers
select new Customer()
{
Id = c.Id,
Name = c.Name,
// Other Customer properties you desire
}).Cast<Customer>())
};
return Json(jsonMachines);
Based on txl's answer you have to
disable lazy loading and proxy creation and you can use the normal methods to get your data.
Example:
//Retrieve Items with Json:
public JsonResult Search(string id = "")
{
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
var res = db.Table.Where(a => a.Name.Contains(id)).Take(8);
return Json(res, JsonRequestBehavior.AllowGet);
}
Use to have the same problem. I have created a simple extension method, that "flattens" L2E objects into an IDictionary. An IDictionary is serialized correctly by the JavaScriptSerializer. The resulting Json is the same as directly serializing the object.
Since I limit the level of serialization, circular references are avoided. It also will not include 1->n linked tables (Entitysets).
private static IDictionary<string, object> JsonFlatten(object data, int maxLevel, int currLevel) {
var result = new Dictionary<string, object>();
var myType = data.GetType();
var myAssembly = myType.Assembly;
var props = myType.GetProperties();
foreach (var prop in props) {
// Remove EntityKey etc.
if (prop.Name.StartsWith("Entity")) {
continue;
}
if (prop.Name.EndsWith("Reference")) {
continue;
}
// Do not include lookups to linked tables
Type typeOfProp = prop.PropertyType;
if (typeOfProp.Name.StartsWith("EntityCollection")) {
continue;
}
// If the type is from my assembly == custom type
// include it, but flattened
if (typeOfProp.Assembly == myAssembly) {
if (currLevel < maxLevel) {
result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1));
}
} else {
result.Add(prop.Name, prop.GetValue(data, null));
}
}
return result;
}
public static IDictionary<string, object> JsonFlatten(this Controller controller, object data, int maxLevel = 2) {
return JsonFlatten(data, maxLevel, 1);
}
My Action method looks like this:
public JsonResult AsJson(int id) {
var data = Find(id);
var result = this.JsonFlatten(data);
return Json(result, JsonRequestBehavior.AllowGet);
}
In the Entity Framework version 4, there is an option available: ObjectContextOptions.LazyLoadingEnabled
Setting it to false should avoid the 'circular reference' issue. However, you will have to explicitly load the navigation properties that you want to include.
see: http://msdn.microsoft.com/en-us/library/bb896272.aspx
Since, to my knowledge, you cannot serialize object references, but only copies you could try employing a bit of a dirty hack that goes something like this:
Customer should serialize its Machine reference as the machine's id
When you deserialize the json code you can then run a simple function on top of it that transforms those id's into proper references.
You need to decide which is the "root" object. Say the machine is the root, then the customer is a sub-object of machine. When you serialise machine, it will serialise the customer as a sub-object in the JSON, and when the customer is serialised, it will NOT serialise it's back-reference to the machine. When your code deserialises the machine, it will deserialise the machine's customer sub-object and reinstate the back-reference from the customer to the machine.
Most serialisation libraries provide some kind of hook to modify how deserialisation is performed for each class. You'd need to use that hook to modify deserialisation for the machine class to reinstate the backreference in the machine's customer. Exactly what that hook is depends on the JSON library you are using.
I've had the same problem this week as well, and could not use anonymous types because I needed to implement an interface asking for a List<MyType>. After making a diagram showing all relationships with navigability, I found out that MyType had a bidirectional relationship with MyObject which caused this circular reference, since they both saved each other.
After deciding that MyObject did not really need to know MyType, and thereby making it a unidirectional relationship this problem was solved.
What I have done is a bit radical, but I don't need the property, which makes the nasty circular-reference-causing error, so I have set it to null before serializing.
SessionTickets result = GetTicketsSession();
foreach(var r in result.Tickets)
{
r.TicketTypes = null; //those two were creating the problem
r.SelectedTicketType = null;
}
return Json(result);
If you really need your properties, you can create a viewmodel which does not hold circular references, but maybe keeps some Id of the important element, that you could use later for restoring the original value.

Resources