I have the following class:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public Couple Couple { get; set; }
}
public class Couple
{
public Guid Id { get; set; }
public User Groom { get; set; }
public User Bride { get; set; }
}
Important points:
Bride and Groom properties are required
One-to-one relationship
In the User class, it is Couple required
DbContext in OnModelCreating
modelBuilder.Entity<User>().HasRequired(u => u.Couple).WithRequiredPrincipal();
modelBuilder.Entity<Couple>().HasRequired(u => u.Bride).WithRequiredDependent();
modelBuilder.Entity<Couple>().HasRequired(u => u.Groom).WithRequiredDependent();
But I can not be required!
All fileds are with null in the database!.
How do I get the fields in the database as not null?
If possible using the API Flient.
It should be this :
modelBuilder.Entity<User>().HasRequired(u => u.Couple).WithRequiredDependent();
modelBuilder.Entity<Couple>().HasRequired(u => u.Bride).WithRequiredDependent();
modelBuilder.Entity<Couple>().HasRequired(u => u.Groom).WithRequiredDependent();
How WithRequiredDependent Works : Configures the relationship to be required:required without a navigation property on the other side of the relationship. The entity type being configured will be the dependent and contain a foreign key to the principal. The entity
type that the relationship targets will be the principal in the relationship.
Meaning : Let's consider your first line of code here. It creates a foreign key in the entity being configured (User) making it Dependant and making the other side of the relationship (Couple) Principal
Important : Don't you think the configuration you desire will generate a deadlock? I've not tested the code above, but this configuration seems to be a deadlock to me so i'm not sure if EF would allow you to create it. User must need a Couple, and Couple must need that same user i guess.
Related
I am following Microsoft's guide on how to model data in Cosmos Db. I am trying to implement something based on the second JSON snippet in this section of the guide.
It says:
This model has the three most recent comments embedded in the post
container, which is an array with a fixed set of attributes. The other
comments are grouped in to batches of 100 comments and stored as
separate items. The size of the batch was chosen as 100 because our
fictitious application allows the user to load 100 comments at a time.
From the implementation point of view, posts and comments are separate classes but the partition key is id for posts and postId for comments. The container "post" is the same.
I am using Microsoft.EntityFrameworkCore.Cosmos in a .NET 5.0 app. We have one container called "Users". In my scenario, the user has many logs. Users data is represented by the AppUser entity and the logs data is represented by the Logs entity. I want to store three recent logs in the user document and the rest of the logs of the user in a separate document, just like it is represented in the above example from Microsoft's guide.
So here are my models:
public class AppUser : IdentityUser
{
public AppUser()
{
Logs = new List<Used>();
}
public List<Logs> Logs { get; set; }
}
public class Logs
{
public DateTime Date { get; set; }
public string Details { get; set; }
}
public class LogDocument : Entity
{
public LogDocument() : base("LogDocument")
{
Logs = new List<Logs>();
}
public string UserId { get; set; }
public List<Logs> Logs { get; set; }
}
public abstract class Entity
{
public Entity(string type)
{
this.Id = Guid.NewGuid().ToString();
this.Type = type;
}
/// <summary>
/// Object unique identifier
/// </summary>
[Key]
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Object type
/// </summary>
public string Type { get; private set; }
}
The entity configuration in the database context is:
builder.Entity<AppUser>().HasKey(_ => _.Id);
builder.Entity<AppUser>().HasPartitionKey(_ => _.Id);
builder.Entity<AppUser>().Property(_ => _.ConcurrencyStamp).IsETagConcurrency();
builder.Entity<AppUser>().OwnsMany(p => p.Logs);
builder.Entity<LogDocument>().HasKey(e => e.Id);
builder.Entity<LogDocument>().HasPartitionKey(_ => _.UserId)
builder.Entity<LogDocument>().OwnsMany(_ => _.Logs);
builder.Entity<AppUser>().ToContainer("Users");
builder.Entity<LogDocument>().ToContainer("Users");
Whenever I run the application I get the following exception:
InvalidOperationException: The partition key property 'Id' on
'AppUser' is mapped as 'Id', but the partition key property 'UserId'
on 'LogDocument' is mapped as 'UserId'. All partition key properties
need to be mapped to the same store property.
What am I doing wrong? Why should I specify Id as a partition key for LogDocument? If Id is specified as a partition key in the entity configuration of LogDocument, then the error goes away but it is confusing based on how data is represented in the JSON example from Microsoft's guide. There is no key in the comments item. It is just postId and the array of comments.
How would Microsoft's example or my scenario be modelled and configured in a real-world app that uses Microsoft.EntityFrameworkCore.Cosmos?
In a GET request, I can create a mapping from my back-end model to a customized DTO with AutoMapper with ease. However, I have some concerns when using AutoMapper with POST requests.
Suppose a user orders a product online, he sends the server a POST request with some required data. The fact is, not every piece of data in the back-end model is sent by the user. Let's say the ID of the Order is a GUID which is generated automatically when the entry is inserted into the database; or maybe there are other properties which are auto-incremented. All of these cannot-be-mapped properties lead to a lot of .ForMember(dest => dest.myProperty, opt => opt.Ignore()) chains, and extra handling on the mapped instance after var mappedInstance = Mapper.Map<PostDTO, BackEndModel>(postDTO).
Is AutoMapper not designed for the aforementioned scenario? What is the practice for handling the model-mapping process if the back-end model is much more complex than the DTO?
Update
public class MultipleChoiceQuestion
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid MultipleChoiceQuestionId { get; set; }
[Required]
public string Question { get; set; }
[Required]
public ICollection<PossibleChoice> PossibleChoices { get; set; }
}
public class PossibleChoice
{
[Key, Column(Order = 1), ForeignKey("MultipleChoiceQuestion")]
public Guid MultipleChoiceQuestionId { get; set; }
[Key, Column(Order = 2)]
public int ChoiceIndex { get; set; }
[Required]
public string AnswerText { get; set; }
public MultipleChoiceQuestion MultipleChoiceQuestion { get; set; }
}
The user sends a request to create a new question. Only 2 fields are sent.
{
"Question": "How are you?",
"Answers": [
{ "Text": "I am fine." },
{ "Text": "Feeling bad." }
]
}
Properties that are missing at this stage:
MultipleChoiceQuestionId
Generated after the insertion
ChoiceIndex
Auto-incremented from 1 up to the number of answers available
Without manual mapping, how to handle this situation with AutoMapper?
1- Define your DTOs to be something like this:
public class MultipleChoiceQuestionDto
{
// This property could stay here, because you may need to use the same DTO for update (PUT),
// which means you need the Id to distinguish and validate the DTO data against the URL id
//public Guid MultipleChoiceQuestionId { get; set; }
public string Question { get; set; }
public ICollection<PossibleChoiceDto> PossibleChoices { get; set; }
}
public class PossibleChoiceDto
{
// This can go from this dto, because this DTO is a child dto for its parent.
//public Guid MultipleChoiceQuestionId { get; set; }
// This property could stay here, because you may need to use the same DTO for update (PUT),
// which means you need the Id to know which Choice was updated.
//public int ChoiceIndex { get; set; }
public string AnswerText { get; set; }
}
2- You create a mapping between the entity and the corresponding Dto like this, make sure you call this code from the global.asax file.
Mapper.CreateMap<MultipleChoiceQuestion, MultipleChoiceQuestionDto>();
Mapper.CreateMap<MultipleChoiceQuestionDto, MultipleChoiceQuestion>()
.ForMember(m => m.MultipleChoiceQuestionId, e => e.Ignore()); // you force automapper to ignore this property
Mapper.CreateMap<PossibleChoice, PossibleChoiceDto>();
Mapper.CreateMap<PossibleChoiceDto, PossibleChoice>()
.ForMember(m => m.MultipleChoiceQuestion, e => e.Ignore()) //
.ForMember(m => m.MultipleChoiceQuestionId, e => e.Ignore())
.ForMember(m => m.ChoiceIndex, e => e.Ignore());
3- In your controller.Post you need to map from the DTO to the entity and save the mapped entity to the database.
Now, the above solution will work for you for POST, however, you need to think about the PUT scenario and soon you will realize that you need the Ids to be included in the DTOs, and if you decided to do that then you need to revisit the mapping in point 2 and remove the Ignore code for the properties that you decided to include in the DTO.
Hope that helps.
I'm not sure where in your architecture you're using AutoMapper, but you could conceptually whitelist the properties before doing the automapping. For example, if you're in MVC and you're doing modelbinding, there are techniques (e.g. in the UpdateModel method) to include or exclude a list of properties.
I have two classes: Customer and Association.
A customer can have an association with many customers. Each association is of a defined type (Family, Friend, etc) i.e Customer A is a friend of Customer B. Customer A is related to Customer C. The type of association is defined by an enum AssociationType.
In order to create this in EF i've defined the following classes
public class Customer
{
public string FirstName {get; set;}
public string LastName {get; set;}
public virtual ICollection<Association> Associations { get; set; }
}
public class Association
{
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public int AssociatedCustomerId { get; set; }
public virtual Customer AssociatedCustomer { get; set; }
public AssociationType AssociationType { get; set; }
}
I've removed the Data Annotations as I was unable to get this to compile. I get the error:
"Model compatibility cannot be checked because the database does not
contain model metadata".
Does anyone have any ideas?
It happens sometimes when an error occurs during database creation. The database schema is created then - except the __MigrationHistory table. When you run your application again EF wants to check against the __MigrationHistory table if the schema is still up-to-date with the model and if that table doesn't exist it throws the exception you are having.
To fix the problem either delete the database manually or set the initializer to DropCreateDatabaseAlways<MyContext> (with Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>()) - only once. After the DB is created set it back to your original initializer.
BTW: For your model you will have to specify explicitly that Customer.Associations is related to Association.Customer, either with data annotations...
[InverseProperty("Customer")]
public virtual ICollection<Association> Associations { get; set; }
...or with Fluent API:
modelBuilder.Entity<Customer>()
.HasMany(c => c.Associations)
.WithRequired(a => a.Customer)
.HasForeignKey(a => a.CustomerId);
Thank you Slauma,
your answer got us going in the right direction.
We added the following configuration to the Association configuration:
HasRequired(x => x.AssociatedCustomer).WithMany().WillCascadeOnDelete(false);
I miss a table in my data model
I have an existing DB, and tried:
EF Power Tools like THIS description
tables:
User (Id, ...)
Project (ID, Name,..)
Timestamp (ID,Start, End, UserID, ProjectID..)
But I have no Table "Poject_Favorit" (UserId, ProjectID)
So I write the Table myself:
public class Project_Favorite
{
[Key]public Guid GuidUser { get; set; }
public Guid GuidProject { get; set; }
}
and I added:
public DbSet<Project_Favorite> Project_Favorite { get; set; }
to my DbContext
EF searched for "dbo.Project_Favorite" but it should search for "Timeworx.Project_Favorite"
so I added:
modelBuilder.Configurations.Add(new Project_FavoriteMap());
to my DbContext
and created the file "Project_FavoriteMap.cs:
class Project_FavoriteMap : EntityTypeConfiguration<Project_Favorite>
{
public Project_FavoriteMap() {
//Primary Key
this.HasKey(t => t.GuidProject);
this.HasKey(t => t.GuidUser);
this.ToTable("Project_Favorite", "Timeworx");
}
}
Now there is an error. It says "...Project_Favorite already defined..."
It looks like the Poject_Favorit table is a many-to-many join between User and Project. In that case you don't need a corresponding entity class. Your User class will probably have an ICollection<Project> and your Project class will probably have an ICollection<User>. That represents a many-to-many hierarchy. A project can favorited by many users and a user can favorite many projects. Hence the two collections.
I've just installed the new Entity Framework 4.1 NuGet package, thus replacing the EFCodeFirst package as per NuGet intructions and this article of Scott Hanselman.
Now, imagine the following model:
public class User
{
[Key]
public string UserName { get; set; }
// whatever
}
public class UserThing
{
public int ID { get; set; }
public virtual User User { get; set; }
// whatever
}
The last EFCodeFirst release generated a foreign key in the UserThing table called UserUserName.
After installing the new release and running I get the following error:
Invalid column name 'User_UserName'
Which of course means that the new release has a different FK naming strategy. This is consistent among all other tables and columns: whatever FK EFCodeFirst named AnyOldForeignKeyID EF 4.1 wants to call AnyOldForeignKey_ID (note the underscore).
I don't mind naming the FK's with an underscore, but in this case it means having to either unnecessarily throw away the database and recreate it or unnecessarily renaming al FK's.
Does any one know why the FK naming convention has changed and whether it can be configured without using the Fluent API?
Unfortunately, one of the things that didn't make it to this release is the ability to add custom conventions in Code First:
http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-release-candidate-available.aspx
If you don't want to use the fluent API to configure the column name (which I don't blame you), then most straight forward way to do it is probably using sp_rename.
Why don't you do the following?
public class User
{
[Key]
public string UserName { get; set; }
// whatever
}
public class UserThing
{
public int ID { get; set; }
public string UserUserName { get; set; }
[ForeignKey("UserUserName")]
public virtual User User { get; set; }
// whatever
}
Or, if you don't want to add the UserUserName property to UserThing, then use the fluent API, like so:
// class User same as in question
// class UserThing same as in question
public class MyContext : DbContext
{
public MyContext()
: base("MyCeDb") { }
public DbSet<User> Users { get; set; }
public DbSet<UserThing> UserThings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserThing>()
.HasOptional(ut => ut.User) // See if HasRequired fits your model better
.WithMany().Map(u => u.MapKey("UserUserName"));
}
}