Searching Record List using single parameter Id and compare with multiple columns EF - entity-framework

I want to fetch records from table but I have single parameter Id. I want to compare that single parameter with multiple columns. Here is my code.
var list = _ctx.Scheduler.Where(x => x.SaloonId == Id).ToList().Select(x => new AppointmentListModel
{
Id = x.ID,
StatusName = x.AppointmentStatus.StatusName,
AppointmentDate = x.AppointmentDate.ToShortDateString(),
AppointmentDay = x.AppointmentDay,
CustomerName = x.CustomerDetail.UserMaster.FirstName + " " + x.CustomerDetail.UserMaster.LastName,
FromTime = x.AppointmentTimeFrom,
ToTime = x.AppointmentTimeTo
}).ToList();
The above query is to get appointments from scheduler table on saloon Id. Here is the next query which is used to get record from same table but compare with customerId.
var list = _ctx.Scheduler.Where(x => x.CustomerId == Id).ToList().Select(x => new AppointmentListModel
{
Id = x.ID,
StatusName = x.AppointmentStatus.StatusName,
AppointmentDate = x.AppointmentDate.ToShortDateString(),
AppointmentDay = x.AppointmentDay,
CustomerName = x.CustomerDetail.UserMaster.FirstName + " " + x.CustomerDetail.UserMaster.LastName,
FromTime = x.AppointmentTimeFrom,
ToTime = x.AppointmentTimeTo
}).ToList();
How to achieve this by using single query.

Try using conditional logical OR (||) operator:
var list = _ctx.Scheduler
.Where(x => x.SaloonId == Id || x.CustomerId == Id)
.ToList()
.Select(x => new AppointmentListModel
{
Id = x.ID,
StatusName = x.AppointmentStatus.StatusName,
AppointmentDate = x.AppointmentDate.ToShortDateString(),
AppointmentDay = x.AppointmentDay,
CustomerName = x.CustomerDetail.UserMaster.FirstName + " " + x.CustomerDetail.UserMaster.LastName,
FromTime = x.AppointmentTimeFrom,
ToTime = x.AppointmentTimeTo
})
.ToList();

You just need to add both conditions to Where clause.
var list = _ctx.Scheduler.Where(x => x.SaloonId == Id || x.CustomerId == Id).ToList().Select(x => new AppointmentListModel
{
Id = x.ID,
StatusName = x.AppointmentStatus.StatusName,
AppointmentDate = x.AppointmentDate.ToShortDateString(),
AppointmentDay = x.AppointmentDay,
CustomerName = x.CustomerDetail.UserMaster.FirstName + " " + x.CustomerDetail.UserMaster.LastName,
FromTime = x.AppointmentTimeFrom,
ToTime = x.AppointmentTimeTo
}).ToList();

Related

How can I get empty months while grouping records by last 12 month in EF Core 5

I have code snippet below for grouping records for last 12 months and it works properly but I just noticed that empty months is not included. Where did I go wrong?
Thanks in advance
public IQueryable<DashboardGrouping> DashboardStats()
{
var yearAgo = DateTime.Now.AddYears(-1);
var date = new DateTime(yearAgo.Year, yearAgo.Month, 1);
var items = context.Set<Transaction>()
.Where(x => x.IsActive &&
x.CreatedAt.HasValue && x.CreatedAt.Value.Date >= date.Date && x.PaymentStatusId ==
PaymentStatus.Completed)
.Include(x => x.Payment)
.Include(x => x.Branch)
.AsNoTracking()
.Select(x => new
{
Year = x.CreatedAt.Value.Year,
Month = x.CreatedAt.Value.Month,
CashAmount = x.Payment.CashAmount,
CardAmount = x.Payment.CardAmount,
})
.GroupBy(x => new
{
Year = x.Year,
Month = x.Month,
})
.Select(x => new DashboardGrouping
{
Year = x.Key.Year,
Month = x.Key.Month,
TotalSale = x.Sum(s => s.CashAmount + s.CardAmount)
});
return items;
}
You can do client-side postprocessing and enrich result with missing records.
Helper function for generating months:
static IEnumerable<DateTime> GetMonths(DateTime startDate, DateTime endDate)
{
startDate = new DateTime(startDate.Year, startDate.Month, 1);
endDate = new DateTime(endDate.Year, endDate.Month, 1);
while (startDate < endDate)
{
yield return startDate;
startDate = startDate.AddMonths(1);
}
}
Postprocessing:
var currentDate = DateTime.Now;
var yearAgo = currentDate.AddYears(-1);
var months = GetMonths(yearAgo, currentDate);
var stats = DashboardStats().ToList();
// left join months to actual data
var query =
from m in months
join s in stats on new { m.Year, m.Month } equals new { s.Year, s.Month } into gj
from s in gj.DefaultIfEmpty()
select s ?? new DashboardGrouping
{
Year = m.Year,
Month = m.Month,
TotalSale = 0
};
var result = query.ToList();

The instance of entity type 'x' cannot be tracked because another instance with the same key value for {'a', 'b'} is already being tracked

I got the error in the title while editing relational table between many-to-many relationship. It will not let duplicate on table, so I try to remove rows and then create new one but it didn't work.
public void Update(ThermoformProduct entity, int[] thermoformCategoryIds)
{
using (var context = new ShopContext())
{
var product = context.ThermoformProducts
.Include(i => i.ThermoformProductCategories)
.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
product.Code = entity.Code;
product.Culture = entity.Culture;
product.Renk = entity.Renk;
product.UstGenislik = entity.UstGenislik;
product.UstCap = entity.UstCap;
product.AltCap = entity.AltCap;
product.TbCap = entity.TbCap;
product.Yukseklik = entity.Yukseklik;
product.Hacim = entity.Hacim;
product.TamHacim = entity.TamHacim;
product.Baski = entity.Baski;
product.SosisIciAdet = entity.SosisIciAdet;
product.KoliIciAdet = entity.KoliIciAdet;
product.ImageUrl = entity.ImageUrl;
product.ThermoformProductCategories.RemoveAll(s=>s.ProductId == product.ProductId);
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
ThermoformProduct = product,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
context.SaveChanges();
}
}
}
EF cannot track two different instance of an entity type with same primary key. You have included the related ThermoformProductCategory entities, so they are being tracked by the context. When you remove them, they are cleared from the ThermoformProductCategories property of that product, but they are not removed from the context, and are still being tracked. Finally when you create the new list of ThermoformProductCategory, some of the new ones' primary key are matching the previous ones' (which already exist in the context)
Since you are creating the entire list again, you don't need to fetch the related entities in the first place. Simply assign a new list and EF will replace the entire list of related entities -
var product = context.ThermoformProducts.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
// set all the properties
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
CategoryId = catid
}).ToList();
context.SaveChanges();
}
Two things:
The filter is redundant - the ThermoformProductCategories navigation property should already be filtered.
product.ThermoformProductCategories.RemoveAll(s=>s.ProductId == product.ProductId);
instead do this:
product.ThermoformProductCategories.RemoveAll(); // Or .Clear()
Don't set navigation properties in this case, just the foreign key values - this should resolve your issue:
Instead of:
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
ThermoformProduct = product,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
Do:
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId, // Even this might be redundant since you're adding to the product navigation property list.
CategoryId = catid
}).ToList();
both answers was solution here is my final form if anyone gets trouble
public void Update(ThermoformProduct entity, int[] thermoformCategoryIds)
{
using (var context = new ShopContext())
{
var product = context.ThermoformProducts
.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
product.Code = entity.Code;
product.Culture = entity.Culture;
product.Renk = entity.Renk;
product.UstGenislik = entity.UstGenislik;
product.UstCap = entity.UstCap;
product.AltCap = entity.AltCap;
product.TbCap = entity.TbCap;
product.Yukseklik = entity.Yukseklik;
product.Hacim = entity.Hacim;
product.TamHacim = entity.TamHacim;
product.Baski = entity.Baski;
product.SosisIciAdet = entity.SosisIciAdet;
product.KoliIciAdet = entity.KoliIciAdet;
product.ImageUrl = entity.ImageUrl;
var cmd = "delete from ThermoformProductCategory where ProductId=#p0";
context.Database.ExecuteSqlRaw(cmd, product.ProductId);
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
context.Entry(product).State = EntityState.Modified;
context.SaveChanges();
}
}
}

Prevent sort result of union in entity framework after select and distinct

Before I asked Prevent sort result of union in entity framework
I got my answer but now I have new problem with this issue. I have this code:
var productExactlyTitle = products.Where(x => x.Title == keyword);
var productStartWithPhrase = products.Where(x => x.Title.StartsWith(keyword));
var productStartWithWord = products.Where(x => x.Title.StartsWith(keyword + " "));
var productContainsWord = products.Where(x => x.Title.Contains(" " + keyword + " "));
var productContainsPhrase = products.Where(x => x.Title.Contains(keyword)
|| x.Title.Contains(keyword)
|| x.SubTitle.Contains(keyword)
|| x.OtherName.Contains(keyword));
var splitWords = keyword.Split(' ');
var productSplitWordSearch = splitWords.Aggregate(products, (current, word) => current.Where(x => x.Title.Contains(word.Trim())));
var p1 = productExactlyTitle.Select(x => new { Item = x, Order = 1 });
var p2 = productStartWithWord.Select(x => new { Item = x, Order = 2 });
var p3 = productStartWithPhrase.Select(x => new { Item = x, Order = 3 });
var p4 = productContainsWord.Select(x => new { Item = x, Order = 4 });
var p5 = productContainsPhrase.Select(x => new { Item = x, Order = 5 });
var p6 = productSplitWordSearch.Select(x => new { Item = x, Order = 6 });
var productList = p1
.Union(p2)
.Union(p3)
.Union(p4)
.Union(p5)
.Union(p6)
.OrderBy(x => x.Order)
.Take(21)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Item.Id,
Title = x.Item.Title,
Price = DiscountController.ApplyDiscountToPrice(x.Item).ToPrice(),
Image = x.Item.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
Result of above code have duplicate records and I have to use select and distinct to remove duplicate records. so I change my code like this:
var productList = p1
.Union(p2)
.Union(p3)
.Union(p4)
.Union(p5)
.Union(p6)
.OrderBy(x => x.Order)
.Select(x => x.Item)
.Distinct()
.Take(21)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Id,
Title = x.Title,
Price = DiscountController.ApplyDiscountToPrice(x).ToPrice(),
Image = x.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
But after that my result is sorted with Id column again.
How can I solved this?
First, since adding Order field to each query makes the record unique, using Union (which is supposed to remove duplicates) doesn't make sense, so simply use Concat instead.
Second, to remove duplicates and not lose the Order field needed for later ordering, you need to group by Item and take the minimum Order for each group. The rest is the same as in the original solution.
var productList = p1
.Concat(p2)
.Concat(p3)
.Concat(p4)
.Concat(p5)
.Concat(p6)
.GroupBy(e => e.Item)
.Select(g => new { Item = g.Key, Order = g.Min(e => e.Order) })
.OrderBy(e => e.Order)
.Select(e => e.Item)
.Take(21)
.AsEnumerable()
...

Prevent sort result of union in entity framework

In SQL server union, result is sorted based on primary key column. I want to prevent this behavior in entity framework.
In this post, #praveen has explained how to do this in pure sql. But I want to do this in entity framework.
My code:
public virtual ActionResult Search(string keyword)
{
var products = _db.Products
.Where(x => x.IsActive)
.AsQueryable();
var productExactlyTitle = products.Where(x => x.Title == keyword);
var productStartTitle = products.Where(x => x.Title.StartsWith(keyword));
var productContainsTitle = products.Where(x => x.Title.Contains(keyword)
|| x.Title.Contains(keyword)
|| x.SubTitle.Contains(keyword)
|| x.OtherName.Contains(keyword));
var productList = productExactlyTitle.Union(productStartTitle)
.Union(productContainsTitle)
.Take(10)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Id,
Title = x.Title,
Price = x.Price.ToPrice(),
Image = x.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
// some code ...
}
I want to show records with below order:
First: records of productExactlyTitle
Second: records of productStartTitle
Third: records of productContainsTitle
But result is sorted with Id column! and I don't want this.
Is there a way for do this?
In SQL all queries without an order by explicitly set is considered unordered. (and EF queries a translated into SQL). So if you want a specific order after your union just specify it.
var result = q1.Union(q2).OrderBy(x => x.?);
For your specific case:
var p1 = productExactlyTitle.Select(x => new { Item = x, Order = 1 });
var p2 = productStartTitle.Select(x => new { Item = x, Order = 2 });
var p3 = productContainsTitle.Select(x => new { Item = x, Order = 3 });
var productList = p1.Union(p2)
.Union(p3)
.OrderBy(x => x.Order)
.Select(x => x.Item)
.Take(10);

Single database call pulling data from multiple tables in EF Core

The following code currently opens a connection three times to my database, to pull out each object.
Is there a better way to craft the query so the database is only hit once and pulls back all the objects I'm looking for?
var metadataResult = new MetadataViewModel
{
Milestones = goalsContext.Milestones.Select(m => new MilestoneViewModel
{
Id = m.Id,
Name = m.Name,
Year = m.Year,
Date = m.Date
}),
Aggregates = goalsContext.Aggregates.Select(a => new AggregateViewModel
{
Id = a.Id,
Name = a.Name
}),
Metrics = goalsContext.Metrics.Select(m => new MetricViewModel
{
Id = m.Id,
Name = m.Name,
Description = m.Description
})
};
If your view models are a fairly similar shape then you should be able to use Union to get everything in one query and then transform the rows into appropriate ViewModel instances afterwards. Something like the following -
var combinedResults =
context.Products.Select(p => new
{
Type = "Product",
ID = p.ProductID,
Name = p.ProductName,
SupplierName = p.Supplier.CompanyName
})
.Union(
context.Categories.Select(c => new
{
Type = "Category",
ID = c.CategoryID,
Name = c.CategoryName,
SupplierName = (string)null
})
)
.ToList();
var viewModel = new ViewModel
{
Products = combinedResults
.Where(x => x.Type == "Product")
.Select(x => new ProductViewModel
{
ID = x.ID,
Name = x.Name,
SupplierName = x.SupplierName
}),
Categories = combinedResults
.Where(x => x.Type == "Category")
.Select(x => new CategoryViewModel
{
ID = x.ID,
Name = x.Name
})
};