I have a .net core 2.0 project using .netcore identity' .
I have a classApplicationUser'(aka Owner) and 3 other classes of media objects Videos, Images, and Audio ("Clips"). So far all is great. where i am hitting a wall is when i try to refactor so that rather than having to hit the DB and say something like:
user=_usermanager.getuserasync(User)
images= _context.Images.Where(i=>i.i.Owner.username== user.username)
I would like to be able to access the same thing by simply saying
user.images
which would hopefully allow me to say things like :'foreach (Audio clip in User.Clips){//do the hokeypokee//}
so i modified my ApplicationUser class to include an ICollection<Type> for each of my media classes. No matter what i do however when i access user.images etc i always get null.(tried virtual nav props as well)
rather than sitting here explaining what i THINK it is (Something im not doing with the getter and setter perhaps?) which would be confusing i figure I just let you guys tell me what direction,I should be going in because I've been down many rabbit holes now trying to figure this out. Entity framework seems in order its just something im not doing ... i cant even figure out (if not in EF somewhere) how the setter is initially defined meaning how does it know what images belong to the user without querying the database to see which images have the fk that much the user... anyway the more i type the more i confuse myself LOL.
User Class:
public class ApplicationUser : IdentityUser, ITraits
{
public int Age { get; set; }
public Ethnicity Ethnicity { get; set; }
public Color EyeColor { get; set; }
public Color HairColor { get; set; }
public int Height { get; set; }
public Race Race { get; set; }
public CuckRole CuckRole { get; set; }
public BiologicalSex BiologicalSex { get; set; }
public Sexuality Sexuality { get; set; }
public Color SkinColor { get; set; }
public int Weight { get; set; }
public DateTime BirthDay { get; set; }
public ICollection<Image> Images { get; set; }
public ICollection<Audio> Clips { get; set; }
public ICollection<Video> Videos { get; set; }
public bool Equals(Traits other) => throw new NotImplementedException();
}
and then my media class (just one all the same just different names)
public class Audio
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public ApplicationUser Owner { get; set; }
//Constructors//
public Audio()
{
}
public Audio(string path,ApplicationUser owner)
{
Path = path;
Owner = owner;
}
}
EDIT
ok so i tried to implement the suggested code as best i could and thus far this is what i've come up with :
Media Class :
public class Audio
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public ApplicationUser Owner { get; set; }
public string OwnerId { get; set; }
//Constructors//
public Audio()
{
}
public Audio(string path,ApplicationUser owner)
{
Path = path;
Owner = owner;
}
}
and my ApplicationUser Class now looks like this :
public class ApplicationUser : IdentityUser, ITraits
{
public int Age { get; set; }
public Ethnicity Ethnicity { get; set; }
public Color EyeColor { get; set; }
public Color HairColor { get; set; }
public int Height { get; set; }
public Race Race { get; set; }
public CuckRole CuckRole { get; set; }
public BiologicalSex BiologicalSex { get; set; }
public Sexuality Sexuality { get; set; }
public Color SkinColor { get; set; }
public int Weight { get; set; }
public DateTime BirthDay { get; set; }
public ICollection<Image> Images { get; set; }
public ICollection<Audio> Clips { get; set; }
public ICollection<Video> Videos { get; set; }
public bool Equals(Traits other) => throw new NotImplementedException();
}
and finally my DBContext :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Cucklist.Models.Image> Images { get; set; }
public DbSet<Cucklist.Models.Video> Videos { get; set; }
public DbSet<Cucklist.Models.Audio> Clips { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Image>().ToTable("Image")
.HasOne(Image => Image.Owner)
.WithMany(Owner => Owner.Images)
.HasForeignKey(Image => Image.OwnerId);
builder.Entity<Video>().ToTable("Video")
.HasOne(Video => Video.Owner)
.WithMany(Owner => Owner.Videos)
.HasForeignKey(Video =>Video.OwnerId );
builder.Entity<Audio>().ToTable("Clips")
.HasOne(Audio => Audio.Owner)
.WithMany(Owner => Owner.Clips)
.HasForeignKey(Audio =>Audio.OwnerId );
}
}
sadly i still get a null when accessing user.media (image,video etc)
Hey Guys thanks again for your help (#nilsK) - so the answer ended up being an entity framework "Thing".
Basically lazy loading isn't supported in Entity Framework Core until v2.1 which is in preview now (not sure if i missed making sure to specify it was entity framework core but i guessed that all assumed since it is a .net core project and you have to use Ef Core for a .net core project.) at any rate even with the Microsoft.EntityFrameworkCore updated and installed 2.1.0-previewfinal-2 it still doesn't work .
In order for it to "work" In Other words- not pull back a null when trying to access related entities you must also install the package Microsoft.EntityFramworkCore.Proxies.
Actually this is the easiest way(According to MS) to "enable" lazy loading in ef core; using the proxies. Although two other methods are available that are much more involved to implement
here is the link to the article that finally answered this question once and for all : https://learn.microsoft.com/en-us/ef/core/querying/related-data
so the short and skinny is once you installed the packages you have to (should for above option ) make all call to .UseLazyLoadingProxies() option in your DI container -- go to startup.cs and modify the DBContext options adding .UseLazyLoadingProixes() before the .UseSqlServer(...) and that's it....
now when you instantiate an entity with related entities they will be loaded on first access to the property... so basically It will stop returning a null!!!
otherwise you'd have to use implicit include statements etc... eager loading isnt and option but rather per query declarations...
Summary:
Step 1: Install Microsoft.EntityFrameworkCore preview 2.1.xx
Step 2: Install Microsoft.EntityFrameworkCore.Proxies
Step 3: Modify Services container/Startup.cs (Configure services) DBContext to include the option .UseLasyLoadingProxies
So your old code might look like :
services.AddDbContext(options =>
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("LocalConnection"))
now it might look like this :
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("LocalConnection"))
.UseLazyLoadingProxies());
Step 4 - Modify your DBContext Class to lasy load the related entities (Yes they must all be listed which is probably not optimal but im sure MS will address this)
builder.Entity<Image>().HasOne(u => u.ApplicationUser)
.WithMany(i => i.Images)
.HasForeignKey(u => u.ApplicationUserId)
.HasConstraintName
(
"FK_Image_AspNetUsers_ApplicationUserId"
);
builder.Entity<Video>().HasOne(u => u.ApplicationUser)
.WithMany(p => p.Videos)
.HasForeignKey(u => u.ApplicationUserId)
.HasConstraintName
(
"FK_Video_AspNetUsers_ApplicationUserId"
);
builder.Entity<Audio>().HasOne(u => u.ApplicationUser)
.WithMany(p => p.Audios)
.HasForeignKey(u => u.ApplicationUserId)
.HasConstraintName
(
"FK_Audio_AspNetUsers_ApplicationUserId"
);
you'll notice that im using Identity Core - in the table "AspNetUsers" there is no ApplicationUserId column so you must have the foreign key constraint in place which should already be there if you've modified your class appropriately which i will show below:
Step 5 Make sure you're classes look like this -->
ApplicationUser Class
public class ApplicationUser : IdentityUser, ITraits
{
public int Age { get; set; }
public Ethnicity Ethnicity { get; set; }
public Color EyeColor { get; set; }
public Color HairColor { get; set; }
public int Height { get; set; }
public Race Race { get; set; }
public CuckRole CuckRole { get; set; }
public BiologicalSex BiologicalSex { get; set; }
public Sexuality Sexuality { get; set; }
public Color SkinColor { get; set; }
public int Weight { get; set; }
public DateTime BirthDay { get; set; }
public virtual ICollection<Image> Images { get; set; }<--must be virtual
public virtual ICollection<Video> Videos { get; set; }<--must be virtual
public virtual ICollection<Audio> Audios { get; set; }<--must be virtual
public bool Equals(Traits other)
{
throw new NotImplementedException();
}
}
one of the media classes :
Images:
public class Image
{
//Fields and Properties//
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }<-be virtual
public virtual string ApplicationUserId { get; set; }<--must be virtual
public Image()
{
}
public Image(string path) => Path = path;
}
Step 6
in a view all you need to do is have a ApplicationUser and you can pull the current user using the httpcontext like so :
in your controller preferably although you can do this in the view
ApplicationUser user = await _usermanager.GetUserAsync(HttpContext.User);
then pass the user to the view as
return View(user)
Step 7
in your view make user the model is applicationuser
somewhere in your view try and access the media classes via the user object so
foreach (Audio clip in Model.Clips)
{
<img src="#clip.path" />
}
viola !
**Edit just a helpful read about the topic and proxies etc - ef core 2-1 whats new with lazy loading
In your DbContext-class you can override the OnModelCreating method and configure your relations here.
There are some cleaner ways, but this might be good start.
protected override void OnModelCreationg(DbModelBuilder modelBuilder)
{
// on-to-many relation example
modelBuilder.Entity<Audio>()
.HasOptional(audio => audio.Owner)
.WithMany(owner => owner.Clips)
.HasForeignKey(audio => audio.OwnerId) // this one is missing in your example
.WillCascadeOnDelete(false); // maybe true? depending on what should happen if the user gets deleted
}
You need to tell EF how to manage your relations - you will need a foreign key (Audio.OwnerId).
Another key-word for further investigations might by be ObjectEntityConfiguration.
Related
I am having an issue with EF returning NULL values within a child list. Here is my model that I am trying to get:
public class CompoundIngredient : Ingredient
{
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public string UserId { get; set; }
public CompoundIngredient()
{
MeasuredIngredients = new List<MeasuredIngredient>();
IsPublic = true;
}
}
However, when I do this:
return await _dataContext.CompoundIngredients
.Include(a => a.MeasuredIngredients)
.ThenInclude(a => a.MeasurementType)
.Include(a => a.MeasuredIngredients)
.ThenInclude(a => a.Ingredient)
.ThenInclude(a => a.IngredientType)
.FirstOrDefaultAsync(c => c.DisplayValue == name);
I get back a collection of 4 items. 2 items are populated and 2 are NULL.
Here is the data in the DB
As you can see from the picture there are 4 entries in the table, 2 of which belong to CompoundIngredientId 6 which is the ID of the ingredient who matches the Name value.
Why am I getting 4 results back, 2 of which are null?
EDIT:
So here are the models
public class CompoundIngredient : Ingredient
{
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public string UserId { get; set; }
public CompoundIngredient()
{
MeasuredIngredients = new List<MeasuredIngredient>();
IsPublic = true;
}
}
public class Ingredient
{
public int Id { get; set; }
public string DisplayValue { get; set; }
public string Description { get; set; }
public bool IsPublic { get; set; }
public IngredientType IngredientType { get; set; }
public int IngredientTypeId { get; set; }
public int CompanyId { get; set; }
public string UserName { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedDate { get; set; }
}
public class MeasuredIngredient
{
public int Id { get; set; }
public decimal Amount { get; set; }
public int MeasurementTypeId { get; set; }
public MeasurementType MeasurementType { get; set; }
public int IngredientId { get; set; }
public Ingredient Ingredient { get; set; }
public int? UseId { get; set; }
public Use Use { get; set; }
}
public class Recipe
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsPublic { get; set; }
public int RecipeCategoryId { get; set; }
public RecipeCategory RecipeCategory { get; set; }
public int SocialMediaId { get; set; }
public SocialMedia SocialMedia { get; set; }
public virtual List<TimeTemp> TimeTemps { get; set; }
public virtual List<RecipeFuel> RecipeFuels{ get;set; }
public List<MeasuredIngredient> MeasuredIngredients { get; set; }
public List<RecipeStep> RecipeSteps { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedDate { get; set; }
public Recipe()
{
MeasuredIngredients = new List<MeasuredIngredient>();
RecipeSteps = new List<RecipeStep>();
SocialMedia = new SocialMedia();
RecipeFuels = new List<RecipeFuel>();
TimeTemps = new List<TimeTemp>();
IsPublic = true;
}
}
As you can see Measured Ingredient isnt exclusive to CompoundIngredient. Recipe also has a List on it as well.
as far as configurations i dont have much
public class MeasuredIngredientConfiguration : IEntityTypeConfiguration<MeasuredIngredient>
{
public void Configure(EntityTypeBuilder<MeasuredIngredient> builder)
{
builder.Property(p => p.UseId).IsRequired(false);
}
}
public class IngredientConfiguration : IEntityTypeConfiguration<Ingredient>
{
public void Configure(EntityTypeBuilder<Ingredient> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
}
}
public class RecipeConfiguration : IEntityTypeConfiguration<Recipe>
{
public void Configure(EntityTypeBuilder<Recipe> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
}
}
here are the tables in the DB with FKs
I highly suspect the issue will stem from CompoundIngredient inheriting from Ingredient using TPH inheritance (One table with a Discriminator) then being referenced by MeasuredIngredient, and this relationship not being set up quite right. Which version of EF Core is this?
Overall the relationship between these entities/tables feels "odd". You have an ingredient, then a "compound" ingredient that is made up of one or more MeasuredIngredient. (which does not extend ingredient) A Measured ingredient contains one Ingredient, and optionally one CompoundIngredient.
Given a compound ingredient represents just a collection of measured ingredients, this just feels a bit off. It sounds like you want a recipe to contain a list of ingredients where each is associated with a measurement (The MeasuredIngredient) but that "ingredient" may be a combination of other ingredients (with associated measurements) where you may want to possibly avoid duplicating data.
I built a simple test with the core relationships in EF Core 5 and I was able to get the expected results. The important detail here was ensuring the relationship between the 3 classes (and possibly other related classes) is configured correctly. For example, cutting down the object model down to the core I came up with:
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CompoundIngredient : Ingredient
{
public virtual ICollection<MeasuredIngredient> MeasuredIngredients { get; set; } = new List<MeasuredIngredient>();
}
public class MeasuredIngredient
{
public int Id { get; set; }
publi int IngredientId { get; set; }
public virtual Ingredient Ingredient { get; set; }
}
Creating test records with the relationships you showed and running EF Core 5 I wasn't able to reproduce the issue, but I honestly did not feel comfortable with leaving EF to sort out the discriminator and relationships.
The bits I didn't like were:
CompoundIngredient extends Ingredient while containing a Many relationship to MeasuredIngredient where there is no corresponding "One" relationship on MeasuredIngredient, but it does have a "One" relationship with Ingredient.
The discriminator here is implied, not configured.
What I am more comfortable with was:
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CompoundIngredient : Ingredient
{
public virtual ICollection<MeasuredIngredient> MeasuredIngredients { get; set; } = new List<MeasuredIngredient>();
}
public class MeasuredIngredient
{
public int Id { get; set; }
public virtual Ingredient Ingredient { get; set; }
public virtual CompoundIngredient CompoundIngredient { get; set; }
}
Then explicitly mapping the relationships to ensure there is no confusion on FKs:
public class MeasuredIngredientConfiguration : IEntityTypeConfiguration<MeasuredIngredient>
{
public void Configure(EntityTypeBuilder<MeasuredIngredient> builder)
{
builder.Property(p => p.UseId).IsRequired(false);
builder.HasOne(p => p.Ingredient)
.WithMany()
.IsRequired()
.HasForeignKey("IngredientId");
builder.HasOne(p => p.CompoundIngredient)
.WithMany(p => p.MeasuredIngredients)
.IsRequired(false)
.HasForeignKey("CompoundIngredientId");
}
}
public class IngredientConfiguration : IEntityTypeConfiguration<Ingredient>
{
public void Configure(EntityTypeBuilder<Ingredient> builder)
{
builder.Property(p => p.IsPublic).HasDefaultValue(true);
builder.HasDiscriminator<string>("Discriminator")
.HasValue<Ingredient>("I")
.HasValue<CompoundIngredient>("C"); // Whichever discriminator values you want to use.
}
}
I generally do not have FKs exposed in entities for navigation properties, opting for shadow properties. This should work just as well with the FK fields mapped.
Now I had excluded this configuration and this example did work with EF Core 5. I was also trying to force a misconfiguration around possibly the CompoundIngredientId and IngredientId in the measured ingredient, but outside of generating specific configuration errors around missing assumed FKs I wasn't able to reproduce your issue. It could also be behaviour specific to the version of EF Core you are using.
You could try adding the explicit mapping to see if that solves or otherwise changes your results. Getting null entries in your collection smells like EF is trying to parse the CompoundIngredient -> MeasuredIngredient, but it is getting other measured Ingredients with the same Ingredient reference (1-2) but not the matching compound ingredient ID. It's definitely a weird one.
Otherwise I would look to temporarily eliminate all other references such as Recipe, measurement type, etc. down to the simplest possible example and data set that reproduces the problem. This becomes easier to investigate options to identify where/what is getting mixed up.
Hopefully this gives you some ideas on how to get to the bottom of the issue.
Turns out the issue is not with EF… after looking further into it EF is returning the proper counts and relations. The issue is higher up during the deserialization of the json being returned from the API. I created a new question for this
JsonSerializer.DeserializeAsync<> Creating Null Items in Collections
I have following RecipeModel, IngredientModel and RecipePartModel classes which represent the DTO classes for the frontend user:
public class RecipeModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public IEnumerable<RecipePartModel> RecipeParts { get; set; }
}
public class IngredientModel
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class RecipePartModel
{
public Guid Id { get; set; }
public IngredientModel Ingredient { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
Here are my entity classes:
public class Recipe : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class Ingredient : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class RecipePart : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public Ingredient Ingredient { get; set; }
public Recipe Recipe { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
My question is - how can I map the Recipe to RecipeModel using AutoMapper? I tried something like this but I assume it is bad, because it just join all the RecipeParts for the whole database, am I correct?
public class DomainProfile : Profile
{
public DomainProfile()
{
CreateMap<Ingredient, IngredientModel>().ReverseMap();
CreateMap<Recipe, RecipeModel>()
.ForMember(x => x.RecipeParts, opt => opt.MapFrom(src => src.RecipeParts));
}
}
To answer your question about how to use AutoMapper to map a type to another type, there are many ways of doing this. Documentation is here: http://docs.automapper.org/en/stable/Getting-started.html.
I wrote a console app and got it working in the quickest way I know possible using your code. When I debug this, and check inside recipeModel, it references a list of RecipePartModels with a single RecipePartModel. Inside that RecipePartModel, it references an IngredientModel.
static void Main(string[] args)
{
var profile = new DomainProfile();
Mapper.Initialize(cfg => cfg.AddProfile(profile));
var recipe = new Recipe
{
RecipeParts = new List<RecipePart>
{
new RecipePart()
{
Ingredient = new Ingredient()
}
}
};
var recipeModel = Mapper.Map<Recipe, RecipeModel>(recipe);
Console.ReadKey();
}
To answer your concern about getting all recipes from the database, if you're using Entity Framework, it depends on if you have lazy loading turned on. Lazy loading ensures that, when you get a recipe from the database, the recipe parts will not be loaded. They will only be loaded when you access the recipe part directly later on in the program flow. Lazy loading is turned on by default so this is the default behaviour. If you turn it off, you've enabled eager loading which loads all recipe parts and in turn their ingredient.
This might help: http://www.entityframeworktutorial.net/lazyloading-in-entity-framework.aspx.
There is nothing bad about this mapping. In fact you don't even need the ForMember call as this is the default convention. The mapping will simply convert each element in the entity child collection to a corresponding model object.
Of course, whether you load your entities in an efficient manner is another matter. If you load a large amount of Recipe entities, and lazy load the RecipeParts collections for each, you will have a major "SELECT N+1" problem. But this is not the fault of AutoMapper.
I'm having quite the issue right now while trying to learn Entity Framework.
Let's say I have this entity:
public class BuildingGroup {
public int ID { get; set; }
public string NameOfManager { get; set; }
public virtual ICollection<Building> Buildings { get; set; }
}
And also this entity.
public class Architect {
public int ID { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Building> BuildingsBeingWorkedOn { get; set; }
}
These two entities are completely unrelated. Here's the Building entity:
public class Building {
public int ID { get; set; }
public string Address { get; set; }
}
My problem happens when I try to add a building to, say a BuildingGroup. In my domain model, I can modify the equivalent collection of buildings, by adding, modifying or removing buildings. However, when I try to update BuildingGroup through a repository, the buildings will not be updated.
public void Update(BuildingGroup buildingGroup) {
var buildingGroupEntity = _context.BuildingGroups.Single(b => b.ID == buildingGroup.ID);
// This will not map the Building collection
context.Entry(buildingGroupEntity).CurrentValues.SetValues(buildingGroup);
// My attempt at mapping the buildings
buildingGroupEntity.Buildings.Clear();
buildingGroup.Buildings.ToList().ForEach(b => buildingGroupEntity.Buildings.Add(_context.Buildings.Single(x => x.ID == b.ID)));
_context.Entry(buildingGroupEntity).State = EntityState.Modified;
}
This fails if the building were not saved in the database prior to the call to Update(), which is normal since buildings can live independently. It must also be done for every child collection of BuildingGroup (if there were more), and for child collections of these children, well...
I have noticed other people use a foreign key constraint in the child object (here, Building), but I can't really do that since many unrelated entities can point to a building: I'd have a lot of navigation properties.
Is there a graceful way to manage referencing objects that can also live independently from those who hold references to them?
If all the entities have to exist independently, yet have relationships with each other, it's better to use many-to-many relationship.
Change your model classes as follows, the Building should contain a couple of collections for architects and groups.
public class BuildingGroup
{
public int ID { get; set; }
public string NameOfManager { get; set; }
public virtual ICollection<Building> Buildings { get; set; }
}
public class Architect
{
public int ID { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Building> BuildingsBeingWorkedOn { get; set; }
}
public class Building
{
public int ID { get; set; }
public string Address { get; set; }
public virtual ICollection<Architect> Architects { get; set; }
public virtual ICollection<BuildingGroup> BuildingGroups { get; set; }
}
If you use entity type configuration, you could define the relationship as follows:
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Building>().HasMany(it => it.Architects).WithMany(it => it.BuildingsBeingWorkedOn);
modelBuilder.Entity<Building>().HasMany(it => it.BuildingGroups).WithMany(it => it.Buildings);
base.OnModelCreating(modelBuilder);
}
}
Hi I have problem with EF Core insert entity. The problem is that I need to insert new entity with relation to another one which is already existing. I have created the relations with fluent API. I have done this for two times. First I am creating car and adding the last edited by field with Identity user and all works but when I am trying to do the same with another entity it crashes down with
My fluent APi code which works good:
builder.Entity<Car>()
.HasOne(x => x.Owner)
.WithMany(x => x.OwnerCars)
.HasForeignKey(x => x.OwnerId);
Here is car entity:
public class Car : CarBase
{
[Key]
public int CarId { get; set; }
public bool IsTrailer { get; set; }
public virtual TrailerType TrailerType { get; set; }
public virtual int? TrailerTypeId { get; set; }
public virtual ApplicationUser Owner { get; set; }
public virtual string OwnerId { get; set; }
}
and here is Application user entity
public class ApplicationUser : IdentityUser
{
[MaxLength(100)]
public string Address { get; set; }
public DateTime CreatedDateTime { get; set; }
public DateTime LastEditationDateTime { get; set; }
public virtual ApplicationUser LastEditedBy { get; set; }
public bool IsDeleted { get; set; }
public virtual DateTime DeletedDateTime { get; set; }
public ICollection<DriverLicenseApplicationUser> DriverLicenses { get; set; }
public ICollection<RideApplicationUser> Rides { get; set; }
public ICollection<Car> OwnerCars { get; set; }
public ICollection<Car> EditedCars { get; set; }
public ICollection<Trailer> EditedTrailers { get; set; }
public ICollection<Customer> EditedCustomers { get; set; }
}
To add this entity I only call this function and all works.
public Car CreateCar(Car car)
{
_context.Cars.Add(car);
return car;
}
But when I want to save this way this another entity type it shows an error. All steps are same so I do not understand this. Here I am adding the code I use to do that.
builder.Entity<Trailer>()
.HasOne(x => x.TrailerType)
.WithMany(x => x.Trailers)
.HasForeignKey(x => x.TrailerTypeId);
Here is Trailer:
public class Trailer : CarBase
{
[Key]
public int TrailerId { get; set; }
//[Required]
public virtual TrailerType TrailerType { get; set; }
public virtual int TrailerTypeId { get; set; }
}
and here is traylerTyper:
public class TrailerType:Trackable
{
//[Key]
public int TrailerTypeId { get; set; }
[MaxLength(100)]
[Required]
public string Type { get; set; }
public string Note { get; set; }
public ICollection<Car> TrailerTypeCars { get; set; }
public ICollection<Trailer> Trailers{ get; set; }
}
and the method is the same as the one already mentioned
public Trailer CreateTrailer(Trailer trailer)
{
trailer.TrailerTypeId = trailer.TrailerType.TrailerTypeId;
//_context.Attach(trailer.TrailerType);
var result = _context.Trailers.Add(trailer);
return result.Entity;
}
When I uncomment the attach it works but I think that I dont have to attach this because I have got the relation based on IDs and the example mentioned first works great. It gives me no sense. So if anyone could give me advice it would be awsome.
Here is the error I am getting:
Cannot insert explicit value for identity column in table 'TrailerTypes' when IDENTITY_INSERT is set to OFF.
It looks like the EF doesnt know that the traylertype entity already exists and is trying to insert the same entity again and the app crashes because it already exists and I am not allowing to insert IDs directly. As I said I have absolutely no idea why is this happening.
The problem is Lazy loading. Propetry from ViewModel is not completly same as property in Database and EF tracks whole graph of property in object and doesn´t recognize that it is the same object. The solution is to work only with IDs instead with whole objects.
I'm using EF Code First (4.3.1) on a personal ASP.NET MVC 3 project, with a very simple domain model, and I'm almost at the point where EF will generate the DB schema the way I want it to.
The domain model has two classes: Painting and Gallery. Each Painting belongs to a single Gallery, and the Gallery has two virtual properties pointing to Painting: One to indicate which of the painting is it's cover image, and one for which of the paintings is the Slider image displayed on the home page.
The classes are as follow. I've removed some annotations and irrelevant properties to make it readable.
public class Gallery
{
public Gallery()
{
Paintings = new List<Painting>();
}
[ScaffoldColumn(false)]
[Key]
public int GalleryId { get; set; }
public string Name { get; set; }
[ScaffoldColumn(false)]
[Column("LaCover")]
public Painting Cover { get; set; }
[ScaffoldColumn(false)]
[Column("ElSlider")]
public Painting Slider { get; set; }
[ScaffoldColumn(false)]
public virtual List<Painting> Paintings { get; set; }
}
and painting:
public class Painting
{
[ScaffoldColumn(false)]
[Key]
public int PaintingId { get; set; }
public string Name { get; set; }
public int GalleryId { get; set; }
[Column("GalleryId")]
[ForeignKey("GalleryId")]
[InverseProperty("Paintings")]
public virtual Gallery Gallery { get; set; }
public string Filename { get; set; }
}
It generates a correct db schema for both classes and its relationships, the only small issue I have is that I haven't found a way to control the column names it gives to the virtual properties of Cover and Slider in the Gallery table.
It'll name them Cover_PaintingId and Slider_PaintingId.
I tried using the [Column("columnNameHere")] attribute, but that doesn't affect it at all. As in "I typed a certain non related word and it didnt show up in the schema".
I'd like to name it CoverPaintingId, without the underscore.
Any help is greatly appreciated. Thanks
The ForeignKey attribute lets you define the property that will act as your foreign key if you want it to exist within your model:
[ForeignKey("CoverPaintingId")]
public virtual Painting Cover { get; set; }
public int? CoverPaintingId { get; set; }
Note you can put the attribute either on the virtual property on the foreign key - just need to specify the name of the "other one".
However, since you will have two relationships between the same set of entities, you won't be able to do this without disabling Cascading deletes on one or both of them. This can only be done using the Fluent Configuration API.
public class Gallery
{
public int GalleryId { get; set; }
[ForeignKey("CoverPaintingId")]
public virtual Painting Cover { get; set; }
public int? CoverPaintingId { get; set; }
[ForeignKey("SliderPaintingId")]
public virtual Painting Slider { get; set; }
public int? SliderPaintingId { get; set; }
}
public class Painting
{
public int PaintingId { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Gallery> Galleries { get; set; }
public DbSet<Painting> Paintins { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Gallery>().HasOptional(g => g.Cover).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<Gallery>().HasOptional(g => g.Slider).WithMany().WillCascadeOnDelete(false);
}
}
If you don't want the foreign key properties to exist in your code model, you can also configure these by using .Map(...) before the .WillCascadeOnDelete(false) part of the API instead of using ForeignKey. I prefer to use Foreign Key, but here's how the code would look if you wanted to do it this way:
public class Gallery
{
public int GalleryId { get; set; }
public virtual Painting Cover { get; set; }
public virtual Painting Slider { get; set; }
}
public class Painting
{
public int PaintingId { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Gallery> Galleries { get; set; }
public DbSet<Painting> Paintins { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Gallery>().HasOptional(g => g.Cover).WithMany().Map(m => m.MapKey("CoverPaintingId")).WillCascadeOnDelete(false);
modelBuilder.Entity<Gallery>().HasOptional(g => g.Slider).WithMany().Map(m => m.MapKey("SliderPaintingId")).WillCascadeOnDelete(false);
}
}
you could try to use [inverseproperty] decoration to achieve this.