If I have some classes that implement the same interface, so they all contain the same property. Is there a way to add a formatter to these properties? I only found the possibility to add a formatter to a specific property type.
Here's some code that should clarify what I mean:
public interface ITaggable
{
IList<string> Tags { get; set; }
}
public class Post : ITaggable
{
public IList<string> Tags { get; set; }
public IList<string> Categories { get; set; }
...
}
public class Page : ITaggable
{
public IList<string> Tags { get; set; }
....
}
I'd like to map these to view models that look something like this:
public class PostViewModel
{
public string Tags { get; set; }
public IList<string> Categories { get; set; }
...
}
public class PageViewModel
{
public string Tags { get; set; }
...
}
If a Post has the tags "foo" and "bar", then the PostViewModel's Tags property should contain the string "foo, bar". Categories should remain an IList<string>.
I could accomplish this by creating a custom formatter and then add it on every mapping, like this:
protected override void Configure()
{
CreateMap<Post, PostViewModel>()
.ForMember(x => x.Tags, opt => opt.AddFormatter<TagsFormatter>());
CreateMap<Page, PageViewModel>()
.ForMember(x => x.Tags, opt => opt.AddFormatter<TagsFormatter>());
}
But I'd like to do something like this (this code doesn't work ;-)
protected override void Configure()
{
ForSourceType<ITaggable>()
.ForMember(x => x.Tags, opt => opt.AddFormatter<TagsFormatter>());
CreateMap<Post, PostViewModel>();
CreateMap<Page, PageViewModel>();
}
It's not exactly an answer to the question I was asking, but a solution to the problem:
I built a TagsFormatter that checks the name of the property for "Tags":
public class TagsFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
if (context.MemberName.Equals("Tags", StringComparison.InvariantCultureIgnoreCase))
{
var tags = context.SourceValue as IList<string>;
if (tags != null)
return String.Join(", ", tags);
}
return context.SourceValue.ToString();
}
}
In the configuration I can then register this Formatter for all mappings:
public class ViewModelProfile : AutoMapper.Profile
{
protected override void Configure()
{
ForSourceType<IList<string>>().AddFormatter<TagsFormatter>();
CreateMap<Post, PostViewModel>();
CreateMap<Page, PageViewModel>();
}
}
Related
I am using Entity Framework 6 Code First for my project.
Entities have Inheritance so I am following TPH(Table per Hierarchy).
I read following Article and many others.
None of them explain a way in which I can use an existing DB Column mapped to a property in Base Entity as Discriminator.
Based on the sample below I get following Exception
One or more validation errors were detected during model generation:
TaskType: Name: Each property name in a type must be unique. Property name 'TaskType' is already defined.
I think EF's auto generated Discriminator and my Entities Mapping is Conflicting.
Is there a possible way to instruct EF to not auto generate column and use Entity mapped Column.
If not, is there any explanation of this can not be avoided.
Peace.
I have Entities in following format
public enum TaskType
{
Random = 0,
Polished = 1,
Dropping = 2
}
public interface ITask
{
int Id { get; set; }
string Name { get; set; }
TaskType typeofTask { get; set; }
}
public abstract class BaseTask : ITask
{
public BaseTask(string name, TaskType type)
{
this.Name = Name;
this.typeofTask = type;
}
public int Id { get; set; }
public string Name { get; set; }
public TaskType typeofTask { get; set; }
}
public class RandomTask : BaseTask
{
public RandomTask() : base("My Random", TaskType.Random)
{
}
public int Owner { get; set; }
}
public class PolishedTask : BaseTask
{
public PolishedTask() : base("My Polished", TaskType.Polished)
{
}
}
public class DBContextTest : DbContext
{
public DBContextTest(string connection) : base(connection)
{
}
public DbSet<BaseTask> Task { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseTask>().Map<RandomTask>(m => m.Requires("TaskType").HasValue(1))
.Map<PolishedTask>(m => m.Requires("TaskType").HasValue(1));
modelBuilder.Entity<BaseTask>().Property(p => p.typeofTask).HasColumnName("TaskType");
}
}
class Program
{
static void Main(string[] args)
{
try
{
DBContextTest dataContext = new DBContextTest("Server = (localdb)\\mssqllocaldb;DataBase = LOC2;Trusted_Connection = True;");
RandomTask randomtask = new RandomTask();
PolishedTask polishedTask = new PolishedTask();
dataContext.Task.Add(randomtask);
dataContext.Task.Add(polishedTask);
dataContext.SaveChanges();
}
catch (System.Exception ex)
{
}
}
}
Remove TaskType from your entity and let EF manage that as part of the TPH mapping. To differentiate types if you're dealing with a base-class collection, use .OfType<PolishedTask>() rather than .Where(x => x.TaskType == TaskType.Polished) EF should take care of the rest. If you do want it on the entity, create a non-mapped property in your sub-classes.
I.e.
public abstract class BaseTask
{
[NotMapped]
public abstract TaskType TaskType { get; }
}
public class PolishedTask
{
[NotMapped]
public override TaskType TaskType => TaskType.Polished
// or
//public override TaskType TaskType
//{
// get { return TaskType.Polished; }
//}
}
I have a header-child tables with the child having different types but stored in the same table (TPH).
On top of this, user can snapshot a copy of a header and its children records and I would like to store the snapped copy into a different table since these snapshot records would be less frequent to view/modify.
To achieve this, I am mixing TPC to my existing TPH.
The new structure is as follows:
public class Header
{
private IList<Child> _childs = new List<Child>();
private IList<ChildSnapshot> _childSnapshots = new List<ChildSnapshot>();
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IList<Child> Childs { get { return _childs; } }
public IList<ChildSnapshot> ChildSnapshots { get { return _childSnapshots; } }
}
public abstract class ChildBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Header")]
public int HeaderId { get; set; }
[ForeignKey("HeaderId")]
public virtual Header Header { get; set; }
}
public abstract class Child : ChildBase
{
}
public class Child1 : Child
{
}
public class Child2 : Child
{
}
public abstract class ChildSnapshot : ChildBase
{
}
public class ChildSnapshot1 : ChildSnapshot
{
}
public class ChildSnapshot2 : ChildSnapshot
{
}
And the database context:
public class TestContext : DbContext
{
public DbSet<Header> Headers { get; set; }
public DbSet<Child> Childs { get; set; }
public DbSet<ChildSnapshot> ChildSnapshots { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Header>().Map(m => m.ToTable("Header"));
modelBuilder.Entity<Child>()
.Map<Child>(m =>
{
m.MapInheritedProperties();
m.ToTable("Child");
})
.Map<Child1>(m =>
{
m.Requires("Discriminator").HasValue("Child1");
})
.Map<Child2>(m =>
{
m.Requires("Discriminator").HasValue("Child2");
});
modelBuilder.Entity<ChildSnapshot>()
.Map<ChildSnapshot>(m =>
{
m.MapInheritedProperties();
m.ToTable("ChildSnapshot");
})
.Map<ChildSnapshot1>(m =>
{
m.Requires("Discriminator").HasValue("Child1");
})
.Map<ChildSnapshot2>(m =>
{
m.Requires("Discriminator").HasValue("Child2");
});
}
}
It works perfectly after many trials and errors. However, I've got to create 2 list properties in Header class. Is it possible to have only 1 list property of ChildBase type? I got the following error when I do so.
The type 'Child' cannot be mapped as defined because it maps inherited
properties from types that use entity splitting or another form of
inheritance. Either choose a different inheritance mapping strategy so
as to not map inherited properties, or change all types in the
hierarchy to map inherited properties and to not use splitting.
Why is the behaviour determined by the type of the container list? Can't EF infer from the type of the object in the list instead?
FYI I am using EF 4.3.
I have seriously spent two work days trying to a TPH setup from Database First to Code first. The Error I get is Something like "Invalid Column Name Entity_EntityId/ Entity_Entity_Id1"
I've drawn up a very basic reproduction of the issue like so:
internal class Program
{
private static void Main(string[] args)
{
using (var context = new Context())
{
var baseClass = new Base {Name = "Test"};
context.BaseClasses.Add(baseClass);
context.SaveChanges();
var baseClasses = context.BaseClasses.ToList();
}
}
}
Context:
public class Context : DbContext
{
public Context() : base("TPH")
{
}
public DbSet<Base> BaseClasses { get; set; }
public DbSet<Derived> DervDerivedClasses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
Mapping:
public class BaseMap : EntityTypeConfiguration<Base>
{
public BaseMap()
{
HasKey(b => b.Id);
Property(b => b.Name);
HasOptional(b => b.AnotherClass)
.WithMany(b => b.Bases)
.HasForeignKey(b => b.AnotherClassId);
Map(b => b.Requires("Disc").HasValue(1));
}
}
public class DerivedMap : EntityTypeConfiguration<Derived>
{
public DerivedMap()
{
HasKey(b => b.Id);
Property(b => b.Name);
HasOptional(b => b.AnotherClass)
.WithMany(b => b.Deriveds)
.HasForeignKey(b => b.AnotherClassId);
Map(b => b.Requires("Disc").HasValue(2));
}
}
public class SecondDerivedMap : EntityTypeConfiguration<SecondDerived>
{
public SecondDerivedMap()
{
HasKey(b => b.Id);
Property(b => b.Name);
HasOptional(b => b.AnotherClass)
.WithMany(b => b.SecondDeriveds)
.HasForeignKey(b => b.AnotherClassId);
Map(b => b.Requires("Disc").HasValue(3));
}
}
Entities:
public class Base
{
public int Id { get; set; }
public string Name { get; set; }
public int? AnotherClassId { get; set; }
public AnotherClass AnotherClass { get; set; }
}
public class Derived : Base
{
}
public class SecondDerived : Base
{
}
public class AnotherClass
{
public int Id { get; set; }
public ICollection<Base> Bases { get; set; }
public ICollection<Derived> Deriveds { get; set; }
public ICollection<SecondDerived> SecondDeriveds { get; set; }
}
How can I get the table to just have a single "AnotherClassId?"
You're only supposed to have a single navigation property per entity per relationship -- and you have three (Bases, Deriveds, and SecondDeriveds). EF sees those properties and thinks there are three different one-to-many associations between AnotherClass and the various classes in the Base hierarchy.
If you want to get a collection of the related Derived entities from AnotherClass, you're supposed to use something like anotherClassEntity.Bases.OfType<Derived>().
This is in reference to the question I asked regarding how to determine when items are added to the virtual ICollection property. As suggested, I have created a custom collection which inherits from Collection as shown below
public class EntityCollection<T> : Collection<T>
{
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
}
}
This is being used as
public class DbAppointment
{
public DbAppointment()
{
exceptionOcurrences = new EntityCollection<DbExceptionOcurrence>();
}
public virtual int AppointmentId { get; set; }
public virtual string Subject { get; set; }
public virtual string Body { get; set; }
public virtual DateTime Start { get; set; }
public virtual DateTime End { get; set; }
private ICollection<DbExceptionOcurrence> exceptionOcurrences;
public virtual ICollection<DbExceptionOcurrence> ExceptionOcurrences
{
get { return exceptionOcurrences; }
set { exceptionOcurrences = value; }
}
}
The problem is the only time the overridden InsertItem method seems to get called is if I initialise the database with a custom initialiser (example code below) and override the seed method!! What am I doing wrong?
Cheers
Abs
public class ContextInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
new List<DbAppointment>
{
new DbAppointment{ Subject = "hello", Body="world", Start=DateTime.Now, End=DateTime.Now.AddMinutes(30)},
}.ForEach(a => context.Appointments.Add(a));
new List<DbExceptionOcurrence>
{
new DbExceptionOcurrence{ExceptionDate=DateTime.Now}
}.ForEach(eo => context.ExceptionOcurrences.Add(eo));
base.Seed(context);
}
}
please look at the code below.
class Program
{
static void Main(string[] args)
{
using (myContext context = new myContext())
{
Team t = new Team();
t.id = 1;
t.Name = "asd";
context.teamSet.Add(t);
context.SaveChanges();
}
}
}
public abstract class Base
{
public virtual int id { get; set; }
}
public abstract class Player : Base
{
public virtual string Name { get; set; }
public virtual int Number { get; set; }
public virtual Team team { get; set; }
[ForeignKey("team")]
public int teamId { get; set; }
}
public class Team : Base
{
public ICollection<Player> Players { get; set; }
public string Name { get; set; }
}
public class FootballPlayer : Player
{
public double Speed { get; set; }
}
public class BasketballPlayer : Player
{
public double Height { get; set; }
public double Speed { get; set; }
}
public class myContext : DbContext
{
public DbSet<Player> playerSet { get; set; }
public DbSet<Team> teamSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BaseConfiguration()).Add(new PlayerConfiguration()).Add(new TeamConfiguration()).Add(new FootballConfiguration()).Add(new BasketballConfiguration());
}
}
public class BaseConfiguration : EntityTypeConfiguration<Base>
{
public BaseConfiguration()
{
HasKey(k => k.id);
Property(p => p.id).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
public class PlayerConfiguration : EntityTypeConfiguration<Player>
{
public PlayerConfiguration()
{
Map(p=>{
p.MapInheritedProperties();
p.ToTable("Player");
});
}
}
public class TeamConfiguration : EntityTypeConfiguration<Team>
{
public TeamConfiguration()
{
Map(p =>
{
p.MapInheritedProperties();
p.ToTable("Team");
});
}
}
public class FootballConfiguration : EntityTypeConfiguration<FootballPlayer>
{
public FootballConfiguration()
{
ToTable("FootballPlayer");
}
}
public class BasketballConfiguration : EntityTypeConfiguration<BasketballPlayer>
{
public BasketballConfiguration()
{
ToTable("BasketballPlayer");
}
}
My Player class and Team Class are derived from Based Class, and FootballPlayer and BasketballPlayer are derived from Player. But in the generated database, Player table doesn't contain a FK teamId, it is only a common property. Furthermore, the FootballPlayer and BasketballPlayer tables don't contains the properties which derived from Player class. Anyone can help?
What inheritance mapping are you trying to achieve? At the moment you have TPC between Base and Player and TPT between Player and its derived types. If you want to have inherited properties in those derived types you must use TPC as well but in such case there should be no Player table in your database. To use TPC for player you must use MapInheritedProperties in their mapping configurations.