Updating entity tree with deep nested entities - c#

My app deals with saving orders received from an external system. The order contains child items like line items, address, fulfillments, refunds > refund items etc.
Currently, I use an ugly looking code to detect what has changed in each entity by its External Id. Can someone recommend me a better way? :)
Following is a simplified entity model of Order
public class Order
{
public long Id { get; set; }
public string ExternalOrderId { get; set; }
public List<LineItem> LineItems { get; set; }
public List<Fulfillment> Fulfillments { get; set; }
public ShippingAddress ShippingAddress { get; set; }
public List<Refund> Refunds { get; set; }
public string FinancialStatus { get; set; }
public string FulfillmentStatus { get; set; }
}
public class LineItem
{
public long Id { get; set; }
public string ExternalLineItemId { get; set; }
public string SKU { get; set; }
public int Quantity { get; set; }
public long OrderId { get; set; }
}
public class Fulfillment
{
public long Id { get; set; }
public string ExternalFulfillmentId { get; set; }
public string Status { get; set; }
public string TrackingUrl { get; set; }
public long OrderId { get; set; }
}
public class ShippingAddress
{
public long Id { get; set; }
public string ExternalShippingAddressrId { get; set; }
public string Addres { get; set; }
public long OrderId { get; set; }
}
public class Refund
{
public long Id { get; set; }
public string ExternalRefundId { get; set; }
public List<RefundedItem> LineItems { get; set; }
public string CancelledReason { get; set; }
public long OrderId { get; set; }
}
public class RefundedItem
{
public long Id { get; set; }
public string ExternalRefundedItemId { get; set; }
public string SKU { get; set; }
public int Quantity { get; set; }
}
My sample code:
private async Task ManageFulfillments(long orderId, Order order)
{
if (order.Fulfillments == null || !order.Fulfillments.Any()) return;
var newFulfillmentIds = order.Fulfillments.Select(c => c.ExternalFulfillmentId).ToList();
var dbFulfillments = await _fulfillmentRepository.GetAll().IgnoreQueryFilters()
.Where(c => c.OrderId == orderId)
.Select(c => new { c.Id, c.ExternalFulfillmentId }).ToListAsync();
var dbFulfillmentIds = dbFulfillments.Select(c => c.ExternalFulfillmentId).ToList();
// Delete Fulfillments that are not present in new Fulfillments list
var deletedFulfillments = dbFulfillmentIds.Except(newFulfillmentIds).ToList();
if (deletedFulfillments.Any())
{
await _fulfillmentRepository.DeleteAsync(c =>
deletedFulfillments.Contains(c.ExternalFulfillmentId) && c.ExternalOrderId == orderId);
}
// Update existing Fulfillments ids
order.Fulfillments
.Where(c => dbFulfillmentIds.Contains(c.ExternalFulfillmentId))
.ToList()
.ForEach(async c =>
{
c.Id = dbFulfillments.Where(p => p.ExternalFulfillmentId == c.ExternalFulfillmentId)
.Select(p => p.Id).FirstOrDefault();
await _fulfillmentRepository.UpdateAsync(c);
});
// New Fulfillments will automatically be added by EF
}
I have similar code in place to update other entites as well and I'm not proud of it!

Related

Cascade delete with EF Core and C# (excludes business and thus delete address together)

My class Address:
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
public string StreetNumber { get; set; }
public string District { get; set; }
public string Complement { get; set; }
public string City { get; set; }
public string State { get; set; }
public virtual ICollection<Business> Business { get; set; }
}
My class Business:
public class Business
{
public int Id { get; set; }
public string CNPJ { get; set; }
public string SocialReson { get; set; }
public string FantasyName { get; set; }
public string BusinessName { get; set; }
public Priority Priority { get; set; }
public Category Category { get; set; }
public int AddressId { get; set; }
public string MainImage { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<BusinessPhotos> BusinessPhotos { get; set; }
public virtual ICollection<OpeningHours> OpeningHours { get; set; }
public virtual ICollection<Product> Products { get; set; }
public virtual ICollection<SocialMedia> SocialMedias { get; set; }
}
My Delete method:
public async Task Delete(int id)
{
var business = await _context
.Business
.Include(x => x.Address)
.FirstOrDefaultAsync(x => x.Id == id);
if (business == null)
return;
_context.Business.Remove(business);
await _context.SaveChangesAsync();
}
Even if I leave the FK AddressId as .OnDelete(DeleteBehavior.Cascade), I can't delete the address together.
I'm using .NET 6 and SQL Server.

How to get data from child to parent entity framework core?

I have two table like this -
public class Job
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime AddedTime { get; set; } = DateTime.Now;
public DateTime LastEdit { get; set; } = DateTime.Now;
public string Explanation { get; set; }
public string PhotoString { get; set; }
public bool isActive { get; set; } = true;
public int CompanyId { get; set; }
public Company Company { get; set; }
}
and company -
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Explanation { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string PhotoString { get; set; }
public bool isActive { get; set; } = true;
public int AppUserId { get; set; }
public AppUser AppUser { get; set; }
public List<Job> Jobs { get; set; }
}
I only want to get AppUserId from Company and all Jobs from every Company. I tried this and it gave me error.
using var context = new SocialWorldDbContext();
return await context.Jobs.Where(I => I.isActive == true && I.Company.isActive).Include(I=>I.Company.AppUserId).ToListAsync();
So my question is there any way I can get this data from parent?
Include adds whole entities to the output. To add just one property use Select, something like
context.Jobs
.Where(I => I.isActive == true && I.Company.isActive)
.Select(e => new {Job=e, CompanyAppUserId = e.Company.AppUserId})
.ToListAsync();

How to delete on cascade EF core

I'm trying to delete the entities related to each other when I remove a row, but it isn't deleting the related entities. It is only deleting one entity and not the others.
My model
public class Company
{
public int CompanyId { get; set;}
public string CompanyName { get; set; }
public int CompanySize { get; set; }
public string Branche { get; set;}
public string Description {get; set;}
public Recruiter Recruiter { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Recruiter
{
public int RecruiterId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public int Compensation { get; set; }
public string Education { get; set; }
public string StartDate { get; set; }
public string Type { get; set; }
public string Profession { get; set; }
public string Language { get; set; }
public string Description { get; set; }
public string Hours { get; set; }
public bool Checked { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(post => post.Company)
.WithMany(company => company.Posts)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.SeedDatabase();
}
The call I make. So when I delete a post, I want that all the related entities are being deleted.
public Post DeclinePostRequest(int postId)
{
var request = _dbContext.Posts.Where(post => post.PostId == postId).Include(post => post.Company).ThenInclude(company => company.Recruiter).FirstOrDefault();
if(!request.Checked)
{
_dbContext.Posts.Remove(request);
_dbContext.SaveChanges();
return request;
}
return null;
}
You are deleting the many side of a 1-to-many relationship there. Everything is working as expected.
Try deleting a Company instead.

Include in EF C#

I have three tables:
Member, business and business address
I'm able to fetch the business with member using Include, but I'm trying to get businessAddress, it is asking for direct relationship i guess.
Here is my models:
[Table("member")]
public partial class Member
{
public Member()
{
Business = new HashSet<Business>();
// BusinessAddress = new HashSet<BusinessAddress>();
}
[Key]
public int memberId { get; set; }
public int chapterid { get; set; }
public string title { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public bool IsActive { get; set; }
public string classification { get; set; }
public int SortOrder { get; set; }
public virtual ICollection<Business> Business { get; set; }
// public virtual ICollection<BusinessAddress> BusinessAddress { get; set; }
}
Business
[Table("Business")]
public partial class Business
{
public Business()
{
BusinessAddress = new HashSet<BusinessAddress>();
}
[Key]
public int BusinessID { get; set; }
public int? categoryid { get; set; }
public int? subcategoryid { get; set; }
public int memberid { get; set; }
public string businessname { get; set; }
public string dealingin { get; set; }
public int? sortOrder { get; set; }
[ForeignKey("memberid")]
public Member Member { get; set; }
public ICollection<BusinessAddress> BusinessAddress { get; set; }
}
BusinessAddress
[Table("BusinessAddress")]
public partial class BusinessAddress
{
[Key]
public int businessaddressid { get; set; }
public int businessid { get; set; }
[ForeignKey("businessid")]
public virtual Business Business { get; set; }
public string address { get; set; }
}
Below is my EF query:
var list = _dbContext.Member.Include("Business").OrderByDescending(x => x.SortOrder).ThenBy(x => x.firstname).ToList();
I want to get Member with business and BusinessAddresses. I have tried Include("BusinesADdress") that didn't work, Help me please.
Try using Include accepting lambda selector followed by ThenInclude:
var list = _dbContext.Member
.Include(m => m.Business)
.ThenInclude(b => b.BusinessAddress)
.OrderByDescending(x => x.SortOrder)
.ThenBy(x => x.firstname)
.ToList();
Also you can just append all needed relations via . in the Include call but in general I would recommend against it cause the first approach is type safe:
var list = _dbContext.Member
.Include("Business.BusinessAddress")
.OrderByDescending(x => x.SortOrder)
.ThenBy(x => x.firstname)
.ToList();

Why can't I get all data related to my model?

I'm using lazyloading to get all my related data and return json to my API. But I can't seem to get all my related data.
Not sure how to use the include syntax correctly either.
public class Recipe
{
public int Id { get; set; }
public string RecipeDescription { get; set; }
public int? HowManyPersons { get; set; }
public int? TimeCreated { get; set; }
public virtual ICollection<IngredientQuantity> IngredientQuantities { get; set;}
public int RecipeTypeId { get; set; }
public virtual RecipeType RecipeType { get; set; }
public string PreparationMethods { get; set; }
}
public class IngredientQuantity
{
public int id { get; set; }
public int IngredientId { get; set; }
[ForeignKey("IngredientId")]
public virtual Ingredient Ingredient { get; set; }
public string Amount { get; set; }
public int QuantityTypeId { get; set; }
[ForeignKey("QuantityTypeId")]
public virtual QuantityType QuantityType { get; set; }
public int RecipeId { get; set; }
[ForeignKey("RecipeId")]
public virtual Recipe Recipe { get; set; }
}
public class Ingredient
{
public int Id { get; set; }
public string IngredientDecsription { get; set; }
}
public async Task<IEnumerable<Recipe>> GetAll()
{
try
{
var recipe = await context.Recipe
.Include(r => r.RecipeType)
.Include(r => r.IngredientQuantities)
.ThenInclude(iq => iq.Ingredient)
.ToListAsync();
return recipe;
}
catch(Exception ex)
{
return null;
}
}
this is my json output:
{"results":[{"ingredientQuantities":[{"ingredient":{"id":1,"ingredientDecsription":"Spinazie"},"quantityType":{"id":1,"quantityTypeDescription":"Handen"}
You can configure Json.NET to ignore cycles that it finds in the object graph.Try to add below code in startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
...
}
Refer to Related data and serialization

Categories

Resources