MVC Core Repository Query Data all at Once - c#

We have Customer transactional table with multiple lookup tables with foreign keys. We want to see the 3 table joined together.
How do I make sure Service call is conducted one? I don't want to grab All unnecessary customer transactions, and then filter in memory. I want to make one service call to find customer transaction > 50 in one sql query.
Repository:
CustomerTransaction GetTransactionbyCustomerId(int customerid)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid).ToList()
return result;
}
Service Call:
void GetByCustomerTransactionGreaterthan50(int id)
{
var newdata = CustomerTransaction.GetByCustomerTransactionId();
nt.newdata.Where(x => x.PurchaseAmount > 50).ToList()
return newdata;
}
Models:
public class CustomerTransaction
{
public int CustomerTransactionId{ get; set; },
public int ProductTypeId {get; set; }, //joins to ProductTypeTable
public int StatusID {get; set; }, //joins to StatusTypeTable
public string DateOfPurchase{ get; set; },
public int PurchaseAmount { get; set; },
}
public class ProductType
{
public int ProductTypeId{ get; set; }
public string ProductName { get; set; },
public string ProductDescription { get; set; },
}
public class StatusType
{
public int StatusId{ get; set; }
public string StatusName{ get; set; },
public string Description{ get; set; },
}

You can make a Select projection before the ToList() this will make sure that your sql statement that EF generate include just fields you are projecting too.
Something like:
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid).Select(x=> new CustomerTransaction()).ToList()
and to filter you can include your filter in the where condition:
ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid && x.PurchaseAmount > 50)
Or make your GetTransactionbyCustomerId return a Query and filter as you did in the service call

Option 1
You will need to adjust your repository in order to be able to add to your query in Service Layer:
Repository:
IQueryable<CustomerTransaction> QueryTransactionbyCustomerId(int customerid)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid);
return result;
}
Option 2
Or to create another method in your data access layer:
Repository:
List<CustomerTransaction> GetTransactionByCustomerIdAndAmount(int customerid, int amount)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid && x.PurchaseAmount > amount).ToList()
return result;
}
Based on your architecture, you should pick one of these options.

Related

How to select needed records in joining table using LINQ with tables which have many to many relationship?

I have many to many relationship between entities user and group, I also have joining table GroupParticipants.
public class User
{
public string Id {get; set;}
public ICollection<GroupParticipant> Group { get; set;}
}
public class Group
{
public int Id { get; set; }
public ICollection<GroupParticipant> Participants { get; set; }
}
public class GroupParticipant
{
public int GroupId { get; set; }
public string ParticipantId { get; set; }
public User Participant { get; set; }
public Group Group { get; set; }
}
I need to select groups which user specified user did not join. I want to do something like:
string userId = 5;
var groupsAvailableToJoin = await _context.Groups
.Where(group => group.Participants.Id != userId);
Thanks!
A query like:
_context.Groups.Where(g =>
!_context.GroupParticipants.Any(gp => gp.UserId == userId && gp.GroupId == g.I'd
);
Should translate to:
SELECT * FROM Groups g
WHERE NOT EXISTS(SELECT null FROM groupParticipants gp WHERE gp.UserId = 5 AND gp.GroupId = g.Id)
Which should be a reasonably performant way of getting you what you're looking for.. I'm sure that the GroupParticipants columns are indexed..
There are various ways to write this - if you find a two step approach easier to understand, it's effectively the same as:
var joined = _context.GroupParticipants.Where(gp => gp.UserId == 5).Select(gp => gp.GroupId).ToList();
var notJoined = _context.Groups.Where(g => !joined.Contains(g.Id));
This one translates as a NOT IN (list,of,groups,they,are,in) for a similar effect

Calculated property for model EF Core - Property or Method?

I am new to EF core. I have a Customer model with the usual properties (Name,Address,Email).
I need a property to calculate the current balance for the customer.
This will be quite an intensive computation (once many records are stored) so am I correct in thinking that it should be stored in a Method, rather than a calculated property?
I am assuming I need to add a method such as .GetCurrentBalance().
Where would I put this method?
Simplified code below:
My Customer Model
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public virtual IEnumerable<SalesInvoice> SalesInvoices{ get; set; }
}
My Sales Invoice Model
public class SalesInvoice
{
public int SalesInvoiceId { get; set; }
public int CustomerId { get; set; }
public virtual IEnumerable<SalesInvoiceDetail> SalesInvoiceDetails{ get; set; }
}
My Sales Invoice Detail Model
public class SalesInvoiceDetail
{
public int SalesInvoiceDetailId { get; set; }
public int Qty { get; set; }
public decimal UnitPrice { get; set; }
}
Create helper methods which returns desired results. Everything should play around IQueryable:
public class CustomerIdWithBalance
{
public int CustomerId { get; set; }
public decimal Balance { get; set; }
}
public class CustomerWithBalance
{
public Customer Customer { get; set; }
public decimal Balance { get; set; }
}
public static class BusinessLogicExtensions
{
public static IQueryable<CustomerIdWithBalance> GetCustomerIdAndBalance(this IQueryable<Customer> customers)
{
var grouped =
from c in customers
from si in c.SalesInvoices
from sid in si.SalesInvoiceDetails
group sid by new { c.CustomerId } into g
select new CustomerIdWithBalance
{
g.Key.CustomerId,
Balance = x.Sum(x => x.Qty * x.UnitPrice)
}
return grouped;
}
public static IQueryable<CustomerWithBalance> GetCustomerAndBalance(this IQueryable<CustomerIdWithBalance> customerBalances, IQueryable<Customer> customers)
{
var query =
from b in customerBalances
join c in customers on b.CustomerId equals c.CustomerId
select new CustomerWithBalance
{
Customer = c,
Balance = b.Balance
};
return query;
}
}
Later when you need to return that with API call (hypothetic samples)
var qustomerIdsWithHighBalance =
from c in ctx.Customers.GetCustomerIdAndBalance()
where c.Balance > 1000
select c.CustomerId;
var qustomersWithHighBalance =
ctx.Customers.GetCustomerIdAndBalance()
.Where(c => c.Balance > 1000)
.GetCustomerAndBalance(ctx.Customers);
var customersByMatchAndPagination = ctx.Customers
.Where(c => c.Name.StartsWith("John"))
.OrderBy(c => c.Name)
.Skip(100)
.Take(50)
.GetCustomerAndBalance(ctx.Customers);
You will get desired results without additional database roundtrips. With properties you may load too much data into the memory.
It is everything about using EF with its limitations. But world is not stopped because EF team is too busy to create performance effective things.
Let's install https://github.com/axelheer/nein-linq
And create extension methods around Customer
public static class CustomerExtensions
{
[InjectLambda]
public static TotalBalance(this Customer customer)
=> throw new NotImplmentedException();
static Expression<Func<Customer, decimal>> TotalBalance()
{
return customer =>
(from si in customer.SalesInvoices
from sid in si.SalesInvoiceDetails
select sid)
.Sum(x => x.Qty * x.UnitPrice));
}
}
And everything become handy:
var customersWithHighBalance =
from c in ctx.Customers.ToInjectable()
where c.TotalBalance() > 1000
select c;
var customersWithHighBalance =
from c in ctx.Customers.ToInjectable()
let balance = c.TotalBalance()
where balance = balance > 1000
select new CustomerWithBalance
{
Customer = c,
Balance = balance
};
var customersWithBalance =
from c in ctx.Customers.ToInjectable()
where c.Name.StartsWith("John")
select new CustomerWithBalance
{
Customer = c,
Balance = c.TotalBalance()
};
var paginated =
.OrderBy(c => c.Name)
.Skip(100)
.Take(50);
If you would prefer to calculate at property level. Add an InvoiceBal readonly field to the SalesInvoice model.
public class SalesInvoice
{
public double InvoiceBal => SalesInvoiceDetails.Sum(x => x.Qty * x.UnitPrice)
public virtual IEnumerable<SalesInvoiceDetail> SalesInvoiceDetails{ get; set; }
}
Add another TotalBalance readonly field to the Customer that sums the whole thing
public class Customer
{
public double TotalBal => SalesInvoices.Sum(x => x.InvoiceBal)
public virtual IEnumerable<SalesInvoice> SalesInvoices{ get; set; }
}

Get objects whose property does not exist in enumerable

Multiple answers have led me to the following 2 solutions, but both of them do not seem to be working correctly.
What I have are 2 objects
public class DatabaseAssignment : AuditableEntity
{
public Guid Id { get; set; }
public string User_Id { get; set; }
public Guid Database_Id { get; set; }
}
public class Database : AuditableEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Server { get; set; }
public bool IsActive { get; set; }
public Guid DatabaseClientId { get; set; }
}
Now, the front-end will return all selected Database objects (as IEnumerable) for a given user. I am grabbing all current DatabaseAssignments from the database for the given user and comparing them to the databases by the Database.ID property. My goal is to find the DatabaseAssignments that I can remove from the database. However, my solutions keep returning all DatabaseAssignments to be removed.
if (databases != null)
{
var unitOfWork = new UnitOfWork(_context);
var userDatabaseAssignments = unitOfWork.DatabaseAssignments.GetAll().Where(d => d.User_Id == user.Id);
//var assignmentsToRemove = userDatabaseAssignments.Where(ud => databases.Any(d => d.Id != ud.Database_Id));
var assignmentsToRemove = userDatabaseAssignments.Select(ud => userDatabaseAssignments.FirstOrDefault()).Where(d1 => databases.All(d2 => d2.Id != d1.Database_Id));
var assignmentsToAdd = databases.Select(d => new DatabaseAssignment { User_Id = user.Id, Database_Id = d.Id }).Where(ar => assignmentsToRemove.All(a => a.Database_Id != ar.Database_Id));
if (assignmentsToRemove.Any())
{
unitOfWork.DatabaseAssignments.RemoveRange(assignmentsToRemove);
}
if (assignmentsToAdd.Any())
{
unitOfWork.DatabaseAssignments.AddRange(assignmentsToAdd);
}
unitOfWork.SaveChanges();
}
I think u are looking for an Except extension, have a look at this link
LINQ: Select where object does not contain items from list
Or other way is with contains see below Fiddler link :
https://dotnetfiddle.net/lKyI2F

Joins with Dapper return null

I have the following classes:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public List<Event> Events { get; set; }
}
public class Event
{
public int Id { get; set; }
public int UserId { get; set; }
public string EventText { get; set; }
public string StartTime { get; set; }
public string EndTime { get; set; }
public string Day { get; set; }
public string ColorIdentifier { get; set; }
public int Week { get; set; }
}
I'm trying to get all the users and their events with Dapper like this:
var sql = "SELECT u.Id, e.UserId, e.EventText FROM cpc.PLANNING_TOOL_USERS u LEFT JOIN cpc.PLANNING_TOOL_EVENTS e ON u.Id=e.UserId";
var result = SqlMapper.Query<User, Event, User>(connection, sql, (u, e) =>
{
if (u.Events == null)
u.Events = new List<Event>();
u.Events.Add(e);
return u;
}, splitOn: "Id, UserId");
The Id for the user is returned back, but the list of events is not populated. I have looked at many examples here on Stack Overflow regarding this, but I can't see what I'm doing wrong.
To omit the situation that SQL returns no data I have just mocked two user rows with SQL union.
User with Id=1 and one Event, and User with Id=2 and two Events.
SqlMapper.Query returns flat results that are best for 1 to 1 relation. You have one user to many events relation, so some helper storage needed to maintain that relation as a mapping thru the results. I have used .NET dictionary for that.
My code sample below:
// introducing temporary storage
var usersDictionary = new Dictionary<int, User>();
var sql = #"SELECT 1 Id, 1 UserId, 'EventText1' EventText
union SELECT 2 Id, 2 UserId, 'EventText2' EventText
union SELECT 2 Id, 2 UserId, 'Another EventText2' EventText";
var result = SqlMapper.Query<User, Event, User>(connection, sql, (u, e) =>
{
if (!usersDictionary.ContainsKey(u.Id))
usersDictionary.Add(u.Id, u);
var cachedUser = usersDictionary[u.Id];
if (cachedUser.Events == null)
cachedUser.Events = new List<Event>();
cachedUser.Events.Add(e);
return cachedUser;
}, splitOn: "UserId");
// we are not really interested in `result` here
// we are more interested in the `usersDictionary`
var users = usersDictionary.Values.AsList();
Assert.AreEqual(2, users.Count);
Assert.AreEqual(1, users[0].Id);
CollectionAssert.IsNotEmpty(users[0].Events);
Assert.AreEqual(1, users[0].Events.Count);
Assert.AreEqual("EventText1", users[0].Events[0].EventText);
Assert.AreEqual(2, users[1].Events.Count);
I hope that helped you solving your mapping issue and events being null.

Using Lambda to insert derived attribute into IQueryable dataset

I have the following query:
IQueryable<BarcodeQuery> barcodes = db.Barcodes.Select(b => new BarcodeQuery
{
id = b.id,
category_id = b.category_id,
...
checkout = b.Checkouts.Select(c => new CheckoutChild
{
id = c.id,
loanee_id = c.loanee_id,
...
})
.Where(c => c.datein == null)
.FirstOrDefault()
});
And so on. It's based on this model:
public class BarcodeQuery
{
public int id { get; set; }
public int category_id { get; set; }
...
public CheckoutChild checkout { get; set; }
public CheckoutStatus checkoutStatus { get; set; }
}
My question is about CheckoutStatus down there at the bottom. It looks like this:
public class CheckoutStatus
{
public string status { get; set; }
public int daysUntilDue { get; set; }
public int daysOverdue { get; set; }
}
All of those values are derived from information I get from the query--none of them are in the database itself. What is the best way of inserting the CheckoutStatus values into each barcode record?
I have a function that creates the CheckoutStatus values themselves, I just don't know how to get them into the barcode records.
Thanks!
If b has just be created with new, how can b.Checkouts contain something? I do not really understadn what you are trying to do.
EF is converting the lambda expression into a SQL statement. Therefore you can only use expressions that can actually be translated to SQL. Just query the barcodes from the DB and then add the missing information to the barcodes returned in a loop.
var barcodes = db.Barcodes.Select(...).ToList();
foreach (Barcode b in barcodes) {
b.Checkouts = ...
}

Categories