Can I specify a discriminator column with a table-per-type mapping? - entity-framework-4.1

I have a class hierarchy that I want to map across several tables using Entity Framework 4.1 Code First. It's like table-per-type (TPT) but I also want a discrimator column.
The hierarchy looks something like:
public class Event
{
public Guid Id { get; set; }
public string Code { get; set; } // discriminator
public DateTime Date { get; set; }
}
public class Party : Event
{
public int AttendeeCount { get; set; }
}
public class BirthdayParty : Party
{
public int Age { get; set; }
}
public class WeddingParty : Party
{
public string Surname { get; set; }
}
This is a pretty weak example but I hope it makes sense. There'll be an "Events" table, a "Parties" table and a table for each kind of party. However, the discriminator column ("Code") will have a known value for each kind of event, like "BIRTH" for birthday parties or "WEDDING" for wedding parties.
The idea is that if I query for just birthday parties on a given date, EF would know to add Code = 'BIRTH' to my query instead of doing a bunch of UNIONs and JOINs to work out which rows it needs.
I map my lowest-level classes like this:
var bd = modelBuilder.Entity<BirthdayParty>();
bd.ToTable("BirthdayParties");
bd.Property(p => p.Age).HasColumnName("BirthdayAge");
I now need to specify the discriminator value in there somehow. I've tried this:
modelBuilder.Entity<Event>().Map<BirthdayParty>(cfg =>
{
cfg.Requires("Code").HasValue("BIRTH");
});
... but that complains that I haven't specified the table name inside the call to Map. So I tried moving the ToTable call into there:
var bd = modelBuilder.Entity<BirthdayParty>();
bd.Property(p => p.Age).HasColumnName("BirthdayAge");
modelBuilder.Entity<Event>().Map<BirthdayParty>(cfg =>
{
cfg.Requires("Code").HasValue("BIRTH");
cfg.ToTable("BirthdayParties");
});
... and now it thinks I want a "Code" column in the "BirthdayParties" table, which is not correct. I've already told it that the "Code" column is in the "Events" table.
Is this even possible? Can I combine the use of a discriminator column with a table-per-type mapping?

Unfortunately this is not supported. Discriminator column can be used only in TPH. TPT differs entity types by mapped tables and it always produces those terrible queries. It could be nice feature so perhaps suggestion on Data UserVoice would make it implemented one day.
Update
There is already a suggestion on user voice for this titled "Discriminator column support in TPT inheritance".

I did an override on SaveChanges to accomplish something similar. I simply added an attribute onto the abstract class called Descriminator and set it based on the Concrete Class Name anytime something new is added.
public class MyContext : DbContext
{
public override int SaveChanges()
{
foreach (var item in ChangeTracker.Entries().Where(x=>x.Entity is MyAbstractClass && x.State == EntityState.Added))
{
((MyAbstractClass)item.Entity).Descriminator = item.Entity.GetType().Name;
}
return base.SaveChanges();
}
}

Related

Adding columns to joint tables with payload in MVC

Evening, I have recently gotten help making my database in MVC on SO (I'm very thankful for that). I have another question, as I'm not in graduate school yet so I don't know the best-practice, I thought someone might in my case.
I am making joint tables between two classes, a User table and a Course table. Put simply, a User can have a Course, and this is being made in my OnModelCreating method of my DbContext class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserMap());
base.OnModelCreating(modelBuilder);
}
<-- snip --> Below is my class for the mappings <-- snip -->
public class UserMap : EntityTypeConfiguration<ApplicationUser>
{
public UserMap()
{
// Primary Key
this.HasKey(m => m.Id);
// UserHasCourse
this.HasMany(m => m.Courses)
.WithMany()
.Map(n =>
{
n.MapLeftKey("UserId");
n.MapRightKey("CourseId");
n.ToTable("UserHasCourse");
});
}
}
What I want to do is be able to add more columns to the UserHasCourse. The question is, how do I do this if the table is being generated here? This will not be the only case where I need to add a column/s that isn't necessarily related to the model (ie. I want to add the columns Credits and Grade, corresponding to the number of credits a user has earned in the course with a grade). I don't want to have Credits and Grade saved in the Courses table (as they only need to be in this joint table).
I can always add in two columns in the Server Explorer, but is there a way I should do this in code (if it is better that way)?
Entity Framework can do LOTS of stuff under the covers. However, this does not necessarily mean it's a good idea to let EF "figure stuff out" for you. I use EF all the time and find the CodeFirst approach a great way to get what you want done. What I would suggest is more of a declarative approach. Tell EF exactly what you want to do.
//Models
public class User
{
public int UserId {get;set;}
public int CourseId {get;set;}
public virtual Course Course {get;set;}
}
public class Course
{
private ICollection<User> _users;
public int CourseId {get;set;}
public string CustomPropOne {get;set;}
public string CustomPropTwo {get;set;}
// mark this as virtual if you want to enable lazy loading
public ICollection<User> Users
{
get{ return _users ?? ( _users = new List<User>()); }
protected set{ _users = value;}
}
}
//Mapping Classes
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
HasKey(u => u.UserId);
HasRequired(u => u.Course)
.WithMany(c => c.Users)
.HasForeignKey(u => u.CourseId);
}
}
//etc.
By declaring EXACTLY what you want to happen, you ensure your db is setup correctly and it becomes trivial to change/update your db model.
--

Save DropDownListFor multiple selected values

How can I save data for a multiple DropDownListFor? I have this code but the selected items aren't reaching the controller.
My code view:
#Html.DropDownListFor(model => model.Ingredients, new SelectList(Model.ListIngredients, "Id", "Description"), "Select the ingredients", new { multiple = "multiple", })
My model:
public ICollection<Ingredient> Ingredients { get; set; }
public ICollection<Ingredient> ListIngredients{ get; set; }
Ingredient.cs:
public string Description { get; set; }
public int Id { get; set; }
I have to change the name and id of my helper for data to be saved?
You are trying to bind the selected values into a collection of Ingredients.
However if you take a look at the posted values, they will look something like this:
...Ingredients=1&Ingredients=2...
That means the model binder will not know how to convert values like 1,2 (which are the ingredient Ids) into instances of the ingredient class.
You need to change the property of your view model where you will get the ingredients selected by the user, so instead of a collection of Ingredients:
public ICollection<Ingredient> Ingredients { get; set; }
you have a collection of Ids (which is the property you are using as the value of the multi-select), for example:
public int[] SelectedIngredients { get; set; }
(I have renamed the property but that is not strictly needed, I have done it just for clarity)
As a suggestion, I would remove the default option value from that multi-select (The "Select the ingredients" option) and use a label or tooltip. Otherwise it may look like any other value from the list and you will have to deal with the scenario where the user selects that option, which is not a valid ingredient.
Even better, I would also use the #Model.ListBoxFor helper as that will let everybody know they are dealing with a multi-select. Otherwise (with the drop down helper) you may not realize it is a multi-select unless you look at the attributes. Something like:
#Html.ListBoxFor(m=>m.SelectedIngredients, new SelectList(Model.ListIngredients, "Id", "Description"))
Of course, these are just suggestions. You can happily Ignore them if they don't apply to your requirements!
As a final comment, it is possible to convert the posted values into Ingredient objects as you initially had in your view model. That would require you to write your own model binder and configure it for the Ingredient type. However you will only receive the id in the posted values, so you would have to retrieve the other properties (from the database probably) or you will have a collection of Ingredient objects where only the Id is populated. I would not go down this route unless you really need to.

After saving object using Entity Framework to MySQL the object id still shows as 0

I am using Entity Framework and have a connection to a MySQL database. The id column is set to use StoreGeneratedPattern Identity and the column in the database has been set to auto-increment. When I create a new object and save it to the database, the item posts correctly in the database. However, after saving, the id of the object in C# remains 0 rather than reflecting the value than was assigned by the database.
The section of code is given below:
Group newGroup = new Group("MyGroupName", "Active");
dbContext.Groups.Add(newGroup);
dbContext.SaveChanges();
int testId = newGroup.id;
Even though "newGroup" saves in the database with a database-assigned id, when I read the id (such as I do when reading testId) the id is still 0.
Based on this, I have tried adding
dbContext.Entry(newGroup).Reload();
after SaveChanges() and I have also tried (based on this and this) adding
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
objectContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, newGroup);
after SaveChanges() in an attempt to refresh the object (and thus the id) from the database, yet the problem remains. How can I get the id that was assigned by the database?
EDIT: Adding class definition for Group:
[Table("groups")]
public partial class Group
{
public Group()
{
this.user_groups = new HashSet<UserGroup>();
}
public long id { get; set; }
public string name { get; set; }
public string status { get; set; }
public System.DateTime created_at { get; set; }
public System.DateTime updated_at { get; set; }
public virtual ICollection<UserGroup> user_groups { get; set; }
}
Try decorating your id with the [Key] attribute.
It SHOULD be this attribute
[DatabaseGenerated(DatabaseGenerationOption.Identity)]
However, this SHOULD be the default.
The [Key] attribute, should be unnecessary since the column name Id is magical... Although this might only be the case when using the accepted naming convention for C#.
I wonder if it might be the long that your id property is typed, or possibly the naming convention... you could try naming it Id.
I'm having the same problem with my project. What I did for a work around was to order the table (group) by the ID descending and select the first or default record, then select the ID column.
var newID = dbcontext.Groups.OrderByDescending(x => x.id).FirstOrDefault().ID
You can then assign that to whatever you need and save changes again. I know it's an old thread but hopefully this helps. Seems like there should be a better way to do it...

How to model many-to-many relationships with a relationship entity in EF 4.1 Code First

Popular example: In the issue tracker JIRA, issues can be linked to other issues. The link itself has some data attached, in particular a type.
Example:
Issue A -> depends on -> Issue B
Issue B <- is depended on by <- Issue A
We are introducing the same kind of relationship for an entity in our C# ASP.NET MVC application using EF 4.1 CodeFirst, and I'm wondering how to best model this relationship?
Details:
There are some particularities about this situation:
A link has some data attached, so we can't simply model a many-to-many relationship between issues and issues. We rather have to introduce a new entity Link, which represents a relationship between two issues.
A link, by definition, links two instances of the same entity, it is a "two-to-many" relationship (a link has two issues, an issue can have many links).
The link is directed, which means, if Issue A depends on Issue B, then Issue B is depended on by Issue A.
We will certainly have a Link entity that looks like this:
public class Link
{
public int ID { get; set; }
public Issue IssueA { get; set; }
public Issue IssueB { get; set; }
public LinkType Type { get; set; }
}
The Issue class might look like this:
public class Issue
{
public int ID { get; set; }
public virtual ICollection<Link> Links { get; set; }
}
Currently there would be only one link type: dependency. So, the link type would look like this:
public class LinkType
{
public int ID { get; set; }
public string ForwardName { get; set; } // depends on
public string BackwardName { get; set; } // is depended on by
}
Now for the big question:
If I want EF to automatically manage Issue.Links, I have to tell it what Foreign key on the Link table to use. Either I use IssueA, or I use IssueB. I can't use both, can I?
Either I define:
modelBuilder.Entity<Issue>().HasMany(i => i.Links).WithRequired(l => l.IssueA);
or I define:
modelBuilder.Entity<Issue>().HasMany(i => i.Links).WithRequired(l => l.IssueB);
Possible approaches - I am curious about your feedback on whether some of them will lead to troubles, cannot be implemented, or whether any of these approaches can be regarded as "best practice":
Add two Collections to the Issue, ICollection<Link> OutgoingLinks, ICollection<Link> IncomingLinks. This way the collections can be maintained by EF, but from a business logic point of view they don't make much sense.
Only add one collection and configure EF 4.1 to add incoming and outgoing links to it, if that is possible.
Only add one collection and implement it on my own:
ICollection<Link> AllLinks { return _context.Links.Where(l => l.IssueA == this || l.IssueB == this).ToList(); }
The problem with this approach is that the domain entity executes data access tasks which is bad in terms of seperation of concerns.
Any other?
Option (1) is the way to go in my opinion, together with a readonly helper perhaps which combines the two collections:
public class Issue
{
public int ID { get; set; }
public virtual ICollection<Link> OutgoingLinks { get; set; }
public virtual ICollection<Link> InComingLinks { get; set; }
public IEnumerable<Link> Links // not mapped because readonly
{
get { return OutgoingLinks.Concat(InComingLinks); }
}
}
Option (2) isn't possible because you cannot map one navigation property to two different ends/navigation properties.

EF 4.1 Code First doesn't create column for List<string>

I have been playing around quite a lot with EF4 Code First and I do love it. However, I cannot seem to sort this easy one out.
When trying to create something like this, no columns are created in my database:
public IList<String> Recievers { get; set; }
public List<String> RecieversTest { get; set; }
public virtual List<String> RecieversAnotherTest { get; set; }
public virtual ICollection<Int32> RecieversAnotherTest { get; set; }
Ive tried Annotations to map it to a different column name, I've tried IEnumerable and all sorts of other collections, but it refuses to create a column for it.
After an hour on google I found one that claims she has done it, but I'm starting to doubt that. Should it even be possible?
I can't really see why it just doesn't create a column and use JSON or CSV.
It can't be that rare, can it? In my case i just want to store a list of emails.
What am I missing? The project creates all other types without problems, and I've inspected the database to see how other properties I add to test with gets created, while these gets ignored.
So the problem must lie in some setting I'm missing or some configuration....
EF 4.1 RTW on an SQL Server 2008 db.
I have bad news for you. EF doesn't do anything like that. If you want any serialization and deserialization you must do it yourselves = you must expose and map property with serialized value:
private IList<String> _receivers;
// This will be skipped
public IList<String> Receivers
{
get
{
return _receivers;
}
set
{
_receivers = value;
}
}
// This will be mapped
public string ReceiversSer
{
get
{
return String.Join(";", _receivers);
}
set
{
_receivers = value.Split(';').ToList();
}
}
Now ReceiversSer will be mapped to a column in the database.
You can't have a column based on a collection/list of something. A column is a singular item such as public string Receiver.
If you are expecting EF CF to take your IList or List and make several Columns out of it you are correct in that it won't.
In EF CF you create lists in your Entity to represent a relationship to another table. An Order may have many Items in it. You would in this case have an Order class with a list to an OrderItem object.
You would then have an OrderItem class to describe the OrderItem table. This would then essentially represent the 1 to many relationship of Order and OrderItems.