Want to save selected (i.e. More than 1) listings as a string with NHibernate

I can't make this work for my whole life with my existing code, but I'm trying to save my enum selections as strings in NHibernate. Basically, I have a user interface, and if the user selects several checkboxes, I want to save these options. Now I can get NHibernate to store the ONE selection (for example, from a drop-down list or a list of radio buttons where the user is limited to only one choice).

This is the essence of what I have for listing:

 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 the method to "select" whether a list of flags is displayed (if my BulkItemThreshold is equal to the number of options, a list of flags is displayed). Here is the 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 elements have helpers, but I do not think they are relevant, and the selected input is displayed using the .cshtml file template - again, not sure if this is relevant)

Now I call it like this:

 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 the key (Note: this worked fine for me before NHibernate, and now I'm trying to get many lines of code working with it WITHOUT having to repeat everything if I will do everything again that I will probably triple the code that I already need to make it 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 what I did, and it worked perfectly up to NHibernate. But now I have to use NHibernate. Do not get around this.

I use a service class that binds them together in the Create method to save to the database using NHibernate, and for the above, this usually looks like this:

  part.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox; 

This will work if it is just one choice.

Well, I spent two (two) months trying to get this to work. This is complicated because I have a lot of code where the user can only make one choice (for example, with a list of radio books), and it works GREAT - even with NHibernate. Let me give you an example:

 public virtual IncomeType? IndividualIncomeTypeCheckBox { get; set; } 

If I do this above, a drop-down list will be displayed, and NHibernate will save the ONE function selected by the user, selected by the user in the database, without problems. But more than one option with List<string> does not work.

Now I tried everything that I could find here or somewhere else, and nothing works. Yes, I know that it should be IList<IncomeType> or some other option. But if I use this, then NHibernate requires IncomeType be another table in the database. This is too much code to write such a simple thing as I think. We are not talking about the many-to-many relationship in the sense that this is not a user with multiple addresses (in which the addresses would have a street, city, state, zip code, etc.).

I tried different types of get and set proxy code, but nothing works. I tried [Flags] and other things that work only with string , but to no avail. These last solutions will "work", but ONLY to save the first item selected from several (i.e., in my scenario, if the user selects "FullTime" and "Rental" as income types, then only "FullTime" ( string ) will be saved or "1" ( [Flags] / int ), not both selected items.

I have a situation where I am re-viewing options using the ReadOnly attribute as follows:

 [IncomeTypeSelector] [ReadOnly(true)] public List<string> IndividualIncomeTypeCheckBoxPost { get { return IndividualIncomeTypeCheckBox; } } 

This will display in the user interface, but I tried to do something similar with NHibernate, and it will not work.

Can someone show me, using the above, how can I get NHibernate to store more than one enum in this checkbox list script?

UPDATE: Noisier here and on the Internet, and I came up with the following (which still doesn't 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 the change of listing:

 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 user interface screen and select one or more parameters, I get a validation error (value is invalid). For example, let's say I choose FullTime myself or choose FullTime and Retirement, then the following error will be displayed in the user interface:

The value "FullTime" is invalid.

The value "FullTime, Retirement" is invalid.

(respectively)

Even if I delete the int declaration for enum and get rid of the value that I started with "1", I still get this validation error. I tried to team up and add various bindings to the model (now I have a dead end that my original problem still exists, and now I have a different problem, but you still have bonus points :)).

I stretch my hair. If I could offer more generosity, I would do it. I need a final decision. 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); } } 

Class of service:

 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 here is how it is called in the controller (this is stored in the database): (update 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); } 

Update using additional code (serialization and view model):

"myData" is defined in the controller (using Serialization) as follows:

 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-stage wizard (as shown in the backButton "nextButton" controller action) on the interface. I do not use a driver (which can display only Admin or on the front -end, but only on .cshtml files directly in the ~ / Views folder (not in the list of structured folders as I use). There is no driver = there is no type view of the model code type = There is no mechanism for "creating" data in the DB. If I do not use any method like "create", the form will be submitted, but all the data will be "NULL".

When you say that data should be saved automatically, I'm sorry, but I don’t see how to do it. All the material that I read or the code that I look through has SOME method of updating the database with what is entered in the form. If I miss something, my apologies.

"MyViewModel" is pretty simple:

 [Serializabel] public class MyViewModel { public SimplePartRecord SimplePartRecord { get; set; } } 

And, just in case, here is the corresponding part 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 get is

Cannot implicitly convert type 'string' to 'System.Collections.Generic.IEnumerable' "

when I do the following in the Create method of my class of service:

 simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox; 

Another thought: I tried to use the nn link pattern to handle this scenario. Besides the fact that this is a lot of additional code for what I thought should be simple and simple, due to the way I use serialization, I had many errors in the reference to the object and I could not figure out how to correctly encode my controller to handle it.

+2
asp.net-mvc nhibernate orchardcms
source share
3 answers

The problem is that it will not be able to display the list without creating a complete relationship with the staging relationship table. This is easier if the record stores the values ​​as a comma separated string (therefore, your record property is a string, not a list of strings), and your part can move between the string and the list.

Here you can find an example:

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 does not use enum values, but is a list of identifiers, but this should give you a good idea of ​​how to make this work simple enough: parsing the listings that you already know how to do.

Let me know if you need more information, but I think you needed to unlock it.

0
source share

There is a lot of information to get here, so I hope I have not missed it. It seems to me that the goals:

  • A business class has the collection property IList<IncomeType> , without requiring an additional table
  • The values ​​in this collection should be stored as a separator string of enumeration names.

The best approach is to use a custom type ( NHibernate.UserTypes.IUserType implementation) to map the property. Below is a generic IUserType that maps an enumeration of type T from the IList<T> property to a comma-delimited string in the database and vice versa. There is no easy way to limit T to a transition, but the code will only work with enumerations.

Matching a property using a custom type is just using 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 is 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; } } } 
+4
source share

I think you're on the right track, chasing the renaming of [Flags] . You may have done this, but just in case - making the enum flag worthy is more than adding an attribute. You must also specify a value for the elements in binary form. I found the easiest way to do this 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 do not, you will get consecutive integer values ​​that break the bitwise comparison, which allows you to run multiple values ​​in a single integer value.

Your code to create a SelectList looks great. Your parameters should create form values ​​that are sent back with the same name. If you want to use the default model module, this means that the related property on your view model must be List<int> . If you are not using a presentation model (you probably should), you can pull it out of the collection of forms.

Once you configure this setting, translating from your view model to your NHibernate object is simple, if a little annoying. You basically need to loop through the values ​​in the list and |= them to a single enumeration property of the NHibernate object.

So, suppose you have a presentation model as follows:

 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 will create your view using your helpers and all that, and then create checkboxes using SelectList , but make sure the login name is IncomeSelections , and then when it is sent back, you will enter the view model data into your NHibernate 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 is probably a smarter way to do this, and you might have to drop the int to your type of enumeration, but you will understand it (I will do it for you, but it's Friday and I already have a beer open).

NHibernate should persist, but you don't need to do anything funny on the NH side.

In short ...

This seems to be more of a problem in how you process published data than the NHibernate side. If you implement something like this, then be sure to use Fiddler or FireBug to check the published values ​​to make sure they are 1) they are integer and 2) the names are the same, so they will be added to the list.

Good luck

+2
source share

All Articles