I have a table (Commodity) which has a one-to-one relationship with another table (CommodityMaterial), in my GET endpoint the Commodity returns it's own columns and also the columns (and values) of the referenced table which works perfectly. However, in the POST operation of the endpoint, a user should not be able to POST data of the reference table (CommodityMaterial), how can this be achieved? I used to disable this by using a DataContract, however, because I need the columns for my GET operator, this is not an option.
I already tried, following this post: https://csharp.christiannagel.com/2016/11/07/efcorefields/, removing the SET on the reference table and making a backing field but this does not seem to work (error that the backing field is read-only).
I also tried setting the SET to protected, but this is not working.
So the question is, how to make the reference table read-only (only available for my GET endpoint and not my POST endpoint).
The Commodity POCO class:
[DataContract]
public class Commodity
{
public Commodity()
{
}
public Commodity(CommodityMaterial commodityMaterial)
{
CommodityMaterial = commodityMaterial;
}
[DataMember]
public long CommodityID { get; set; }
[DataMember]
public long CommodityMaterialID { get; set; }
[DataMember]
public decimal? SpecficWeight { get; set; }
[DataMember]
public CommodityMaterial CommodityMaterial { get; }
}
Fluent part:
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.CommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
The parameters your action accepts should represent what your action does/is allowed to do. If a client should not be able to update a related entity, then the class you bind the request body to, should not have that entity available. Use a view model, essentially:
public class CommodityRequest
{
// all properties you want editable
// exclude `CommodityMaterial` obviously
}
Then:
public IActionResult Update(CommodityRequest model)
Related
I am trying to get a single User, with a list of Items, mapped with a many-to-many entity UserItems. However, I am unable to retrieve the mapped Items due to to an error that I'm unable to solve (error at bottom of question). Here is my code:
public class User
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class Item
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class UserItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
public int Quantity { get; set; }
}
The UserItem class configuration has the following relationships defined:
builder.HasOne(x => x.User)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.ClientCascade);
builder.HasOne(x => x.Item)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.ItemId)
.OnDelete(DeleteBehavior.ClientCascade);
I have the following generic repo with this method:
public class GenericRepository<T> : where T : class
{
private readonly DbContext _context;
public GenericRepository(DbContext context) => _context = context;
public T Get(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] navigationProperties)
{
IQueryable<T> query = _context.Set<T>();
query = navigationProperties.Aggregate(query, (current, property) => current.Include(property));
var entity = query.FirstOrDefault(where);
return entity;
}
}
However, when I try to run the code, I get an error on the Select(x => x.Item):
var user = repo.Get(x => x.Id == 1, x => x.UserItems.Select(y => y.Item));
Error:
System.InvalidOperationException: 'The expression 'x.UserItems.AsQueryable().Select(y => y.Item)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.'
What am I doing wrong, this seems to work for my other projects?
This error Occurs because you are not passing in a navigation property (x.UserItems would be a navigation property) but rather something you want to do with the navigation property. UserItems.Select(y => y.Item) is not a property of x because Select() is a function and therefore it cannot be included.
What you are trying to do (I assume it is including UserItems and also the corresponding Items) is not going to work with your current implementation of the repository. To include navigation properties of navigation properties .ThenInclude() must be used instead of .Include() which works only for navigation properties directly defined on the Entity the DbSet is created for.
But apart from your question I would suggest not to use such an generic implementation of Repository. The main benefit from using reposiories is to separarte code related to loading and storing of entities from the rest of your code. In your case if the consumer of repository knows that navigation properties must be included and that he has to provide them - then what is the point of having a repository at all? Then the consumer again cares about database specific code which makes having a repository unneccessary. I would recommend just making a conrete "UserRepository" which can only be used to retrieve users and explicitly includes the needed properties.
This should be fairly easy but I am struggling quite a bit.
My situation is as follows:
I have a table (Commodity) with a foreign key to another table (CommodityMaterial). In my GET endpoint I want to retrieve all the column data from Commodity and all the columns from CommodityMaterial as well.
I have set it up as follows:
public class Commodity
{
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
public ICollection<OmsCommodityMaterial> OmsCommodityMaterials { get; set; }
}
The Foreign key table:
public class CommodityMaterial
{
public long? CommodityMaterialID { get; set; }
public string Name { get; set; }
public long? SortOrder { get; set; }
public Commodity Commodity { get; set; }
}
The FLUENT API:
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.OmsCommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
This creates the proper relationship, a CommodityMaterial (ID) is needed when creating a Commodity.
Now I want to get all values / columns of the Commodity table, but also the related columns of the CommodityMaterial table (CommodityMaterialID, Name and SortOrder). After reading some other posts i found out I should/could use Include to get related data, my GET operation looks as follow:
[HttpGet]
public async Task<IEnumerable<Commodity>> GetTest()
{
return await Context.Commodity.Include(x => x.OmsCommodityMaterials).ToListAsync();
}
However, this still only returns the 3 columns of the Commodity table (CommodityID, CommodityMaterialID, SpecficWeight)
Anyone has a clue what I am doing wrong?
SOLUTION
Ok, I am a bit ashamed, the problem was the fact that I was using a DataContract annotation in my Commodity POCO class, which was omitted in the code above, the OmsCommodityMaterial reference was missing a DataMember attribute thus making it's fields invisible...
Although not an answer per se maybe you would find it yourself here.
Include()/ThenInclude() (Eager loading) is one of three possible solutions. I suggest you explore the other two as well - Explicit and Lazy.
EDIT: Further investigating your code it seems a bit strange to have a collection of entities in a one-to-one relationship. Maybe you would want to use just the class and (this is important!) specify class names in < and > when calling HasOne(). If you use ReSharper, or something similar, such tool suggest name simplification, that is to get rid of explicitly specifying the types when templating fluent methods. This is the next special place when EF gives slightly different meaning to known C# features. The first one is the very well-known virtuals.
HTH
I am a bit ashamed by this, the problem was the fact that I was using a DataContract annotation in my Commodity POCO class, which was omitted in the code above, the OmsCommodityMaterial reference was missing a DataMember attribute thus making it's fields invisible...
I am having difficulty creating a join table relationship between my Identity Framework IdentityContext(the IdentityUser) and one of my other tables Let's call it Entry. The problem is, Entry is in an entirely separate context doing it's own thing as well.
What is the proper way to associate these two? Where do I define the Join Table in fluent api?
Right now, I am getting the following error.
The key {'ApplicationUserId'} contains properties in shadow state and is referenced by a relationship from 'ApplicationUser.ApplicationUserEntries' to 'ApplicationUserEntry.ApplicationUser'. Configure a non-shadow principal key for this relationship.
These are how my tables are defined.
public class ApplicationUser : IdentityUser
{
...
public virtual List<ApplicationUserEntry> ApplicationUserEntries { get; set; }
}
public class Entry
{
public int Id { get; set; }
...
public virtual List<ApplicationUserEntry> ApplicationUserEntries { get; set; }
}
And the join table as follows.
public class ApplicationUserEntry
{
public int ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int EntryId { get; set; }
public Entry Entry { get; set; }
}
For the IdentityContext I have just some generic setup for other properties
var users = modelBuilder.Entity<ApplicationUser>();
users.Property(u => u.Name).IsRequired().HasMaxLength(65);
users.Property(u => u.FirstName).HasMaxLength(32);
users.Property(u => u.LastName).HasMaxLength(32);
And in my GoalsContext I have some general setup for other unrelated stuff, and the join table defined for ApplicationUserEntry
// Entry Configuration
var entries = modelBuilder.Entity<Entry>();
entries.HasKey(e => e.Id);
entries.HasAlternateKey(e => new { e.MilestoneId, e.CategoryId, e.MetricId });
entries.Property(e => e.Value).IsRequired();
entries.Property(e => e.Locked).IsRequired().HasDefaultValue(false);
entries.ToTable("GoalsEntries");
// ApplicationUserEntry Join Table
modelBuilder.Entity<ApplicationUserEntry>()
.ToTable("GoalsApplicationUserEntry")
.HasKey(se => new { se.ApplicationUserId, se.EntryId });
modelBuilder.Entity<ApplicationUserEntry>()
.HasOne(se => se.ApplicationUser)
.WithMany(s => s.ApplicationUserEntries)
.HasForeignKey(se => se.ApplicationUserId);
modelBuilder.Entity<ApplicationUserEntry>()
.HasOne(se => se.Entry)
.WithMany(e => e.ApplicationUserEntries)
.HasForeignKey(se => se.EntryId);
Now I'm sure I'm obviously missing something but I can't figure out what. I've never attempted to create a many to many relationship between two tables that are defined in two different contexts... and not even sure if that's wise or not to do.
My ultimate goal is to be able to associate owners with Entry records, so they can only be modified by the owners, which I verify with Identity Framework.
Ideally I would just prefer a unidirectional relationship, so I can find the owner from the Entry, but I'm not intending to get a list of Entry by looking at the IdentityUser
I have the following table being created (by using DNX commands in EF7, now EF Core)
[Table("FishGrade")]
public partial class FishGrade
{
public FishGrade()
{
FishPrices = new HashSet<FishPrice>();
}
[HiddenInput]
[Column("FishGradeId")]
public int Id { get; set; }
[Column("GradeCode")]
[MaxLength(5), Required]
public string Code { get; set; }
[Column("GradeName")]
public string Name { get; set; }
public string IsActive { get; set; }
public virtual ICollection<FishPrice> FishPrices { get; set; }
}
But when it the table is created, the Code column (column named=GradeCode), is created as a 1 character long column.
I also have the following in the OnModelCreating method
modelBuilder.Entity<FishGrade>(entity =>
{
entity.Property(e => e.Code)
.HasColumnType("char");
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
entity.Property(e => e.IsActive)
.HasMaxLength(1)
.HasColumnType("char");
});
Why is this happening? How can I get the column to be created with length = 5?
You can define your model in three ways:
conventions
attributes
fluent API
Conventions are applied first, then attributes, and finally the fluent API in your model builder. Your model builder is resetting the attribute configuration.
You should try to simplify your model configuration and use always the same method.
NOTE: take into account that, if you use something like MVC client side validation, it only understands the configuration made via attributes. In all other regards it doesn't mind how you configure your EF model.
I am trying to create two many-to-many relationship maps on a Record object:
Record object that is inherited from
public class Record {
public virtual ICollection<Language> SourceLanguages { get; set; }
public virtual ICollection<Language> TargetLanguages { get; set; }
}
Second Object
public class Language
{
public int Language { get; set; }
public string Locale { get; set; }
public string LanguageName { get; set; }
public virtual ICollection<Record> Records { get; set; }
}
Map for Record
public class RecordMap : EntityTypeConfiguration<Record>
{
this.HasMany(r => r.SourceLanguages)
.WithMany(c => c.Records)
.Map(sl =>
{
sl.ToTable("SourceLanguageRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("LanguageId");
});
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
}
When I run migration on the object listed above I get the following error:
System.Data.Entity.Core.MetadataException: Schema specified is not
valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available. ...
Schema specified is not valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available.
If I comment the following line out, it will work with just one many to many map, however, it will add RecordId_Record to Language Table. Any idea why?
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
Any idea as to what I am doing wrong?
If you have 2 Many-to-Many relationships to the same table you need to create 2 separate ICollection properties in order for Entity Framework to fully pick up on what you're trying to do. You can't combine them into one, or else you'll get that lovely error that you're seeing there.