EF Core Fluent API HasData adding delete data on subsequent migration - entity-framework-core

I am using HasData to seed data and it is creating insert data migration script right but with the subsequent migrations it is adding delete migrations without any further changes.
Entities
Country
public class Country : BaseEntity
{
public string CountryName { get; set; }
}
County
public class County : BaseEntity
{
public string CountyName { get;set; }
public int CountryId { get;set; }
public Country Country { get; set; }
}
BaseEntity
public class BaseEntity
{
public int Id { get; set; }
public Guid ApplicationUserId { get; set; }
}
Configurations
public class CountyConfiguration : BaseEntityConfiguration<County>
{
private const string TABLE_NAME = "Counties";
public CountyConfiguration() : base(TABLE_NAME)
{
}
public override void Configure(EntityTypeBuilder<County> entity)
{
base.Configure(entity);
entity.Property(c => c.CountyName).IsRequired().HasMaxLength(100);
entity.HasIndex(c => c.CountryId).IsUnique(false);
entity.HasIndex(c => new {c.CountyName, c.CountryId }).IsUnique();
entity.HasOne(c => c.Country).WithOne().OnDelete(DeleteBehavior.Cascade);
entity.Ignore(c => c.ApplicationUserId);
entity.HasData(
new County { Id = 1, CountryId = 1, CountyName = "Antrim"},
new County { Id = 2, CountryId = 1, CountyName = "Carlow"},
new County { Id = 3, CountryId = 1, CountyName = "Cavan"},
new County { Id = 4, CountryId = 1, CountyName = "Clare"},
new County { Id = 5, CountryId = 1, CountyName = "Cork"},
new County { Id = 6, CountryId = 1, CountyName = "Derry (Londonderry)"},
new County { Id = 7, CountryId = 1, CountyName = "Donegal"},
new County { Id = 8, CountryId = 1, CountyName = "Dublin"},
new County { Id = 9, CountryId = 1, CountyName = "Galway"},
new County { Id = 10, CountryId = 1, CountyName = "Kerry"},
new County { Id = 11, CountryId = 1, CountyName = "Kildare"},
new County { Id = 12, CountryId = 1, CountyName = "Kilkenny"},
new County { Id = 13, CountryId = 1, CountyName = "Laois (Queens)"},
new County { Id = 14, CountryId = 1, CountyName = "Leitrim"},
new County { Id = 15, CountryId = 1, CountyName = "Limerick"},
new County { Id = 16, CountryId = 1, CountyName = "Longford"},
new County { Id = 17, CountryId = 1, CountyName = "Louth"},
new County { Id = 18, CountryId = 1, CountyName = "Mayo"},
new County { Id = 19, CountryId = 1, CountyName = "Meath"},
new County { Id = 20, CountryId = 1, CountyName = "Monaghan"},
new County { Id = 21, CountryId = 1, CountyName = "Offaly (Kings)"},
new County { Id = 22, CountryId = 1, CountyName = "Roscommon"},
new County { Id = 23, CountryId = 1, CountyName = "Sligo"},
new County { Id = 24, CountryId = 1, CountyName = "Tipperary"},
new County { Id = 25, CountryId = 1, CountyName = "Waterford"},
new County { Id = 26, CountryId = 1, CountyName = "Westmeath"},
new County { Id = 27, CountryId = 1, CountyName = "Wexford"},
new County { Id = 28, CountryId = 1, CountyName = "Wicklow"}
);
}
}
Generated Migration1:
public partial class County : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Counties",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CountyName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CountryId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("Pk_Counties_Id", x => x.Id);
table.ForeignKey(
name: "FK_Counties_Countries_CountryId",
column: x => x.CountryId,
principalTable: "Countries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.InsertData(
table: "Counties",
columns: new[] { "Id", "CountryId", "CountyName" },
values: new object[,]
{
{ 3, 1, "Cavan" },
{ 26, 1, "Westmeath" },
{ 25, 1, "Waterford" },
{ 24, 1, "Tipperary" },
{ 23, 1, "Sligo" },
{ 22, 1, "Roscommon" },
{ 21, 1, "Offaly (Kings)" },
{ 20, 1, "Monaghan" },
{ 19, 1, "Meath" },
{ 18, 1, "Mayo" },
{ 17, 1, "Louth" },
{ 16, 1, "Longford" },
{ 27, 1, "Wexford" },
{ 15, 1, "Limerick" },
{ 13, 1, "Laois (Queens)" },
{ 12, 1, "Kilkenny" },
{ 11, 1, "Kildare" },
{ 10, 1, "Kerry" },
{ 9, 1, "Galway" },
{ 8, 1, "Dublin" },
{ 7, 1, "Donegal" },
{ 6, 1, "Derry (Londonderry)" },
{ 5, 1, "Cork" },
{ 4, 1, "Clare" },
{ 2, 1, "Carlow" },
{ 14, 1, "Leitrim" },
{ 28, 1, "Wicklow" }
});
migrationBuilder.CreateIndex(
name: "IX_Counties_CountryId",
table: "Counties",
column: "CountryId");
migrationBuilder.CreateIndex(
name: "IX_Counties_CountyName_CountryId",
table: "Counties",
columns: new[] { "CountyName", "CountryId" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Counties");
}
}
Next Migration 2: (Without any changes)
public partial class Empty : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "Counties",
keyColumn: "Id",
keyValue: 1);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "Counties",
keyColumn: "Id",
keyValue: 1);
migrationBuilder.InsertData(
table: "Counties",
columns: new[] { "Id", "CountryId", "CountyName" },
values: new object[] { 1, 1, "Antrim" });
}
}
Not sure, why it is adding migrationBuilder.DeleteData delete script?

Here
entity.HasOne(c => c.Country).WithOne().OnDelete(DeleteBehavior.Cascade);
with WithOne() you are basically telling EF that the County.CountryId must be unique (because the only difference between one-to-one and one-to-many in relational databases is the unique constraint (index) for the FK column(s)).
However, before that you are telling EF the opposite
entity.HasIndex(c => c.CountryId).IsUnique(false);
This sequence of conflicting configurations somehow confuses EF and it starts doing strange things.
While this could be considered their bug, at the end the problem is in your code, since apparently you don't want one-to-one relationship. So correct that by replacing HasOne with HasMany
entity.HasOne(c => c.Country).WithMany().OnDelete(DeleteBehavior.Cascade);
or completely remove it since all it does is the same as the default EF Core conventions.
Once you do that, you could also remove the HasIndex configuration since it is also the default by EF Core FK index convention.

Related

How to return object references in ASP.net core Web API

I dont understand why getAllAssemblies() return referenced objects where "getAssembly(int id)" does not.
Can you give a heads up on how to have referenced objects returned from both methods?
The type:
public class Assembly
{
public int Id { get; init; }
public string? Name { get; set; }
public List<Object>? ChildrenObjects { get; set; }
[Required]
public int ParrentObjectId { get; set; }
[JsonIgnore]
public Assembly? ParrentObject { get; set; }
}
The data:
modelBuilder.Entity<Assembly>().HasData(
new Assembly() { Id = 10, Name = "1" },
new Assembly() { Id = 11, Name = "1.1", ParrentObjectId = 10
});
GetAssembly(int id)
[HttpGet("{id}")]
public async Task<ActionResult> GetAssembly(int id)
{
return Ok(await _objectsContext.Assemblies.FindAsync(id));
}
returns:
{
"id": 10,
"name": "1",
"childrenObjects": null,
"parrentObjectId": 0
}
GetAllAssemblies()
public async Task<ActionResult> GetAllAssemblies()
{
return Ok(await _objectsContext.Assemblies.ToListAsync());
}
returns:
[
{
"id": 10,
"name": "1",
"childrenObjects": [
{
"id": 11,
"name": "1.1",
"childrenObjects": null,
"parrentObjectId": 10
}
],
"parrentObjectId": 0
},
{
"id": 11,
"name": "1.1",
"childrenObjects": null,
"parrentObjectId": 10
}
]

Prisma : Models and Relationship 1-n

I have two tables User and Tasks and a user can have many tasks, however i want a query to return a particular task, fetching details for the task, with author and assigned to users from the user table, usually would be done using aliases. DB is mysql - Thanks
//schema.prisma
model User {
id Int #id #default(autoincrement())
taskby Task[] #relation("taskBy")
taskto Task[] #relation("taskTo")
}
model Task {
id Int #id #default(autoincrement())
created_at DateTime #default(now())
updated_at DateTime #updatedAt
assigned_to_uid Int
assigned_by_uid Int
assigned_to User #relation("taskTo",fields: [assigned_to_uid], references: [id])
assigned_by User #relation("taskBy",fields: [assigned_by_uid], references: [id])
}
API:
if (id) {
res = await prisma.task.findUnique({
where: { id },
include: {
assigned_to: true
},
include: {
assigned_by: true
},
})
} else {...
Desired Response:
{
"id": 2,
"taskid": 2,
"assigned_to_uid": 1,
"assigned_by_uid": 2,
"assigned_by": {
"id": 2,
"firstName": "user2",
},
"assigned_to": {
"id": 1
"firstName": "user1",
},
}
You should be able to get the desired response by using the below query:
if (id) {
const response = await prisma.task.findUnique({
where: { id },
include: {
assigned_to: true,
assigned_by: true,
},
});
console.log(response);
}
Response for the above query:
{
id: 1,
created_at: 2022-02-28T07:22:06.917Z,
updated_at: 2022-02-28T07:22:06.918Z,
assigned_to_uid: 2,
assigned_by_uid: 1,
assigned_to: { id: 2, firstName: 'Jane', lastName: 'Doe' },
assigned_by: { id: 1, firstName: 'John', lastName: 'Doe' }
}

Included property returns object that it's included to

I'm working on my side project with is a WebAPI in ASP.NET Core 2.1. I'm using Entity Framework Core 2.1.
I have a User:
public class User : Account
{
public UInt64 UserEmail { get; protected set; }
public UserImage UserImage { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
public User() { }
public User(string nick, UInt64 login, byte[] salt, byte[] passwordHash,
string restoreKey, UInt64 userEmail) : base(nick, login, salt, passwordHash, restoreKey)
{
UserEmail = userEmail;
Role = "user";
}
public void Update(UInt64 login, UInt64 userEmail)
{
Login = login;
UserEmail = userEmail;
UpdatedAt = DateTime.UtcNow;
}
public void UpdatePassword(byte[] newPassword)
{
PasswordHash = newPassword;
UpdatedAt = DateTime.UtcNow;
}
}
and UserImage:
public class UserImage : Image
{
public int? UserRef { get; set; }
public virtual User User { get; set; }
public UserImage() : base() { }
public UserImage(string content) : base(content) { }
}
In my DbContext I have something like this:
modelBuilder.Entity<User>()
.HasOne(x => x.UserImage)
.WithOne(y => y.User)
.HasForeignKey<UserImage>(y => y.UserRef)
.IsRequired(false);
I have a method in my service that returns a user with his image:
public async Task<User> GetAsync(int id)
{
var user = await _context.Users.GetById(id)
.Include(x => x.UserImage)
.SingleOrDefaultAsync();
if (user == null)
throw new CorruptedOperationException("Invalid id");
return user;
}
The problem is that when I use a Postman to get a user I get that kind of response:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": {
"userEmail": 2606810040825320252,
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
},
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}
Like you can see I get a user with his image and in this image i get (again) this user.
I would like to get response like this:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": null,
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}
Or something similar. I tried to work my way with Include() and (or without) virtual but I ended-up with this.
UPDATE
Definition for GetById():
public static IQueryable<User> GetById(this IQueryable<User> value,int id)
=> value.Where(x => x.Id == id);
You can find full project on GitHub. I'm working on branch MB#20.
For looping reference, you could not control it in EF Core.
For general handling, we configure it in Startup.cs like
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json
.ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
});
And usecase:
var user = await _userService.GetAsync(id);
return Ok(user);
For your issue, you modified user before return by return Ok(user.ShapeData(fields)); which made JsonSerialize fail to know its the loop reference.
For a workaround, before converting to ExpandoObject, handle the loop reference like
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var result =JsonConvert.SerializeObject(source, new JsonSerializerSettings{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
} );
source = JsonConvert.DeserializeObject<TSource>(result);
var dataShapedObject = new ExpandoObject();
//your rest code
return dataShapedObject;
}
Update
For null in UserImage, it is caused by you specify the properties as public string ImageContent { get; protected set; }.
Try to custom DefaultContractResolver to deserialize the protected properties.
IncludePrivateStateContractResolver.cs
public class IncludePrivateStateContractResolver : DefaultContractResolver
{
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
const BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var properties = objectType.GetProperties(BindingFlags);//.Where(p => p.HasSetter() && p.HasGetter());
var fields = objectType.GetFields(BindingFlags);
var allMembers = properties.Cast<MemberInfo>().Union(fields);
return allMembers.ToList();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
prop.Writable = property.HasSetter();
}
else
{
var field = member as FieldInfo;
if (field != null)
{
prop.Writable = true;
}
}
}
if (!prop.Readable)
{
var field = member as FieldInfo;
if (field != null)
{
prop.Readable = true;
}
}
return prop;
}
}
public static class TypeExtensions
{
public static bool HasSetter(this PropertyInfo property)
{
//In this way we can check for private setters in base classes
return property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Any(m => m.Name == "set_" + property.Name);
}
public static bool HasGetter(this PropertyInfo property)
{
//In this way we can check for private getters in base classes
return property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Any(m => m.Name == "get_" + property.Name);
}
}
usecase:
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
var soureType = source.GetType();
if (source == null)
{
throw new ArgumentNullException("source");
}
var serializeSettings = new JsonSerializerSettings{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var deserializeSettings = new JsonSerializerSettings{
ContractResolver = new IncludePrivateStateContractResolver()
};
var result =JsonConvert.SerializeObject(source, serializeSettings );
var castType = JsonConvert.DeserializeObject<TSource>(result, deserializeSettings);
var dataShapedObject = new ExpandoObject();
I managed to fix my problem. I changed all private setters in models properties to public an used Tao Zhou original answer.
My ShapeData method:
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var result = JsonConvert.SerializeObject(source, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
source = JsonConvert.DeserializeObject<TSource>(result);
var dataShapedObject = new ExpandoObject();
// rest of my code
}
User:
public class User : Account
{
public UInt64 UserEmail { get; set; }
public UserImage UserImage { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
// rest of the code
}
UserImage:
public class UserImage : Image
{
public int? UserRef { get; set; }
public virtual User User { get; set; }
public UserImage() : base() { }
public UserImage(string content) : base(content) { }
}
Example of the returned data:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": null,
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}

How to partition a reactive observable by a key and merge the partitions into groups combine last element of each partition of the group

I have an hot observable of a sequence of items that are have a key that identifies a specific sub-stream. I'm interested to map those M streams into N with N < M (group them into N buckets). For each bucket, each time an element arrives, I want to apply a function to the latest element of each underlining sequence of that group. I've prior knowledge of both N and M groups.
In the following sample, we have a sequence of quote for four fruits. I want to map those streams into two, by the type of fruit (Apple or Pear). For each group I want to collect the last known quote of each fruit.
class Input {
public string ProductID {get;set;}
public string ProductType {get;set;}
public int Price {get;set;}
}
class Output {
public string ProductType {get;set;}
public Input[] Underlining {get;set;}
}
var obs = new List<Input> {
new Input { ProductID = "Stark", ProductType = "Apple", Price = 21 },
new Input { ProductID = "Jonagold", ProductType = "Apple", Price = 12 },
new Input { ProductID = "Williams", ProductType = "Pear", Price = 33 },
new Input { ProductID = "Beth", ProductType = "Pear", Price = 22 },
new Input { ProductID = "Stark", ProductType = "Apple", Price = 43 },
new Input { ProductID = "Williams", ProductType = "Pear", Price = 55 },
new Input { ProductID = "Beth", ProductType = "Pear", Price = 66 },
new Input { ProductID = "Jonagold", ProductType = "Apple", Price = 77 },
new Input { ProductID = "Jonagold", ProductType = "Apple", Price = 25 },
new Input { ProductID = "Williams", ProductType = "Pear", Price = 77 },
new Input { ProductID = "Beth", ProductType = "Pear", Price = 13 },
new Input { ProductID = "Stark", ProductType = "Apple", Price = 21 },
}.ToObservable();
IObservable<Output> result = obs.GroupBy ... Select ... Concat ... ; // I'm a bit loss here
result.Dump();
Expected result:
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 21 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 21 }, { ProductID = "Jonagold", Price = 12 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 23 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 23 }, { ProductID = "Beth", Price = 22 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = **43** }, { ProductID = "Jonagold", Price = 12 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = **55** }, { ProductID = "Beth", Price = 22 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 55 }, { ProductID = "Beth", Price = **66** }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 43 }, { ProductID = "Jonagold", Price = **77** }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 43 }, { ProductID = "Jonagold", Price = **25** }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = **77** }, { ProductID = "Beth", Price = 66 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 77 }, { ProductID = "Beth", Price = **13** }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = **21** }, { ProductID = "Jonagold", Price = 25 }] }
I think this is what you want:
var outputs =
obs
.GroupBy(x => x.ProductType)
.Select(xs =>
xs
.Scan(
new Dictionary<string, Input>(),
(d, x) => { d[x.ProductID] = x; return d; })
.Select(x => new Output()
{
ProductType = xs.Key,
Underlining = x.Values.ToArray(),
}))
.Merge();
I used outputs.Select(x => $"{{ ProductType = \"{x.ProductType}\", Underlining = [{String.Join(", ", x.Underlining.Select(y => $"{{ ProductID = \"{y.ProductID}\", Price = {y.Price} }}"))}] }}") to get the following output to test it:
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 21 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 21 }, { ProductID = "Jonagold", Price = 12 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 33 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 33 }, { ProductID = "Beth", Price = 22 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 43 }, { ProductID = "Jonagold", Price = 12 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 55 }, { ProductID = "Beth", Price = 22 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 55 }, { ProductID = "Beth", Price = 66 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 43 }, { ProductID = "Jonagold", Price = 77 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 43 }, { ProductID = "Jonagold", Price = 25 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 77 }, { ProductID = "Beth", Price = 66 }] }
{ ProductType = "Pear", Underlining = [{ ProductID = "Williams", Price = 77 }, { ProductID = "Beth", Price = 13 }] }
{ ProductType = "Apple", Underlining = [{ ProductID = "Stark", Price = 21 }, { ProductID = "Jonagold", Price = 25 }] }

Seeding Related Data with EF 6

I'm looking for some simple advice, I just cannot seem to get the answer (or I don't know how to ask the right question).
Here goes, I have 2 classes used to build a db using EF with Code first approach:
public class Dealer
{
public int Id { get; set; }
public string DealerName { get; set; }
}
and
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public Dealer Dealer { get; set; }
}
So, EF creates a DB for me, and in the Products table also creates a "Dealer-ID (FK, int, null)" field so that Products can be related to Dealers.
So, in order to have a nice test application (which this is), I want to Seed the DB with some info.
I create some Dealers:
context.Dealers.AddOrUpdate(
p => p.DealerName,
new Models.Dealer { DealerName = "Los Angeles" },
new Models.Dealer { DealerName = "New York" },
new Models.Dealer { DealerName = "London" },
new Models.Dealer { DealerName = "Quebec" },
new Models.Dealer { DealerName = "Gothenburg" }
);
and now I want to create some products, and relate them to Dealers at the same time. Ordinarily, I would simply put an index value into the Dealer_ID Foreign Key field in the products table, but, since this wasn't in my models class in the first place, it's not available.
I do however have a List available.
Like this:
context.Products.AddOrUpdate(
p => p.ProductName,
new Models.Product { ProductName = "Electronics",
Dealer = ???????
},
new Models.Product
{
ProductName = "Sushi",
Dealer = new Models.Dealer { DealerName = "Johannesburg" }
}
);
I successfully add a Product "Sushi", and relate it to a new dealer called "Johannesburg", but, how do I add "Electronics" to the existing dealer "London" for example. What goes in the ?????? so that I can reference an existing dealer??
I can't create new dealers for each product, since that defeats the purpose of relating the tables in the first place.
Save the dealers in a List<Dealer> (or an array) and set the Dealer property in a Product using the specific index in the list:
var dealers =new List<Models.Dealer>(){
new Models.Dealer { DealerName = "Los Angeles" },
new Models.Dealer { DealerName = "New York" },
new Models.Dealer { DealerName = "London" },
new Models.Dealer { DealerName = "Quebec" },
new Models.Dealer { DealerName = "Gothenburg" },
new Models.Dealer { DealerName = "Johannesburg" }
};
context.Dealers.AddOrUpdate(p => p.DealerName,dealers.ToArray());
context.Products.AddOrUpdate(
p => p.ProductName,
new Models.Product { ProductName = "Electronics", Dealer = dealers[2]},
new Models.Product { ProductName = "Sushi",Dealer = dealers[5]});
context.SaveChanges();
I believe what octaviocci is getting at is that you need to provide a value for the Dealer property. Entity Framework will take care of populating the Dealer_Id. That being said, I would recommend storing any of the dealers which you wish to reference in individual variables so that your code is more resilient to change and clear to read.
var laDealer = new Models.Dealer { DealerName = "Los Angeles" };
var nyDealer = new Models.Dealer { DealerName = "New York" };
var londonDealer = new Models.Dealer { DealerName = "London" };
var quebecDealer = new Models.Dealer { DealerName = "Quebec" };
var gothenburgDealer = new Models.Dealer { DealerName = "Gothenburg" };
var johannesburgDealer = new Models.Dealer { DealerName = "Johannesburg" };
var dealers =new List<Models.Dealer>(){
laDealer,
nyDealer,
londonDealer,
quebecDealer,
gothenburgDealer,
johannesburgDealer
};
context.Dealers.AddOrUpdate(p => p.DealerName,dealers.ToArray());
I would also recommend adding an additional save between the dealer creation and the products creation.
context.SaveChanges();
context.Products.AddOrUpdate(
p => p.ProductName,
new Models.Product { ProductName = "Electronics", Dealer = londonDealer},
new Models.Product { ProductName = "Sushi",Dealer = johannesburgDealer}
);
context.SaveChanges();
I recommend this because my experience has shown that in some cases the save for the dealers may not execute before the product save. This can cause a foreign key conflict. (This might only apply when the foreign key column is non-nullable, but I'm not certain.)
I was looking at these type of solutions, but I kept on thinking "it shouldn't be this complex".
I solved it eventually, and here's what I did:
You basically Instantiate a particular Dealer first, then add all the products to it...
Models.Dealer dealer1 = context.Dealers.Where(e => e.DealerName == "Los Angeles").First();
context.Products.AddOrUpdate(
p => p.ProductName,
new Models.Product
{
ProductName = "Electronics",
Dealer = dealer1
},
new Models.Product
{
ProductName = "Gourmet Food",
Dealer = dealer1
},
new Models.Product
{
ProductName = "Sushi",
Dealer = dealer1
},
new Models.Product
{
ProductName = "Beach Wear",
Dealer = dealer1
}, new Models.Product
{
ProductName = "Fast Cars",
Dealer = dealer1
});
It is important to note though, that if you're doing this, by the time the Products are to be added, there will be no Dealers committed to the DB yet, so you need to actually do this:
protected override void Seed(RelatedDataExample.Models.myContext context)
{
context.Dealers.AddOrUpdate(
p => p.DealerName,
new Models.Dealer { DealerName = "Los Angeles" },
new Models.Dealer { DealerName = "New York" },
new Models.Dealer { DealerName = "London" },
new Models.Dealer { DealerName = "Quebec" },
new Models.Dealer { DealerName = "Gothenburg" }
);
context.SaveChanges();
Models.Dealer dealer1 = context.Dealers.Where(e => e.DealerName == "Los Angeles").First();
context.Products.AddOrUpdate(
p => p.ProductName,
new Models.Product
{
ProductName = "Electronics",
Dealer = dealer1
},
new Models.Product
{
ProductName = "Gourmet Food",
Dealer = dealer1
},
new Models.Product
{
ProductName = "Sushi",
Dealer = dealer1
},
new Models.Product
{
ProductName = "Beach Wear",
Dealer = dealer1
}, new Models.Product
{
ProductName = "Fast Cars",
Dealer = dealer1
});
Models.Dealer dealer2 = context.Dealers.Where(e => e.DealerName == "Quebec").First();
context.Products.AddOrUpdate(
p => p.ProductName,
new Models.Product
{
ProductName = "Maple Syrup",
Dealer = dealer2
},
new Models.Product
{
ProductName = "Warm Jackets",
Dealer = dealer2
},
new Models.Product
{
ProductName = "Fermented Fish Head",
Dealer = dealer2
});
}
To my simple mind, this was the simplest solution.
Anyone have a better way?
Cheers
JohannS