I've been messing around with EF Core 3.1 trying to setup a relationship between two classes. The trick is the composite key on the one class which the other one is only related to through one part.
Consider the following:
public class A
{
public string Foo { get; set; }
public long Id { get; set; }
public IEnumerable<B> B { get; set; }
}
public class B
{
[Key]
public int Id { get; set; }
public string Bar { get; set; }
}
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<A>(b =>
{
b.HasKey(x => new {x.Id, x.Foo});
b.HasMany(x => x.B)
.WithOne()
.HasPrincipalKey(x => x.Id);
});
}
Which creates the following migration:
migrationBuilder.CreateTable(
name: "A",
columns: table => new
{
Foo = table.Column<string>(nullable: false),
Id = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_A", x => new { x.Id, x.Foo });
table.UniqueConstraint("AK_A_Id", x => x.Id);
});
migrationBuilder.CreateTable(
name: "B",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Bar = table.Column<string>(nullable: true),
AId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_B", x => x.Id);
table.ForeignKey(
name: "FK_B_A_AId",
column: x => x.AId,
principalTable: "A",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_B_AId",
table: "B",
column: "AId");
Which is almost what I want, except for the UniqueConstraint on table A! Is there a way around this?
This is not possible. And not because of EF Core, but the definition of the relationship multiplicity (cardinality).
The direct FK relationship from dependent to principal is to (has multiplicity) one (1) (or zero-or-one 0..1 in case of optional relationship), i.e. the FK must identify exactly one principal record. That's why the referenced principal key must be unique, which is satisfied by primary or unique key constraint.
What you are asking allows dependent record referencing more than one (i.e. many) matching principal records. In other words, to many cardinality. Which cannot be achieved with FK in the dependent table, but would require the typical for many-to-many relationship intermediate link (join) entity/table.
Related
I have a problem with EF Core code first class object mapping. I have a class Match where I have properties for Team A and Team B.
public class Match: MyEntity
{
public virtual Team TeamA { get; private set; }
public virtual Team TeamB { get; private set; }
public GameType GameType { get; private set; }
public MatchStatus Status { get; private set; }
public Match()
{
}
Here my Team entity, where I have reference on MatchId.
public class Team: MyEntity
{
public virtual int MatchId { get; private set; }
private Team()
{ }
...
}
So I need MatchId be the same for TeamA and TeamB in case they are in the same match.
When I am trying to create migration, I have an error:
Both relationships between 'Team' and 'Match.TeamA' and between 'Team' and 'Match.TeamB' could use {'MatchId'} as the foreign key. To resolve this, configure the foreign key properties explicitly in 'OnModelCreating' on at least one of the relationships.
builder.Entity<Team>()
.HasOne<Match>()
.WithOne(x => x.TeamA)
.HasForeignKey<Team>(x => x.MatchId);
builder.Entity<Team>()
.HasOne<Match>()
.WithOne(x => x.TeamB)
.HasForeignKey<Team>(x => x.MatchId);
When I am using this configuration it works, but it is deleting in migration TeamAId and TeamBId columns from Matches table and creates two columns in Team table: MatchId and MatchId1.
migrationBuilder.DropColumn(
name: "TeamAId",
table: "Matches");
migrationBuilder.DropColumn(
name: "TeamBId",
table: "Matches");
migrationBuilder.AddColumn<int>(
name: "MatchId",
table: "Teams",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "MatchId1",
table: "Teams",
type: "int",
nullable: true);
Maybe am I missing something?
How should I fix this ?
Thank you for any help.
I've fixed it with not relying on default EF Core configurations, but on mine in OnModelCreating. Hope I did it in right way, someone can tell if see something wrong.
builder.Entity<Team>(b =>
{
b.HasOne<Match>()
.WithOne(x => x.TeamA)
.HasForeignKey<Team>(x => x.MatchId);
b.HasOne<Match>()
.WithOne(x => x.TeamB)
.HasForeignKey<Team>(x => x.MatchId);
});
builder.Entity<Match>(b =>
{
b.HasOne<Team>("TeamA")
.WithMany()
.HasForeignKey("TeamAId");
b.HasOne<Team>("TeamB")
.WithMany()
.HasForeignKey("TeamBId");
b.Navigation("TeamA");
b.Navigation("TeamB");
});
(Sort of like this old SO post, but for EF Core 2 or 3)
When I map two independent entities that have an optional 1-to-1 bi-directional navigation to each other, the migration generated by EF Core is missing one of the foreign keys I would expect to see.
I have the following classes:
public class ClassOne
{
public int Id { get; set; }
public ClassTwo ClassTwo { get; set; }
public int? ClassTwoId { get; set; }
}
public class ClassTwo
{
public int Id { get; set; }
public ClassOne ClassOne { get; set; }
public int? ClassOneId { get; set; }
}
And the following mapping definition:
public class ClassOneDbConfiguration : IEntityTypeConfiguration<ClassOne>
{
public void Configure(EntityTypeBuilder<ClassOne> builder)
{
builder.HasOne(e => e.ClassTwo)
.WithOne(e => e.ClassOne)
.HasForeignKey<ClassTwo>(e => e.ClassOneId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasIndex(e => e.ClassTwoId).IsUnique();
}
}
public class ClassTwoDbConfiguration : IEntityTypeConfiguration<ClassTwo>
{
public void Configure(EntityTypeBuilder<ClassTwo> builder)
{
builder.HasOne(e => e.ClassOne)
.WithOne(e => e.ClassTwo)
.HasForeignKey<ClassOne>(e => e.ClassTwoId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasIndex(e => e.ClassOneId).IsUnique();
}
}
The migration generated by dotnet-ef migrations add CreateLinks gives (I created the tables in a separate migration to simplify):
migrationBuilder.AddColumn<int>(
name: "ClassOneId",
table: "EntitiesTwo",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "ClassTwoId",
table: "EntitiesOne",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_EntitiesTwo_ClassOneId",
table: "EntitiesTwo",
column: "ClassOneId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_EntitiesOne_ClassTwoId",
table: "EntitiesOne",
column: "ClassTwoId",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_EntitiesOne_EntitiesTwo_ClassTwoId",
table: "EntitiesOne",
column: "ClassTwoId",
principalTable: "EntitiesTwo",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
Where is the Foreign Key creation for table EntitiesTwo? Without it, I cannot have the ON CASCADE SET NULL on it and may end up with invalid data in my DB.
I can ensure referential integrity in my code, sure, but can I have the foreign key on the DB as well?
I have a simple enum I am trying to include in my GraphQL type and am receiving teh following error:
The GraphQL type for Field: 'Category' on parent type: 'NewsItemType'
could not be derived implicitly.
------------------The GraphQL type for Field: 'Category' on parent type: 'NewsItemType' could not be derived implicitly.
---------------The type: NewsType cannot be coerced effectively to a GraphQL type Parameter name: type
my simple enum looks like:
public enum NewsType
{
General = 0,
Business = 1,
Entertainment = 2,
Sports = 3,
Technology = 4
}
The GraphQL ObjectGraphType that it is included in:
public class NewsItemType : ObjectGraphType<NewsItemViewModel>
{
public NewsItemType()
{
Field(x => x.Id).Description("Id of a news item.");
Field(x => x.Title).Description("Title of a new item.");
Field(x => x.Description).Description("Description of a news item.");
Field(x => x.Author).Description("Author of a news item.");
Field(x => x.Url).Description("URI location of the news item");
Field(x => x.ImageUrl).Description("URI location of the image for the news item");
Field(x => x.PublishDate).Description("Date the news item was published");
Field(x => x.Category).Description("Category of the news item.");
}
}
and finally, teh viewmodel that the graphql type is based on:
public class NewsItemViewModel : ViewModelBase
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public string ImageUrl { get; set; }
public DateTime PublishDate { get; set; }
public NewsType Category { get; set; }
}
What am I doing wrong here and how can I overcome it?
EDIT:
my query contains the following:
Field<ListGraphType<NewsItemType>>(
name: "newsItems",
arguments: new QueryArguments(
new QueryArgument<IntGraphType>() { Name = "count" },
new QueryArgument<IntGraphType>() { Name = "category" }),
resolve: context =>
{
var count = context.GetArgument<int?>("count");
var category = context.GetArgument<int>("category");
var newsType = (NewsType)category;
if (count.HasValue)
{
return newsItemService.GetMostRecent(newsType, count.Value);
}
else
{
return newsItemService.GetMostRecent(newsType);
}
}
)
I had a very hard time getting this to work as well. Official documentation definitely seems to be lacking in this regard. The way I got it to work was based on something I found in this article.
For your scenario, you would create a 'GraphType' class for your enum as follows:
public class NewsEnumType : EnumerationGraphType<NewsType>
{
}
Then update your field to:
Field<NewsEnumType>(nameof(NewsItemViewModel.Category)).Description("Category of the news item.");
One other thing to mention that I ran into with EnumTypes that I wasn't expecting. If you are using an enum as a parameter, do what you did above where you ingest it as an IntGraphType and then cast it to your enum type (NewsType)category
Field<ListGraphType<NewsItemType>>(
name: "newsItems",
arguments: new QueryArguments(
new QueryArgument<IntGraphType>() { Name = "count" },
new QueryArgument<IntGraphType>() { Name = "category" }),
resolve: context =>
{
var count = context.GetArgument<int?>("count");
var category = context.GetArgument<int>("category");
var newsType = (NewsType)category;
if (count.HasValue)
{
return newsItemService.GetMostRecent(newsType, count.Value);
}
else
{
return newsItemService.GetMostRecent(newsType);
}
}
)
My enum was part of a complex object that I was passing as a parameter, more like new QueryArgument<NewsItemType>() { Name = "newsItem" },
If you are going to do that, then the category property on the object passed to the server needs to be a string. So, if the category you are passing back to the server is Business = 1, then you would need to pass category: 'Business' and NOT category: 1.
Here's a quick way to do this for your case. Basically you don't use the default lambda expression Field. Instead actually write out a resolver yourself and convert the enum to the proper type. This is helping GraphQL to convert the type properly into the type you want to return.
Field<IntGraphType>("category", resolve: context => (int)context.Source.Category);
If your enum was a string, you could do the same for that as well.
Field<StringGraphType>("category", resolve: context => context.Source.Category.ToString());
There is another, more verbose way, shared in this answer where you inherit from the EnumerationGraphType<T> first and then do the same custom resolver as above.
https://stackoverflow.com/a/56051133/11842628
I'm trying to use seed method like this;
context.Reeves.AddOrUpdate(
p => new { p.FirstName, p.LastName },
new Reeve { FirstName = "A", LastName = "A" },
new Reeve { FirstName = "B", LastName = "B" });
context.SaveChanges();
context.Districts.AddOrUpdate(
p => p.Name,
new District() { Name = "X", ReeveId = context.Reeves.First(r => r.FirstName == "A" && r.LastName == "A").Id },
new District() { Name = "Y", ReeveId = context.Reeves.First(r => r.FirstName == "B" && r.LastName == "B").Id });
context.SaveChanges();
I'm receiving error message that "The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.District_dbo.Reeve_Id". The conflict occurred in database "ProjectTracking", table "dbo.Reeve", column 'Id'."
If i change the code like below;
context.Districts.AddOrUpdate(
p => p.Name,
new District() { Name = "X", Reeve = context.Reeves.First(r => r.FirstName == "A" && r.LastName == "A") },
new District() { Name = "Y", Reeve = context.Reeves.First(r => r.FirstName == "B" && r.LastName == "B") });
context.SaveChanges();
Error message disapear but when i check the districts table i see all ReeveId columns are 0.
What is my mistake, any idea?
PS: I dont want to create inline Reeve's inside District's AddOrUpdate methods. Something like; context.Districts.AddOrUpdate(p => p.Name, new District() { Name = "X", Reeve = new Reeve () { FirstName = "A", LastName = "A" });
My Entities
public class Reeve
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
public virtual District District { get; set; }
public byte[] RowVersion { get; set; }
}
public class District
{
public District()
{
Projects = new HashSet<Project>();
ProjectRequests = new HashSet<ProjectRequest>();
}
public int Id { get; set; }
public string Name { get; set; }
public int ReeveId { get; set; }
public virtual Reeve Reeve { get; set; }
public byte[] RowVersion { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<ProjectRequest> ProjectRequests { get; set; }
}
Entity Configurations
public class ReeveConfiguration : EntityTypeConfiguration<Reeve>
{
public ReeveConfiguration()
{
HasKey<int>(p => p.Id);
Ignore(p => p.FullName);
Property(p => p.FirstName).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("FullName", 1) { IsUnique = true })).HasMaxLength(50).IsRequired();
Property(p => p.LastName).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("FullName", 2) { IsUnique = true })).HasMaxLength(50).IsRequired();
Property(p => p.RowVersion).IsRowVersion();
}
}
public class DistrictConfiguration : EntityTypeConfiguration<District>
{
public DistrictConfiguration()
{
HasKey<int>(p => p.Id);
HasRequired(p => p.Reeve).WithOptional(p => p.District);
HasMany(p => p.Projects).WithRequired(p => p.District).HasForeignKey(p => p.DistrictId);
HasMany(p => p.ProjectRequests).WithRequired(p => p.District).HasForeignKey(p => p.DistrictId);
Property(p => p.Name).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute() { IsUnique = true })).HasMaxLength(100).IsRequired();
Property(p => p.RowVersion).IsRowVersion();
}
}
I hate entity framework team's 1 to 1 relationship rules. Delegated entity is must be same name PK with principle entity and also delegated entity PK must be also FK. This must be a joke. All 1 to 1 relationship can not be like Person -> PersonPhoto or Car -> Steering wheel. Am i right or i misunderstand their logic. For example i have also project and project request entities, project's request and request's project can be null i mean they have 0..1 to 0..1 relationship and they must be own PK Id's. Also how about that if i have Entity base class that have Id primary key field. How can i derived my 1 to 1 relation entities from that.
Ok, i solved my problem myself. Let's talk again my expectations. I want to write base class for my entities, at first step it's only contain Id property. Also i want to control my foreign key names, i don't want to ef do it for me automatically.
Also let's talk about received error too. I can not seed data and receive an error because of ef creating foreign key for me automatically and in district entity ReeveId column creating something like other data column. So when i set District.ReeveId with existing reeve and save changes ef throw foreign key error.
I do below changes to my code for solve the problem toward ef and my expectations;
Delete ReeveId from district entity because there is no need
Add HasRequired(p => p.Reeve).WithOptional(p => p.District).Map(m => m.MapKey("ReeveId")); code to my district configuration
So both table contain own Id column for primary key, district also have ReeveId column for foreign key. As a result ef and my expectations met.
Having a list of items I need to sort/order items within that list based on outter list's id
public class Venue
{
public int Id { get; set; }
public string Code { get; set; }
public IEnumerable<Machines> Machines { get; set; }
}
public class Machine
{
public string Name { get; set; }
}
Suppose I have a list of 3 venues and I want to sort only Venue's Machines where Venue.Id = 1;
Having a list of venues, I want to always sort by Venue Code, THEN sort machines by Name of Venue with Id = 1;
I tried this but doesn't work correctly:
query = query.OrderBy(x => x.Code).ThenBy(y => y.Machines.OrderBy(q => q.Name).Where(x => y.Id == 1))
If you want to sort a property of an object which is an IEnumerable<T> you need to select a new:
query = query
.Select(v => new Venue
{
Id = v.Id,
Code = v.Code,
Machines = v.Id == 1 ? v.Machines.OrderBy(m => m.Name) : v.Machines
})
.OrderBy(v => v.Code);