I am trying to map same column to be an attribute and a relationship (for reasons that have to do with legacy data) using following mapping:
References(x => x.BaseProductTemplate, "ProductCodeTxt");
Map(x => x.DescriptionCode, "ProductCodeTxt")
.CustomType(typeof(TrimmedStringUserType));
but "System.IndexOutOfRangeException: Invalid index 9 for this SqlParameterCollection with Count=9." exception is thrown. How can I achieve this with NH without getting this error.
Here is a class:
public static MarketingPlanBaseProduct Create(BaseProductTemplate baseProductTemplate, ProductType productType)
{
var toReturn = new MarketingPlanBaseProduct();
toReturn.BaseProductTemplate = baseProductTemplate;
toReturn.Type = productType;
return toReturn;
}
protected MarketingPlanBaseProduct()
{
_coInsurancePercentages = new List<PlanCoInsurance>();
_benefits = new List<BaseProductBenefit>();
}
#region " Old system workaround "
/// HACK: In insight users were able to override description and the code, and system was displaying description from
/// the "BaseProduct" table, not from "ProductRef" table. In order to have this description display in Insight
/// during transitional period
/// we are modeling the MarketingPlanBaseProduct with two independent properties
/// that will be loaded based on the values in "ProductCodeTxt" and ProductNameTxt.
/// New MarketingPlanBaseProducts will however have description populated based on BaseProductTemplate ("ProductRef")
/// and code/description will not be changable from New System
/// Once old system is cut off, "DescriptionCode" Property should be removed,
/// "Name should be changed to be mapped property that derives value from BaseProductTemplate relationship
private string _descriptionCode;
public virtual string DescriptionCode
{
get
{
return _descriptionCode;
}
}
private string _name;
public virtual string Name
{
get { return _name; }
}
private void SetName(BaseProductTemplate baseProductTemplate)
{
_name = baseProductTemplate.Name;
_descriptionCode = baseProductTemplate.Code;
}
private BaseProductTemplate _baseProductTemplate;
public virtual BaseProductTemplate BaseProductTemplate
{
get
{
return _baseProductTemplate;
}
private set
{
_baseProductTemplate = value;
SetName(_baseProductTemplate);
}
}
You can also use Formula:
Map(x => x.MyProperty).Formula("propertyColumn").Not.Insert().Not.Update();
Since this was mapped on the view, I added one more column in the view with different name mapped to the same column, and it works.
Related
Here's the (edited) domain class:
public class Patient : IntegerKeyEntity
{
...
public virtual string LastName
{
get
{
return Encoding.Unicode.GetString(encryptionService.Decrypt(LastNameEncrypted));
}
set
{
LastNameEncrypted = encryptionService.Encrypt(value);
}
}
/// <summary>
/// Contains the encrypted last name. Access via LastName for unencrypted version.
/// </summary>
public virtual byte[] LastNameEncrypted { get; set; }
...
}
Here's the Fluent mapping:
public class PatientMap : ClassMap<Patient>
{
public PatientMap()
{
...
Map(x => x.LastNameEncrypted, "LastName")
.Not.Nullable();
...
}
}
I don't map LastName in Patient. The table has a LastName column (a varbinary) and no LastNameEncrypted column.
Here's the query:
public virtual IQueryable<TResult> SatisfyingElementsFrom(IQueryable<T> candidates, int start, int limit, string sort, string dir)
{
if (this.MatchingCriteria != null)
{
return candidates.Where(this.MatchingCriteria).OrderBy(sort + " " + dir).Skip(start).Take(limit).ToList().ConvertAll(this.ResultMap).AsQueryable();
}
return candidates.ToList().ConvertAll(this.ResultMap).AsQueryable();
}
The return (inside the if block) is where the error is triggered. The error says "Could not resolve property LastName of Patient".
I am using NHibernate (v2.1.2.4000), Fluent NHibernate (v1.1.0.685) and NHibernate.Linq (v1.1.0.1001). I cannot update these DLLs.
Is it because I don't have a mapping for Patient.LastName? I don't have or need one. If that is the issue, how do I map/tell Fluent NHibernate to ignore that property?
PS: I am not using AutoMapping, only explicit mappings. They are loaded as follows. In my application, only cfg (an NHibernate.Cfg.Configuration object) and mappingAssemblies (which points to the one DLL with the mappings) have a value.
private static ISessionFactory CreateSessionFactoryFor(string[] mappingAssemblies, AutoPersistenceModel autoPersistenceModel, Configuration cfg, IPersistenceConfigurer persistenceConfigurer)
{
FluentConfiguration fluentConfiguration = Fluently.Configure(cfg);
if (persistenceConfigurer != null)
{
fluentConfiguration.Database(persistenceConfigurer);
}
fluentConfiguration.Mappings(m =>
{
foreach (var mappingAssembly in mappingAssemblies)
{
var assembly = Assembly.LoadFrom(MakeLoadReadyAssemblyName(mappingAssembly));
m.HbmMappings.AddFromAssembly(assembly);
m.FluentMappings.AddFromAssembly(assembly).Conventions.AddAssembly(assembly);
}
if (autoPersistenceModel != null)
{
m.AutoMappings.Add(autoPersistenceModel);
}
});
return fluentConfiguration.BuildSessionFactory();
}
This error happens when you do the query. Looking to your code I see only one thing that might cause the problem - that is MatchingCriteria:
return candidates.Where(this.MatchingCriteria)...
What type is stored in this.MatchingCriteria?
Try to replace it with inline conditions: in
..Where(<put_inline_criteria_here>)..
I'm trying to specify a unique column for an entity, using the Fluent NHibernate Automapper Override. For my test class of CodeType, I'd like to make the Type property unique. The goal would be for a "new CodeType()" being created with the same type field as a currently saved CodeType to be overlaid on top of the current entity.
I have the following CodeType class:
public class CodeType : SecurableEntity
{
public virtual string Type { get; set; }
public virtual string Description { get; set; }
/// <summary>
/// This is a placeholder constructor for NHibernate.
/// A no-argument constructor must be available for NHibernate to create the object.
/// </summary>
public CodeType() { }
}
I have the following CodeTypeMap Class:
public class CodeTypeMap : IAutoMappingOverride<CodeType>
{
public void Override(AutoMapping<CodeType> mapping)
{
//Doesn't work. Need a way to specify a column as unique.
mapping.Map(m => m.Type).Unique();
}
}
The override is applied to the AutoMap, through the following:
public AutoPersistenceModel Generate()
{
var mappings = AutoMap.AssemblyOf<User>(new AutomappingConfiguration());
mappings.IgnoreBase<Entity>();
mappings.IgnoreBase<SecurableEntity>();
mappings.IgnoreBase(typeof(EntityWithTypedId<>));
mappings.Conventions.Setup(GetConventions());
mappings.UseOverridesFromAssemblyOf<AutoPersistenceModelGenerator>();
mappings.UseOverridesFromAssemblyOf<UserMap>();
mappings.UseOverridesFromAssemblyOf<CodeMap>();
mappings.UseOverridesFromAssemblyOf<CodeTypeMap>();
return mappings;
}
I'd like the following code to update any existing record with "type" equal to "existingType".
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
CodeType ct = new CodeType();
ct.type = "existingType";
ct = ctr.SaveOrUpdate(ct);
How can I make NHibernate key off of the type field as unique?
Is this possible?
short answer, what you want is something you have to handle in code because there are so many possibilities. Everytime you create a new CodeType you have to check the db if there is already one
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
CodeType ct = ctr.GetByType("existingType");
if (ct == null)
{
ct = new CodeType { type = "existingType" };
}
ctr.SaveOrUpdate(ct);
or
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
CodeType ct = ctr.GetByType("existingType");
if (ct != null)
{
ctr.Detach(ct);
ctr.Merge(new CodeType{ type = "existingType" });
}
or
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
int ctId = ctr.GetIdByType("existingType");
if (ct != 0)
{
ctr.Merge(new CodeType{ Id = ctId, type = "existingType" });
}
and there are some things which can be written differently
public CodeType() { } can be removed or made protected CodeType() { } if not needed for your domain
public AutoPersistenceModel Generate()
{
return AutoMap.AssemblyOf<User>(new AutomappingConfiguration())
.IgnoreBase<Entity>()
.IgnoreBase<SecurableEntity>()
.IgnoreBase(typeof(EntityWithTypedId<>))
.Conventions.Setup(GetConventions())
.UseOverridesFromAssemblyOf<AutoPersistenceModelGenerator>();
}
I'm attempting to map a database field ("LS_RECNUM") possible values of NULL, 'M' and 'F' to a property with a Gender enumeration type.
The mapping looks like this:
Map(x => x.Gender).Column("LS_GENDER").Access.Using<GenderPropertyAccessor>();
...and the GenderPropertyAccessor class looks like this:
using System;
using System.Collections;
using System.Reflection;
using Kctc;
using NHibernate.Engine;
using NHibernate.Properties;
public class GenderPropertyAccessor : IPropertyAccessor
{
#region Setter
private class GenderGetterSetter : IGetter, ISetter
{
PropertyInfo _property;
public GenderGetterSetter(PropertyInfo property)
{
if (property == null) throw new ArgumentNullException("property");
if (property.PropertyType != typeof(Gender)) throw new ArgumentException("property");
_property = property;
}
public void Set(object target, object value) //Convert string to enum
{
_property.SetValue(target, GetGenderFromString(value), null);
}
public object Get(object target) //Convert enum back to string
{
Gender gender = (Gender)_property.GetValue(target, null);
return SetGenderToString(gender);
}
/// <summary>
/// Interprets the supplied string as a gender.
/// </summary>
/// <param name="strGender">The gender as either 'F' or 'M'.</param>
/// <returns></returns>
private Gender GetGenderFromString(object strGender)
{
if (strGender == null) return Gender.Unknown;
switch (strGender.ToString().ToLower())
{
case "f":
return Gender.Female;
case "m":
return Gender.Male;
default:
return Gender.Unknown;
}
}
/// <summary>
/// Sets the supplied Gender to the appropriate 'M' or 'F' value.
/// </summary>
/// <param name="objGender">The gender.</param>
/// <returns></returns>
private string SetGenderToString(object objGender)
{
Gender gender = (Gender) objGender;
switch (gender)
{
case Gender.Female:
return "F";
case Gender.Male:
return "M";
default:
return null;
}
}
public MethodInfo Method
{
get { return null; }
}
public string PropertyName
{
get { return _property.Name; }
}
public object GetForInsert(object owner, IDictionary mergeMap, ISessionImplementor session)
{
return Get(owner);
}
public Type ReturnType
{
get { return typeof(byte[]); }
}
}
#endregion
public IGetter GetGetter(Type theClass, string propertyName)
{
return new GenderGetterSetter(theClass.GetProperty(propertyName));
}
public ISetter GetSetter(Type theClass, string propertyName)
{
return new GenderGetterSetter(theClass.GetProperty(propertyName));
}
public bool CanAccessThroughReflectionOptimizer
{
get { return false; }
}
}
Not being particularly familiar with reflection, I'm not at all sure that the Get and Set methods have been implemented correctly.
When I try this, I still get an error 'Can't parse F as Gender'. I've tried debugging the GenderPropertyAccessor class. The relevant line (shown above) in the mapping file is executing correctly, as is the constructor for the GenderGetterSetter class, but the Get and Set methods are never called!!!
Can anyone tell me what I might be doing wrong?
I would use an implementation of IUserType for this. Here's a good simple example. In the NullSafeGet and NullSafeSet methods you will mutate the string to an enum and back, respectively. It's also critical that your Equals implementation is correct in order for NHibernate to detect changes.
Mapping the property to use a custom user type is easy:
Map(x => x.Gender).Column("LS_GENDER").CustomType(typeof(MyUserType));
I am trying to capture links that were added to a work item in TFS by catching WorkItemChangedEvent via TFS services. Here is the relevant XML part of the message that comes through:
<AddedRelations><AddedRelation><WorkItemId>8846</WorkItemId></AddedRelation></AddedRelations>
This is declared as a field in WorkItemChangedEvent class that should be deserialized into object upon receiving the event:
public partial class WorkItemChangedEvent
{
private string[] addedRelations;
/// <remarks/>
[XmlArrayItemAttribute("WorkItemId", IsNullable = false)]
public string[] AddedRelations
{
get { return this.addedRelations; }
set { this.addedRelations = value; }
}
}
I cannot figure out why the AddedRelations does not get deserialized properly.
I can only suspect that the object structure does not match the XML schema.
I have changed the structure of my WorkItemChangedEvent class a little bit to match the XML:
public partial class WorkItemChangedEvent
{
private AddedRelation[] addedRelations;
/// <remarks/>
[XmlArrayItemAttribute("AddedRelation", IsNullable = false)]
public AddedRelation[] AddedRelations
{
get { return this.addedRelations; }
set { this.addedRelations = value; }
}
[GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[SerializableAttribute()]
[DebuggerStepThroughAttribute()]
[DesignerCategoryAttribute("code")]
[XmlTypeAttribute(Namespace = "")]
public partial class AddedRelation
{
#region Fields
private string workItemId;
#endregion
/// <remarks/>
public string WorkItemId
{
get { return this.workItemId; }
set { this.workItemId = value; }
}
}
}
I still think that there must be some logic behind the original solution since it was designed by TFS authors (MS)? Anyway I am glad it works now and that I answered my question first ;]
Is there a standard convention when designing business objects for providing consumers with a way to discover constraints such as a property's maximum length?
It could be used up in the UI layer to, for example, set a Textbox's MaxLength property according to the maximum length limit back in the business object.
Is there a standard design approach for this?
Validation frameworks often contain parts for integrating with UI technologies in communicating the errors. Microsoft Enterprise Library Validation Application Block for instance contains a ValidationProvider extender control for WinForms that binds with the WinForms ErrorProvider control.
Your wish is different though. You want to communicate the constraints before they turn in to errors. Because this is not a standard requirement, I don't believe most validation frameworks have something for this out of the box. However, depending on the chosen framework creating this might be achievable. The Validation Application Block for instance, allows you to analyze the rules that you have registered / configured on a entity. So it is possible to build a control that will do this for you.
[Edit]
What you could also do is validate a form immediately upon startup and after each keystroke. This causes error icons or messages to show up immediately, which allows users to directly see what the constraints are (when you use icons, the user can hover an icon to see the error message). This isn't perhaps as nice as creating your own control, but it much easier to implement.
I have my own validation framework that lets me validate each field with the help of designated ValidationAttribute. It uses Attributes to automate most of the validations.
A sample business object would look like this in my application.
Each business object would inherit from EntityBase abstract class that has a public method called "Validate()". When this method is called on the given instance of the business object it will iterate through all properties of its own having Attributes that are derived from ValidationAttribute can call ValidationAttriubte's IsValid method to validate the value of associated proerty and return true/false with err. msg if any.
User.cs
[TableMapping("Users")]
public class User : EntityBase
{
#region Constructor(s)
public AppUser()
{
BookCollection = new BookCollection();
}
#endregion
#region Properties
#region Default Properties - Direct Field Mapping using DataFieldMappingAttribute
private System.Int32 _UserId;
private System.String _FirstName;
private System.String _LastName;
private System.String _UserName;
private System.Boolean _IsActive;
[DataFieldMapping("UserID")]
[DataObjectFieldAttribute(true, true, false)]
[NotNullOrEmpty(Message = "UserID From Users Table Is Required.")] // VALIDATION ATTRIBUTE
public override int Id
{
get
{
return _UserId;
}
set
{
_UserId = value;
}
}
[DataFieldMapping("UserName")]
[Searchable]
[NotNullOrEmpty(Message = "Username Is Required.")] // VALIDATION ATTRIBUTE
public string UserName
{
get
{
return _UserName;
}
set
{
_UserName = value;
}
}
[DataFieldMapping("FirstName")]
[Searchable]
public string FirstName
{
get
{
return _FirstName;
}
set
{
_FirstName = value;
}
}
[DataFieldMapping("LastName")]
[Searchable]
public string LastName
{
get
{
return _LastName;
}
set
{
_LastName = value;
}
}
[DataFieldMapping("IsActive")]
public bool IsActive
{
get
{
return _IsActive;
}
set
{
_IsActive = value;
}
}
#region One-To-Many Mappings
public BookCollection Books { get; set; }
#endregion
#region Derived Properties
public string FullName { get { return this.FirstName + " " + this.LastName; } }
#endregion
#endregion
public override bool Validate()
{
bool baseValid = base.Validate();
bool localValid = Books.Validate();
return baseValid && localValid;
}
}
BookCollection.cs
/// <summary>
/// The BookCollection class is designed to work with lists of instances of Book.
/// </summary>
public class BookCollection : EntityCollectionBase<Book>
{
/// <summary>
/// Initializes a new instance of the BookCollection class.
/// </summary>
public BookCollection()
{
}
/// <summary>
/// Initializes a new instance of the BookCollection class.
/// </summary>
public BookCollection (IList<Book> initialList)
: base(initialList)
{
}
}
Custom Attributes might serve your need.