I am new to using EF6 with code-first, and I don't now how to create a correct relationship in my map.
Basically I have this entity
Menu
MenuGrupo
Menu 1 x N MenuGrupo
Somenthing, like Menu 1 X n MenuItens
These are my classes:
public class Menu
{
public Menu()
{
ListaFilhos = new List<Menu>();
}
public Int32 MenuID { get; set; }
public String Nome { get; set; }
public String Action { get; set; }
public String Controller { get; set; }
public String Url { get; set; }
public Int32? Pai { get; set; }
public Boolean? Ativo { get; set; }
public virtual List<Menu> ListaFilhos { get; set; }
}
public class MenuMap : EntityTypeConfiguration<Entidade.Menu>
{
public MenuMap()
{
HasKey(x => x.MenuID);
Property(x => x.MenuID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Pai).IsRequired();
Property(x => x.Nome).HasColumnType("varchar").HasMaxLength(100).IsRequired();
Property(x => x.Url).HasColumnType("varchar").HasMaxLength(250);
Property(x => x.Action).HasColumnType("varchar").HasMaxLength(50);
Property(x => x.Controller).HasColumnType("varchar").HasMaxLength(50);
Property(x => x.Ativo).IsRequired();
ToTable("Menu");
}
}
public class MenuGrupo
{
public MenuGrupo()
{
ListaMenu = new List<Menu>();
}
public Int32 MenuGrupoID { get; set; }
public Int32 MenuID { get; set; }
public virtual List<Menu> ListaMenu { get; set; }
}
public class MenuGrupoMap : EntityTypeConfiguration<Entidade.MenuGrupo>
{
public MenuGrupoMap()
{
HasKey(x => x.MenuGrupoID);
Property(x => x.MenuGrupoID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.MenuID).IsRequired();
ToTable("MenuGrupo");
}
}
Please have a look at Getting Started with Entity Framework 6 Code First using MVC 5. It is really very helpful and let you build your project properly.
Regarding to your situation, it would be enough to use DataAnnotations instead of Fluent API at least for starting.
Hoıpe this helps...
You're using List<Menu>, whereas you should be using ICollection<Menu>. The reason why this is necessary is pretty low-level, but suffice to say, it has to do with how Entity Framework handles relationships and things like lazy-loading. A List<T> property requires that the query be executed immediately.
Related
I am getting null values for the collection of entities nested in my top entity. How do I properly write my LINQ query so that these values aren't null??
I am using Entity Framework 7 and MVC 6 Here are my classes:
My models:
public class WorkStation
{
public Id { get; set; }
public string Name{ get; set; }
public ICollection<PersonWorkStation> PersonWorkStations{ get; set; }
}
public class Person
{
public Id { get; set; }
public string FirstName { get; set; }
public ICollection<PersonWorkStation> PersonWorkStations{ get; set; }
}
public class PersonWorkStation
{
public int Id { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
public int WorkStationId { get; set; }
public WorkStation WorkStation { get; set; }
}
My DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonWorkStation>()
.HasKey(op => new { op.Id });
modelBuilder.Entity<PersonWorkStation>()
.HasOne(pt => pt.Person)
.WithMany(p => p.PersonWorkStation)
.HasForeignKey(pt => pt.PersonId);
modelBuilder.Entity<PersonWorkStation>()
.HasOne(pt => pt.WorkStation)
.WithMany(t => t.PersonWorkStation)
.HasForeignKey(pt => pt.WorkStationId);
base.OnModelCreating(modelBuilder);
}
So with that being said, when I bring back a person, and look at the "PersonWorkStation"s collection, the WorkStation property is null. How can I bring back that entity?
Here is how I am retrieving the data:
var person = _context.Persons
.Include(p => p.PersonWorkStation)
.FirstOrDefault(p => p.Id == 1);
return person;
Again, the person.PersonWorkStations.Workstation entity is null for all items in the person.PersonWorkStations collection. How do I return this entity?
Thanks!
I have found the answer, I needed to add this line:
var person = _context.Persons
.Include(p => p.PersonWorkStation)
.ThenInclude(p => p.WorkStation)
.FirstOrDefault(p => p.Id == 1);
return person;
I have 3 classes: Book, Genre, Authors
public class Book {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual int MfRaiting { get; set; }
public virtual int PageNumber { get; set; }
public virtual IList<Genre> Genres { get; set; }
public virtual Series Series { get; set; }
public virtual Mind Mind { get; set; }
public virtual IList<Author> Authors { get; set; }
public Book() {
Genres = new List<Genre>();
Authors = new List<Author>();
}
}
public class Genre {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Book> Books { get; set; }
public Genre() {
Books=new List<Book>();
}
}
public class Author {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string SurName { get; set; }
public virtual string Biography { get; set; }
public virtual IList<Book> Books { get; set; }
public Author() {
Books=new List<Book>();
}
}
And his classMaps
public class BookMap : ClassMap<Book> {
public BookMap() {
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Genres)
.Cascade.All()
.Table("Book_Genre");
HasManyToMany(x => x.Authors)
.Cascade.All()
.Table("Book_Author");
References(x => x.Series);
HasOne(x => x.Mind).Constrained();
}
}
public class GenreMap : ClassMap<Genre> {
public GenreMap() {
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Books)
.Cascade.All()
.Inverse()
.Table("Book_Genre");
}
}
public class AuthorMap : ClassMap<Author> {
public AuthorMap() {
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.SurName);
Map(x => x.Biography);
HasManyToMany(x => x.Books)
.Cascade.All()
.Inverse()
.Table("Book_Author");
}
}
When i try write in code
var criteria = session.CreateCriteria();
criteria.CreateAlias("Genres", "genre", JoinType.LeftOuterJoin);
it works well, but when i do so
public ActionResult Index()
{
var criteria = session.CreateCriteria<Book>();
criteria.CreateAlias("Genres", "genre", JoinType.LeftOuterJoin);
criteria.CreateAlias("Authors", "author", JoinType.LeftOuterJoin);
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
}
i see exception Cannot simultaneously fetch multiple bags. How can I get result of that criteria?
Thank
I resolve this problem! I replace IList on ISet, and replace new List<"Class"> on new HashSet<"Class">
I read this problem here.
http://developer-should-know.tumblr.com/post/118012584847/jpa-fetching-strategies Only one collection that is fetched using JOIN strategy can be of type List, other collection must be of type Set. Otherwise an exception will be thrown:
HibernateException: cannot simultaneously fetch multiple bags
I'm new to ASP.Net MVC and want to create a simple Blog project, therefore I have two entity posts and categories. each post can belong to many categories and each category can belong to many posts.
Models.cs
public class Category
{
[Key]
public int CategoryId { get; set; }
public int? ParentId { get; set; }
public virtual Category Parent { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public virtual ICollection<News> News { get; set; }
public Category()
{
News = new List<News>();
}
}
public class News
{
[Key]
public int NewsId { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public string Content { get; set; }
public string Source { get; set; }
public string SourceURL { get; set; }
public string Images { get; set; }
public string Password { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
public DateTime? DeletedAt { get; set; }
public string CreatedBy { get; set; }
public string DeletedBy { get; set; }
public virtual PublishPeriod PublishPeriodId { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public News()
{
Categories = new List<Category>();
}
}
ModelsMap.cs
public class CategoryMap:EntityTypeConfiguration<Category>
{
public CategoryMap()
{
Property(one => one.Title).HasMaxLength(100).IsRequired();
HasOptional(x => x.Parent).WithMany().HasForeignKey(x => x.ParentId);
}
}
public class NewsMap:EntityTypeConfiguration<News>
{
public NewsMap()
{
Property(x => x.CreatedBy).HasMaxLength(150);
Property(x => x.DeletedBy).HasMaxLength(150);
Property(x => x.Title).IsRequired().HasMaxLength(150);
Property(x => x.Summary).IsRequired();
Property(x => x.Content).IsRequired().HasColumnType("ntext");
Property(x => x.CreatedAt).HasColumnType("datetime");
Property(x => x.Password).IsOptional().HasMaxLength(128);
Property(x => x.DeletedAt).IsOptional();
Property(x => x.ModifiedAt).IsOptional();
HasMany(x => x.Categories).WithMany(x => x.News).Map(x =>
{
x.ToTable("NewsCategories");
x.MapLeftKey("News_NewsId");
x.MapRightKey("Category_CategoryId");
});
}
}
And DB Context
public DbSet<Category> Categories { get; set; }
public DbSet<News> News { get; set; }
public DbSet<PublishPeriod> PublishPeriod { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new CategoryMap());
modelBuilder.Configurations.Add(new NewsMap());
modelBuilder.Configurations.Add(new PublishPeriodMap());
I have a create view for posts that displays categories in a list with checkboxs and each checkbox value is category's ID. How can I insert or update posts and keep relation between post and categories.
NewsController
//
// POST: /Admin/News/Create
[HttpPost]
public ActionResult Create(News news, List<string> Category)
{
ViewBag.Categories = catRepository.All.OrderBy(x => x.Title);
if (ModelState.IsValid)
{
foreach (var item in Category)
{
news.AddCategory(catRepository.Find(int.Parse(item)));
}
news.CreatedAt = DateTime.Now;
news.CreatedBy = "M.Hesabi";
newsRepository.InsertOrUpdate(news);
newsRepository.Save();
return RedirectToAction("Index");
}
else
{
return View();
}
}
UPDATE: I created a method in News Model as #DanS said and edited my controller.
I'd recommend creating a method on the News class:
public void AddCategory(Category category) {
Categories.Add(category);
category.News.Add(this);
}
From your Controller you can then add each selected Category to the News instance, and then add the News to the DbContext prior to calling SaveChanges. This may depend, however, on how your repositories make use of the context -- in that if they open their own, instead of accessing a shared context, you might have to attach the categories to the News repository's context prior to saving. Hopefully this helps...
Update
IEntityChangeTracker error:
It appears as if MVCScaffolding uses a separate context for each repository. As mentioned, having separate contexts can lead to some additional required steps. As it stands now, your categories are tracked by Context A while your news is tracked by Context B-- You could detach/attach the category entities between the two contexts, but I'd say the recommended solution would be to change your repositories to accept a shared context through their constructors.
I'm assuming that you are instantiating the repositories in the controller's constructor, rather than using dependency injection, so you would modify your constructor code to do something like the following:
myContext = new YourContextClass();
catRepository = new CategoryRepository(myContext);
newsRepository = new NewsRepository(myContext);
You would then have to add the constructors to your repositories to assign the internal context property, and finally, adjust your controller to properly dispose of the context.
protected override void Dispose(bool disposing)
{
if (disposing)
myContext.Dispose();
base.Dispose(disposing);
}
I get an cast exception when i am trying to insert an entity in Entity Framework (using code-first).
The cast exception is like "impossible to cast ...Collection'1(Entity) to type (Entity)"
From this code :
public virtual T Insert(T entity)
{
return Context.Set<T>().Add(entity);
}
I can't figure out why. I am pretty sure ive done everything right.
Post entity
public class Post
{
public long PostId { get; private set; }
public DateTime date { get; set; }
[Required]
public string Subject { get; set; }
public User User { get; set; }
public Category Category { get; set; }
[Required]
public string Body { get; set; }
public virtual ICollection<Tag> Tags { get; private set; }
public Post()
{
Category = new Category();
if (Tags == null)
Tags = new Collection<Tag>();
}
public void AttachTag(string name, User user)
{
if (Tags.Count(x => x.Name == name) == 0)
Tags.Add(new Tag {
Name = name,
User = user
});
else
throw new Exception("Tag with specified name is already attached to this post.");
}
public Tag DeleteTag(string name)
{
Tag tag = Tags.Single(x => x.Name == name);
Tags.Remove(tag);
return tag;
}
public bool HasTags()
{
return (Tags.Count > 0);
}
}
Tag entity
public class Tag
{
public long TagId { get; private set; }
public string Name { get; set; }
// Qui a ajouté le tag ?
public User User { get; set; }
}
Mapping
public class PostMap: EntityTypeConfiguration<Post>
{
public PostMap()
{
ToTable("Posts");
HasKey(x => x.PostId);
Property(x => x.Subject)
.HasColumnType("varchar")
.HasMaxLength(256)
.IsRequired();
Property(x => x.Body)
.HasColumnType("text")
.IsRequired();
HasMany(x => x.Tags);
HasOptional(x => x.Tags);
}
}
class TagMap : EntityTypeConfiguration<Tag>
{
public TagMap()
{
ToTable("Tags");
HasKey(x => x.TagId);
Property(x => x.Name)
.HasColumnType("varchar")
.HasMaxLength(256)
.IsRequired();
HasRequired(x => x.User);
}
}
Thanks a lots.
Please make sure that you have passed a single element to the Insert method, not a collection containing a single element.
I found the solution.
Here the correct mapping scenario for Post :
public PostMap()
{
ToTable("Posts");
HasKey(x => x.PostId);
Property(x => x.Subject)
.HasColumnType("varchar")
.HasMaxLength(256)
.IsRequired();
Property(x => x.Body)
.HasColumnType("text")
.IsRequired();
HasRequired(x => x.User);
HasMany(x => x.Tags).WithOptional();
}
It is important to specify the the Tags collection is optional. Which is the case in this scenario. A Post can have zero tags attached to it.
HasMany(x => x.Tags).WithOptional();
I am trying to model out a album that has a collection of photos. Each Album will have a collection of Photos and a Photo that is a thumb. This is what I have but EF does not seem to like it:
public class Album : IEntity {
private DateTime _dateCreated;
public Album() {
_dateCreated = SystemTime.Now();
Photos = new List<Photo>();
}
public long Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public DateTime DateCreated
{
get { return _dateCreated; }
set { _dateCreated = value; }
}
public virtual Site Site { get; set; }
public virtual Photo Thumbnail { get; set; }
public virtual ICollection<Photo> Photos { get; set; }
}
public class Photo : IEntity {
public Photo() {
_dateCreated = SystemTime.Now();
}
private DateTime _dateCreated;
public long Id { get; set; }
public string Caption { get; set; }
public string FileName { get; set; }
public DateTime DateCreated
{
get { return _dateCreated; }
set { _dateCreated = value; }
}
public virtual Album Album { get; set; }
}
public class AlbumMap : EntityConfiguration<Album>
{
public AlbumMap()
{
HasKey(x => x.Id);
Property(x => x.Id).IsIdentity();
Property(x => x.Location).IsVariableLength().HasMaxLength(80);
Property(x => x.Name).IsVariableLength().HasMaxLength(80).IsRequired();
Property(x => x.DateCreated);
MapSingleType(a => new
{
a.Id,
SiteId = a.Site.Id,
ThumbnailId = a.Thumbnail.Id,
a.Location,
a.Name,
a.DateCreated
}).ToTable("Albums");
}
}
public class PhotoMap : EntityConfiguration<Photo>
{
public PhotoMap()
{
HasKey(x => x.Id);
Property(x => x.Id).IsIdentity();
Property(x => x.FileName).IsVariableLength().HasMaxLength(255).IsRequired();
Property(x => x.Caption).IsVariableLength().HasMaxLength(255);
Property(x => x.DateCreated);
MapSingleType(a => new
{
a.Id,
SiteAlbumId = a.Album.Id,
a.FileName,
a.Caption,
a.DateCreated
}).ToTable("AlbumPhotos");
}
}
Am I missing something or does this look right? I am expecting EF to generate a 1 to many in my database but it keeps creating a reference table between Album and Photos (Album_Photos) but this should not be a many-to-many. Any help would be great.
By convention, EF code first does not create a link table in typical one to many scenario, the reason that you are getting it is because your associations between Album and Photo objects has been taken by EF as being a kind of many to many association:
Each album has a collection of Photos and also each Photo has a collection of Albums that this photo is a thumbnail for (although the related navigation property is not explicitly specified on Photo class and only Album has a Thumbnail property).
Solution:
As of EF CTP4, the only way to fix this is by leveraging Fluent API, but before that, I modify your model a little bit and add two explicit FKs to your model to give you ultimate flexibility to work with your objects. They are AlbumId on Photo and ThumbnailId on Album:
public class Photo {
public long Id { get; set; }
public string Caption { get; set; }
public string FileName { get; set; }
public DateTime DateCreated { get; set; }
public long AlbumId { get; set; }
public virtual Album Album { get; set; }
}
public class Album {
public long Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public DateTime DateCreated { get; set; }
public long ThumbnailId { get; set; }
public virtual Photo Thumbnail { get; set; }
public virtual ICollection<Photo> Photos { get; set; }
}
public class PhotoMap : EntityConfiguration<Photo> {
public PhotoMap()
{
this.HasRequired(p => p.Album)
.WithMany(a => a.Photos)
.HasConstraint((p, a) => p.AlbumId == a.Id);
Property(x => x.FileName).IsVariableLength().HasMaxLength(255)
.IsRequired();
Property(x => x.Caption).IsVariableLength().HasMaxLength(255);
MapSingleType(p => new {
p.Id,
SiteAlbumId = p.AlbumId,
p.FileName,
p.Caption,
p.DateCreated
})
.ToTable("Photo");
}
}
public class AlbumMap : EntityConfiguration<Album> {
public AlbumMap()
{
this.HasRequired(a => a.Thumbnail)
.WithMany()
.WillCascadeOnDelete(false)
.HasConstraint((a, p) => p.Id == a.ThumbnailId);
Property(x => x.Location).IsVariableLength().HasMaxLength(80);
Property(x => x.Name).IsVariableLength().HasMaxLength(80).IsRequired();
MapSingleType(a => new {
a.Id,
a.ThumbnailId,
a.Location,
a.Name,
a.DateCreated
})
.ToTable("Album");
}
}
This results to the following desired schema: