Fluent Nhibernate List<string> mapping - c#

I have a simple class with a IList<string> property. How to map this property in Fluent Nhibernate ?
[Serializable]
public class ExportTask
{
private IList<string> _csvExportList = new List<string>();
public ExportTask()
{}
public virtual IList<string> CsvExportList
{
get { return _csvExportList; }
set { _csvExportList = value; }
}
}
public class ExportTaskMap : SubclassMap<ExportTask>
{
public ExportTaskMap()
{
HasMany(x => x.CsvExportList)
.Element("CsvExportList")
.Cascade
.AllDeleteOrphan();
}
}
Following error occurs:
Initializing -failed to lazily initialize a collection of role: MyApp.Tasks.ExportTask.CsvExportList, no session or session was closed
When calling addrange on the collection:
var exportList = new List<string>()
{
{"item1"},
{"item2"}
};
CsvExportList.AddRange(exportList);

It truns out we can use AsList mapping with a column for the list index and allworks great. I wonder why there are no answers out there for this simple usecase. Hope it helps out someone.
public class ExportTaskMap : SubclassMap<ExportTask>
{
public ExportTaskMap()
{
HasMany(x => x.CsvExportList)
.Element(#"CsvProperty")
.KeyColumn(#"ExportTask_id")
.Table(#"CsvExportProperties")
.AsList(x => x.Column(#"CsvPropertyListIndex"))
.Not.LazyLoad();
}
}
And the mapped table will look like the following in the database.

Would be helpful to see the error you get, but one thing seems to be obvious: you are missing setter of the IList<string> CsvExportList. So, mapping should target the field
HasMany<string>(Reveal.Property<string>("_csvExportList"))
Check these how to handle field mapping:
Private collection mapping in fluent nhibernate
How do I map a protected collection in Fluent NHibernate?
Or change your IList<string> to have at least protected setter (I personally would go this way) and remove the readonly setting.
private IList<string> _csvExportList;
public virtual IList<string> CsvExportList
{
get { return _csvExportList ?? (_csvExportList = new List<string>(); }
protected set { _csvExportList = value; }
}
These are hints, the exception or error you get could tell us more

Related

Automapper projection (EF) with encapsulated child collections

I use Automapper to map from EF entities to view models.
I now have this entity
public class MenuGroup : IEntity
{
public int MenuGroupId { get; set; }
protected ICollection<MenuGroupItem> _menuGroupItems { get; set; }
public IEnumerable<MenuGroupItem> MenuGroupItems { get { return _menuGroupItems; } }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
_menuGroupItems.Add(menuGroupItem);
}
}
That is an encapsulated collection, I followed instructions here to make this work: http://lostechies.com/jimmybogard/2014/05/09/missing-ef-feature-workarounds-encapsulated-collections/
So I configure it like so this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
Now the problem I get is when I try to use automapper to map my MenuGroup into a viewmodel.
I run this code: menuGroup = _context.MenuGroups.Project().To<MenuGroupEditModel>().Single(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName);
and get this error: The specified type member 'MenuGroupItems' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Now I can work with the collection, it saves correctly to the database and all is well there it's only when i want to user automapper here that it fails.
If I replace the protected ICollection and public IEnumerable with simply: public ICollection<MenuGroupItem> MenuGroupItems { get; set; } it works right away so the problem lies in automapping with my encapsulated collection.
Update: I also tried this menuGroup = _context.MenuGroups.Include(x => x.MenuGroupItems).Where(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName).Project().ToSingleOrDefault<MenuGroupEditModel>(); with no difference other than that it errored in the ToSingleOrDefault instead.
Your problem is that Automapper can't modify MenuGroupItems because there is no public setter.
Your solution is changing it to this:
public IEnumerable<MenuGroupItem> MenuGroupItems { get; set; }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
MenuGroupItems.Add(menuGroupItem);
}
After some more debugging I figured out the Config file looking like this
public MenuGroupConfiguration()
{
this.HasMany(x => x.MenuGroupAssigments).WithRequired(x => x.BelongTo).WillCascadeOnDelete(true);
this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
}
had not been included leading to that error that now makes sense.
I can add as a general tip that if you don't use auto-mapper for a query but still use your encapsulated collection remember that you have to call decompile for it to work.
like so
var menuGroupsWithType =
_context.MenuGroups.Include(x => x.MenuGroupItems).Include(x => x.MenuGroupAssigments).Where(x => x.MenuGroupAssigments.Any(y => y.AssignToAll == selectedStructureType))
.OrderBy(x => x.Name).Decompile().ToList();

AutoMapper TwoWay Mapping with same Property Name

Given these two objects
public class UserModel
{
public string Name {get;set;}
public IList<RoleModel> Roles {get;set;}
}
public class UserViewModel
{
public string Name {get;set;}
public IList<RoleViewModel> Roles {get;set;} // notice the ViewModel
}
Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?
App Config
Mapper.CreateMap<UserModel, UserViewModel>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Mapper.CreateMap<UserViewModel, UserModel>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Implementation
_userRepository.Create(Mapper.Map<UserModel>(someUserViewModelWithRolesAttached);
Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?
If the property names are identical, you should not have to manually provide a mapping:
Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Just make sure the inner types are mapped as well (RoleViewModel ↔ RoleModel)
What this means, however, is that if you change a source or destination property name, AutoMapper mappings can fail silently and cause hard to track down problems (e.g., if you changed UserModel.Roles to UserModel.RolesCollection without changing UserViewModels.Roles).
AutoMapper provides a Mapper.AssertConfigurationIsValid() method that will check all of your mappings for errors and catch misconfigured mappings. It's useful to have a unit test that runs with the build that validates your mappings for this kind of problem.
You don't need to map the properties. Just make sure that the property names match and there is a mapping defined between them.
Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Mapper.CreateMap<RoleModel, RoleViewModel>();
Mapper.CreateMap<RoleViewModel, RoleModel>();
Or with the cooler way I just found out:
Mapper.CreateMap<UserModel, UserViewModel>().ReverseMap();
Mapper.CreateMap<RoleModel, RoleViewModel>().ReverseMap();
All the other answers, are much better (which I gave an upvote to each).
But what I wanted to post here is a quick playground that you could copy and past right into LinqPad in C# program mode and play your idea's without messing with your actual code.
Another awesome thing about moving all your conversions into a TyperConverter class is that your conversions are now Unit Testable. :)
Here you will notice that the model and viewmodel are almost identical except for one property. But through this process the right property is converted to the correct property in the destination object.
Copy this code into LinqPad and you can run it with the play button after switching to C# Program mode.
void Main()
{
AutoMapper.Mapper.CreateMap<UserModel, UserViewModel>().ConvertUsing(new UserModelToUserViewModelConverter());
AutoMapper.Mapper.AssertConfigurationIsValid();
var userModel = new UserModel
{
DifferentPropertyName = "Batman",
Name = "RockStar",
Roles = new[] {new RoleModel(), new RoleModel() }
};
var userViewModel = AutoMapper.Mapper.Map<UserViewModel>(userModel);
Console.WriteLine(userViewModel.ToString());
}
// Define other methods and classes here
public class UserModel
{
public string Name {get;set;}
public IEnumerable<RoleModel> Roles { get; set; }
public string DifferentPropertyName { get; set; }
}
public class UserViewModel
{
public string Name {get;set;}
public IEnumerable<RoleModel> Roles { get; set; } // notice the ViewModel
public string Thingy { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("Name: {0}", Name));
sb.AppendLine(string.Format("Thingy: {0}", Thingy));
sb.AppendLine(string.Format("Contains #{0} of roles", Roles.Count()));
return sb.ToString();
}
}
public class UserModelToUserViewModelConverter : TypeConverter<UserModel, UserViewModel>
{
protected override UserViewModel ConvertCore(UserModel source)
{
if(source == null)
{
return null;
}
//You can add logic here to deal with nulls, empty strings, empty objects etc
var userViewModel = new UserViewModel
{
Name = source.Name,
Roles = source.Roles,
Thingy = source.DifferentPropertyName
};
return userViewModel;
}
}
public class RoleModel
{
//no content for ease, plus this has it's own mapper in real life
}
Result from the Console.WriteLine(userViewModel.ToString());:
Name: RockStar
Thingy: Batman
Contains #2 of roles
Inside the Startup.cs in the Configure() method:
Mapper.Initialize(config => {
config.CreateMap<UserModel, UserViewModel>().ReverseMap();
// other maps you want to do.
});

LINQ to Entities Casting Issues - Unable to cast object to Generic type

This is the error I'm receiving:
Message = "Unable to cast the type 'App.Models.Subject' to type
'App.Context.ITenantData'. LINQ to Entities only supports casting EDM
primitive or enumeration types."
In an attempt to implement multi-tenancy in my application, I added a Tenants table and linked every tenant-specific model to a Tenant (including Subjects).
I got a lot of help from this post: DbSet, ModelBuilder, and EF Navigation Properties
But now I'm stuck with the above casting issue.
My TenantContext:
public class TenantContext : DbContext {
private readonly RealContext _realContext;
private readonly Tenant _tenant;
public TenantContext(Tenant tenant)
: base("name=DefaultConnection") {
this._tenant = tenant;
this._realContext = new RealContext();
}
// _realContext.Subjects is a DbSet
public IQueryable<Subject> Subjects { get { return FilterTenant(_realContext.Subjects); } }
private IQueryable<T> FilterTenant<T>(IQueryable<T> values) where T : ITenantData
{
return values.Where(x => x.TenantId == _tenant.TenantId);
}
}
With ITenantData:
public interface ITenantData {
int TenantId { get; set; }
}
And Subject implements ITenantData with a TenantId property:
[ForeignKey("Tenant")]
public int TenantId { get; set; }
Now, when I query using TenantContext, I get the above error:
using (var db = CreateContext()) { // returns tenantContext
var dbSubjects = db.Subjects;
var subjects = dbSubjects.ToList(); // error occurs here
What am I doing wrong?
Also - I'm pretty new to this, so if I'm missing anything critical here, let me know and I'll post up. Thank you for any help you can provide.
Updating my TenantContext to include class fixed the problem, but I don't know why:
private IQueryable<T> FilterTenant<T>(IQueryable<T> values) where T : class, ITenantData
{
return values.Where(x => x.TenantId == _tenant.TenantId);
}
If anyone wants to write up anything about the reasoning behind this, I'll gladly accept your answer.
Here:
values.Where(x => x.TenantId == _tenant.TenantId);
translator doesnt have an idea how to translate _tenant.TenantId into SQL

Mapping list of strings in nhibernate

My entity has besides other properties Keyword property which is of type list of strings.
public virtual IList<string> Keywords { get; set; }
so I tried to map this property using conformist mapping by code approach simple as possible like this
Property(x => x.Keywords);
but I'm getting following exception
NHibernate.MappingException : Could not determine type for:
System.Collections.Generic.IList`1[[System.String, mscorlib,
Version=4.0.0.0,.....
You could map this to a private string field and then use string.Split in your Keywords getter to get a list.
public class MyClass {
private string _keywords;
public virtual IEnumerable<string> Keywords {
get { return _keywords.Split(','); }
set { _keywords = string.Join(value, ","); }
}
}
I am not familiar with mapping by code that NH uses (I use FluentNH) but your mapping would probably be something like this:
Map("_keywords", map => {
map.Access(Access.Field);
// ...
});

EF4 CTP5 How To? Map an inherited Property to a field in a related table

i defined an entity called Variable and derived classes by using Table Per Hierarchy (TPH). The Base class "Variable" contains a collection of PropertyValues:
private ICollection<PropertyValue> propertyValues;
public const string DiscriminatorColumn = "Discriminator";
public const string Table = "Variables";
public VariableType VariableType { get; set; }
public string Name { get; set; }
[NotMapped]
public string Discriminator { get; set; }
public virtual ICollection<PropertyValue> PropertyValues
{
get { return this.propertyValues ?? (this.propertyValues = new ObservableCollection<PropertyValue>()); }
set { SetProperty(ref this.propertyValues, value, () => PropertyValues); }
}
Now, i want to derive a SpecialVariable class (or more than one), which define some SpecialProperties (e.g. HighLimit) which should be mapped to an entry in the PropertyValues (table).
public class MySpecialVariabe : Variable
{
public double HighLimit { get; set; }
}
My OnModelCreating function looks currently like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Variable>().HasKey(x => new { x.Id });
modelBuilder.Entity<Variable>()
.Map<MySpecialVariabe>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe).Name))
.Map<MySpecialVariabe2>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe2).Name)).ToTable(Variable.Table);
}
Can someone give me some tips how to realize this, without writing tons of bad looking code in the derived class. (Performance is not that important.)
best regards,
Chris
You can't map properties to records. That is how I understand your question. You have some PropertyValues table which is most probably some Key/Value pair and you want to map entity properties as records (data) to this table. This is not something which EF do for you. You must provide not mapped properties which will work with correct record in propertyValues collection.
Something like:
[NotMapped]
public double HighLimit
{
get
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
return current != null ? current.Value : 0.0;
}
set
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
if (current != null)
{
current.Value = value;
}
else
{
propertyValues.Add(new PropertyValue { Key = "HighLimit", Value = value });
}
}
}
The problem with this approach is that you can't use HighLimit in Linq-to-entities queries - you must always use PropertyValues.
Moreover TPH in EF requires that properties of derived entity (MySpecialVariable) are mapped to the same table as parent entity (Variable). You can't map properties of derived entity into data stored in other table (PropertyValues).

Categories

Resources