I am populating my context for my tests. but for some reason my Guids gets overwritten upon creation of the entity.
I think there is something wrong on my context setup, but I am below average when it comes to entity framework setups.
Edit:
Seed Code:
try
{
using (var context = this.GetContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var lets = new Entities.Models.Users()
{
Id = Guid.Parse("3859e4c1-aaf7-4d9b-bc5e-8730ae9ad531"),
Name = "Test Pilot",
Organization = new Entities.Models.Organizations
{
Id = 12312
},
AppRoles = new Entities.Models.UserRequestRoles
{
AppRole = ""
}
};
context.Users.Add(lets);
context.SaveChanges();
}
}
catch (Exception e)
{
throw e;
}
User Entity:
public class Users
{
[Key]
[Column("Id")]
public Guid Id { get; set; }
// User Details
public string GivenName { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Status { get; set; }
public virtual Organizations Organization { get; set; }
[ForeignKey("Id")]
public virtual UserRequestRoles AppRoles { get; set; }
[ForeignKey("UserId")]
public virtual IEnumerable<UserOrganizations> UserOrganizations { get; set; }
}
Context builder:
builder.Entity<Users>(a =>
{
a.HasKey(a => a.Id);
a.HasOne(b => b.Organization)
.WithMany(b => b.Users);
a.HasMany<UserOrganizations>(c => c.UserOrganizations);
a.HasOne(b => b.AppRoles);
});
There are no errors. its just populating my context.
Any idea why is this happening, thank you
Any idea why is this happening
It's happening because you instructed EF to automatically populate the value of that column via the .HasKey() extension method. Remove the call to that method - you've already specified that the column is a key column with the [Key] attribute defined on it, there's no need to tell EF twice.
In your test, you then need to configure your entity so that it ignores the [Key] attribute:
builder.Entity<Users>(a =>
{
a.HasOne(b => b.Organization)
.WithMany(b => b.Users);
a.HasMany<UserOrganizations>(c => c.UserOrganizations);
a.HasOne(b => b.AppRoles);
}).Property(user => user.Id)
.ValueGeneratedOnAddOrUpdate()
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
Related
I have this three entities Customer, Product and Review.
A customer can have many products, and a product can have only one customer as owner. A customer can also have many reviews, and one review can have only one customer. A product can have many reviews.
It seems like I am having a reference loop and below is the JsonException that I get when trying to get all customers:
Error message
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.
Path: $.rows.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Id.
Code:
namespace Domain.Entities
{
public partial class Customer
{
public int Id { get; set; }
public string? Name { get; set; }
public virtual ICollection<Review> Reviews { get; set; }
}
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Price { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public virtual ICollection<Review> Reviews { get; set; }
}
public partial class Review
{
public int Id { get; set; }
public int Stars { get; set; }
public string Description { get; set; }
public int CustomerId { get; set; }
public int ProductId { get; set; }
public Customer Customer { get; set; }
public Product Product { get; set; }
}
}
ModelBuilder configurations:
// Products configurations
builder.Ignore(e => e.DomainEvents);
builder.HasKey(t => t.Id);
// Customers configurations
builder.Ignore(e => e.DomainEvents);
builder.HasMany(e => e.Reviews)
.WithOne(e => e.Customer)
.HasForeignKey(uc => uc.Id);
builder.HasMany(e => e.MessagesSent)
.WithOne(e => e.Receiver)
.HasForeignKey(uc => uc.SenderId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(e => e.MessagesReceived)
.WithOne(e => e.Sender)
.HasForeignKey(uc => uc.ReceiverId)
.OnDelete(DeleteBehavior.Cascade);
// Reviews configurations
builder.HasKey(t => t.Id);
builder.HasOne(d => d.Customer)
.WithMany(p => p.Reviews)
.HasForeignKey(t => t.CustomerId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(d => d.Product)
.WithMany(p => p.Reviews)
.HasForeignKey(t => t.ProductId)
.OnDelete(DeleteBehavior.Cascade);
Any idea on how to fix this error?
Thanks in advance and if you need any more information please do let me know and I will provide asap.
Edit: this is the query that I am using for getting all customers:
public async Task<PaginatedData<CustomerDto>> Handle(CustomersWithPaginationQuery request)
{
var filters = PredicateBuilder.FromFilter<Customer>("");
var data = await _context.Customers
.Where(filters)
.OrderBy("Id desc")
.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider)
.PaginatedDataAsync(1, 15);
return data;
}
Edit #2: CustomerDto
namespace Application.Customers.DTOs
{
public partial class CustomerDto : IMapFrom<Customer>
{
public int Id { get; set; }
public string Name { get; set; }
public List<Review> Reviews { get; set; }
}
}
To fix this issue you need to add a ReviewDto class like this:
public partial class ReviewDto
{
public int Id { get; set; }
public int Stars { get; set; }
public string Description { get; set; }
// ...
}
And update the CustomerDto:
public partial class CustomerDto : IMapFrom<Customer>
{
public int Id { get; set; }
public string Name { get; set; }
public List<ReviewDto> Reviews { get; set; }
}
As the comments suggest, the problem is not with EF; it is with the default mechanism of System.Text.Json to serialize everything, even if there are loops. The problem with that is you eventually hit a limit giving you that exception. It is probably not your intent to send such a bloated payload back to API clients.
You can prevent that a number of different ways. You can null out the properties that would lead to cycles, but this "sort of" destroys data and could be misinterpreted by clients.
Another way would be to map your classes with cycles to DTOs that explicitly suppress the loop by not including that data, or substituting a reference property (e.g. an ID or some other reference value) to data that has been repeated.
If you don't want to do that, you can prevent the exception by using a ReferenceHandler set to ignore cycles.
This documentation explains how to do that. The effect is equivalent to the first solution of nulling out the values manually. An excerpt from that page
Employee tyler = new()
{
Name = "Tyler Stein"
};
Employee adrian = new()
{
Name = "Adrian King"
};
tyler.DirectReports = new List<Employee> { adrian };
adrian.Manager = tyler;
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
string tylerJson = JsonSerializer.Serialize(tyler, options);
...
Really, though, you're missing a step. It makes more sense to map your returned entities to DTOs. The purpose of the DTOs is to shape the response content to the needs of the API clients. That makes Ghassen's answer a good one.
https://i.imgur.com/rvWQVQt.png
So basically, I want to be able to define a User and have them be able to have a list of other Users that I designate as their friends - for some reason I'm stumped
Here are my classes and attempt so far:
public class User : BaseEntity, IUser
{
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Guid PhotoId { get; set; }
public string Mobile { get; set; }
public IList<ClubbrEvent> ClubbrEvents { get; set; }
public bool ProfileComplete { get; set; }
public List<UserFriends> Friends { get; set; }
public List<UserFriends> FriendsOf { get; set; }
}
public class UserFriends
{
public Long UserId { get; set; }
public User User { get; set; }
public Long FriendId { get; set; }
public User Friend { get; set; }
}
public class UserFriendsConfiguration: IEntityTypeConfiguration<UserFriends>
{
public void Configure(EntityTypeBuilder<UserFriends> builder)
{
builder.HasOne(f => f.Friend)
.WithMany(fo => fo.FriendsOf)
.HasForeignKey(fk => fk.FriendId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(u => u.User)
.WithMany(f => f.Friends)
.HasForeignKey(fk => fk.UserId);
}
}
But when I try to add a migration I get the following error:
The entity type 'UserFriends' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.
Ok, I figured it out, so leaving this here for anyone else in the same situation.
First, I had made a mistake in my join table properties - I had made them long but they should have been guid
Second, I defined the key in the config like so:
builder.HasKey(k => new { k.UserId, k.FriendId });
So in full:
public void Configure(EntityTypeBuilder<UserFriends> builder)
{
builder.HasKey(k => new { k.UserId, k.FriendId });
builder.HasOne(f => f.Friend)
.WithMany(fo => fo.FriendsOf)
.HasForeignKey(fk => fk.FriendId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(u => u.User)
.WithMany(f => f.Friends)
.HasForeignKey(fk => fk.UserId);
}
Running the migration and update now gives me what I need:
https://i.imgur.com/my674wx.png
I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?
Bellow code snippet showing my scenario:
[Table("User")]
public partial class UserModel
{
public UserModel()
{
UserRole = new HashSet<UserRoleModel>();
}
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<UserRoleModel> UserRole { get; set; }
}
[Table("UserRole")]
public partial class UserRoleModel
{
public UserRoleModel()
{
User = new HashSet<UserModel>();
}
public int RoleID { get; set; }
public string RoleName { get; set; }
public virtual ICollection<UserModel> User { get; set; }
}
Now within OnModelCreating(DbModelBuilder modelBuilder) EF Generate code like bellow
modelBuilder.Entity<UserModel>()
.HasMany(e => e.UserRole)
.WithMany(e => e.User)
.Map(m => m.ToTable("UserRoleMapping").MapLeftKey("UserID").MapRightKey("UserRoleID"));
now this is fine add / insert data into UserRoleMapping table. But how to
Get / Update data from UserRoleMapping table ?
I try to solve this issue following create-code-first-many-to-many the post and come-up with third class with join entity
public partial class UserRoleMappingModel
{
[Key, Column(Order = 0)]
public Guid UserId { get; set; }
public UserModel User { get; set; }
[Key, Column(Order = 1)]
public int RoleId { get; set; }
public UserRoleModel UserRole { get; set; }
}
then add public virtual ICollection<UserRoleMappingModel> UserRoleMapping { get; set; } in both the UserModel and UserRoleModel class
But when I try to GET value from database using bellow code
var results = _userRepository.GetAll()
.Include(r => r.UserRoleMapping
.Select(s => s.UserRole))
.SingleOrDefault(e => e.ID == id);
It throws ERROR
"An error occurred while executing the command definition. See the
inner exception for details.System.Data.SqlClient.SqlException
(0x80131904): Invalid object name 'dbo.UserRoleMappingModel'.\r\n
Even I tried bellow Configuration within OnModelCreating, but nothing work as expected
modelBuilder.Entity<UserRoleMappingModel>()
.HasKey(e => new { e.UserId, e.RoleId });
Your class UserRoleMappingModel has no Table-Attribute. Bcause of this, EF searches for a Table UserRoleMappingModel instead von UserRoleMapping.
You have to choose: Either map the n-to-n relationship and don't access the mapping-table or load the table to access the values in it.
As workaround you could implement a Not-Mapped column:
[Table("User")]
public partial class UserModel
{
public UserModel()
{
UserRole = new HashSet<UserRoleModel>();
}
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<UserRoleMappingModel> Mappings { get; set; }
public virtual ICollection<UserRoleModel> UserRole
{
get
{
return this.Mappings.Select(s => s.UserRole);
}
}
}
AS per GertArnold response I solve the issue in bellow way.
1st Remove below settings
modelBuilder.Entity<UserModel>()
.HasMany(e => e.UserRole)
.WithMany(e => e.User)
.Map(m => m.ToTable("UserRoleMapping").MapLeftKey("UserID").MapRightKey("UserRoleID"));
2nd Add bellow settings
modelBuilder.Entity<UserRoleMappingModel>()
.HasKey(e => new { e.UserId, e.RoleId });
3rd add table property in Mapping Model
[Table("UserRoleMapping")]
public partial class UserRoleMappingModel
{
[Key, Column(Order = 0)]
public Guid UserId { get; set; }
public UserModel User { get; set; }
[Key, Column(Order = 1)]
public int RoleId { get; set; }
public UserRoleModel UserRole { get; set; }
}
4th Create a Mapping Repository
IUserRoleMappingRepository
5th a simple get Method (Problem Solved)
var results = _userRoleMappingRepository.SearchFor(e => e.UserId == id)
.Select(s => new
{
s.UserId,
s.UserRoleId,
s.UserRole.RoleName
})
.FirstOrDefault();
Point to be noted : using bellow query I able to get result but unable to serialize with Newtonsoft.Json due to self referencing issue
var results = _userRepository.GetAll()
.Include(r => r.UserRoleMapping
.Select(s => s.UserRole))
.SingleOrDefault(e => e.ID == id);
Try bellow JsonSerializerSettingssetting alternatively but unable to serialize sucessfully
PreserveReferencesHandling = PreserveReferencesHandling.All / Object
ReferenceLoopHandling = ReferenceLoopHandling.Serialize / Ignore
I have a big database for a multi lingual application that gets it's texts from the server , inserts into the database, then based on user preferred language, finds appropriate text.
Let me first describe the database then I'll say my problem:
Illustration: for example I have a table Product, which has a foreign key (Description column) to the Translation table which in turn connects to TranslationEntry table that has all the translations of products's description in all languages.
The languages are in a separate table called Language which has a foreign key to TranslationEntry table.
public class Product : BaseModel
{
public int description { get; set; }
public virtual Translation Description { get; set; }
}
public class Translation : BaseModel
{
public Translation()
{
Products = new List<Product>();
}
public virtual ICollection<Product> Products { get; set; }
public virtual ICollection<MainCategory> MainCategories { get; set; }
public virtual ICollection<Caption> Captions { get; set; }
}
public class TranslationEntry : BaseModel
{
public string text { get; set; }
public int language { get; set; }
public virtual Language Language { get; set; }
public int translation { get; set; }
public virtual Translation Translation { get; set; }
}
public class Language : BaseModel
{
public Language()
{
TranslationEntries = new List<TranslationEntry>();
}
public string title { get; set; }
public string language_code { get; set; }
public virtual ICollection<TranslationEntry> TranslationEntries { get; set; }
}
public class BaseModel
{
public int id { get; set; }
public int MembershipId { get; set; }
public SyncStatus SyncState { get; set; }
....
}
Translation Entry Mapping:
HasRequired(translationEntry => translationEntry.Translation)
.WithMany(translation => translation.TranslationEntries)
.HasForeignKey(translationEntry =>
new {translationEntry.translation, translationEntry.MembershipId, translationEntry.SyncState})
.WillCascadeOnDelete(false);
HasRequired(translationEntry => translationEntry.Language)
.WithMany(language => language.TranslationEntries)
.HasForeignKey(translationEntry =>
new {translationEntry.language, translationEntry.MembershipId, translationEntry.SyncState})
.WillCascadeOnDelete(false);
Property(t => t.translation)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_translatinlanguageOd", 1) { IsUnique = true }));
Property(t => t.language)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_translatinlanguageOd", 2) { IsUnique = true }));
Product Mapping:
HasRequired(product => product.Description)
.WithMany(translation => translation.Products)
.HasForeignKey(product => new { product.description, product.MembershipId, product.SyncState })
.WillCascadeOnDelete(false);
Sample set of data here:
Now the problem: I want to get description of a product, I use the following command
var o = databaseContext.Products.ToList().First(p=>p.id==1)?.Description.TranslationEntries.First(te=>te.language==1);
but I get an error:
A 'Mapping' exception occurred while processing the query. See the inner exception.
Inner exception:
More than one property map found for property 'translation' when using case-insensitive search.
Note that there are many entities which have the same relationships for translation as Product table which I showed.
UPDATE:
my temporary Solution is this:
var Language = context.Languages.Include(l => l.TranslationEntries)
.Where(l => l.id == languageId)
.ToList()
.FirstOrDefault();
TranslationEntries = Language?.TranslationEntries;
var translatedText = (from t in TranslationEntries where t.translation == 2 select t.text).FirstOrDefault();
Finally fixed this stupid problem!
As the error message says, the problem is by case-insensitive search there are 2 property named translation in TranslationEntry Class, I renamed one of them and now everything works without any problem!