Entity Framework trying to use methods to create joins help needed - c#

I could really use some help, I have the following SQL query that works and have been trying to convert it to Entity Framework to no avail. I am using the Methods rather than the other option, I think that is how you say it.
Anyway the SQL is
SELECT c.ACCOUNTID,
c.TOACCOUNTID,
fa.ACCOUNTNAME, ta.ACCOUNTNAME,
p.PAYEENAME
FROM checking AS c
LEFT JOIN
ACCOUNT AS fa ON c.ACCOUNTID = fa.ACCOUNTID
LEFT JOIN
ACCOUNT AS ta ON c.TOACCOUNTID = ta.ACCOUNTID
LEFT JOIN
PAYEE AS p ON c.PAYEEID = p.PAYEEID
WHERE c.ACCOUNTID == 1 OR
c.TOACCOUNTID == 1;
So far I have managed to get it this far.
var checking =
db.Transactions
.Where(item => item.ACCOUNTID == LookupAccount || item.TOACCOUNTID == LookupAccount)
.GroupJoin(db.PAYEE,
transaction => transaction.PAYEEID,
payee => payee.PAYEEID,
(check, payee) => new { Payee = payee }
).SelectMany(
transaction => transaction.Payee.DefaultIfEmpty(),
(transaction, payee) => new { Payee = payee })
.Select(item => new
{
ToAccount = item.ToAccount.AccountName
FromAccount = item.FromAccount.AccountName
Withdrawal = 0,
Deposit = 0,
Payee = item.Payee.PAYEENAME
}).ToList();
The issue I have now, is that I am not sure I understand how joins work in this manner, every time I try to get the 2 other joins in I end up falling flat on my face.
When I add this to the above code, the Payee section is out of whack and I don't understand why. I know it has to do with the select new {} section, I could really use some help understanding how this works.
.Join(db.ACCOUNT,
check => check.ACCOUNTID,
account => account.ACCOUNTID,
(check, fromaccount) => new { FromAccount = fromaccount }
)
Models
Transaction
public partial class Transaction
{
[Key]
public int Id { get; set; }
public int AccountID { get; set; }
public int? ToAccountId { get; set; }
public int PayeeId { get; set; }
[Column(TypeName = "numeric")]
public decimal TransAmount { get; set; }
[Column(TypeName = "numeric")]
public decimal ToTransAmount { get; set; }
public virtual Account Account { get; set; }
}
Account
public partial class Account
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Account()
{
Transaction = new HashSet<Transaction>();
}
[Key]
public int AccountId { get; set; }
[Required]
[StringLength(150)]
public string AccountName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Transaction> Transaction { get; set; }
}

This won't work as a comment, but it might get you further. You have 2 FK in your tx class so you need 2 navigation properties:
public partial class Transaction
{
public int Id { get; set; } // Key by convention
public int FromAccountID { get; set; }
public virtual Account FromAccount { get; set; }
public int? ToAccountId { get; set; }
public virtual Account ToAccount { get; set; }
public int PayeeId { get; set; }
public virtual Payee Payee { get; set; }
public decimal TransAmount { get; set; }
public decimal ToTransAmount { get; set; }
}
public partial class Account
{
public Account()
{
Transaction = new HashSet<Transaction>();
}
public int AccountId { get; set; } // Key by convention
[Required]
[StringLength(150)]
public string AccountName { get; set; }
[InverseProperty("FromAccount")]
public virtual ICollection<Transaction> TransactionsFrom { get; set; }
[InverseProperty("ToAccount")]
public virtual ICollection<Transaction> TransactionsTo { get; set; }
}
Now your query becomes:
var checking =
db.Transactions
.Include(tx => tx.Payee)
.Include(tx => tx.FromAccount)
.Include(tx => tx.ToAccount)
.Where(tx => tx.FromAccountId == lookupAccountId || tx.ToAccountId == lookupAccountId)
.Select(tx => new
{
ToAccountName = tx.ToAccount.AccountName
FromAccountName = tx.FromAccount.AccountName
Withdrawal = tx.ToTransAmount,
Deposit = tx.TransAmount,
PayeeName = tx.Payee.PAYEENAME
}).ToList();
https://coding.abel.nu/2012/06/dont-use-linqs-join-navigate/

I have created this as an answer, for any one else who may come across this and is learning the syntax.
The reason I wasn't getting it to work was really down to a lack of how EF actually works when joining.
Transactions
.Join(Accounts,
tr => tr.AccountId,
ac => ac.AccountId,
(tr, ac) => new { Transaction = tr, ac})
.GroupJoin(Accounts,
tr => tr.Transaction.ToAccountId,
ta => ta.AccountId,
(tr, ta) => new { Transaction = tr.Transaction, Account = ta, FromAccount = tr.ac})
.SelectMany(
transaction => transaction.Account.DefaultIfEmpty()
,(transaction, account) => new { tt = transaction.Transaction, ToAccount = account, FromAccount = transaction.FromAccount}
)
.GroupJoin(Payees,
tr => tr.tt.PayeeId,
payee => payee.PAYEEID,
(tr, payee) => new { Transaction = tr, Payee = payee })
.SelectMany(
transaction => transaction.Payee.DefaultIfEmpty(),
(transaction, payee) => new {transaction = transaction.Transaction.tt, FromAccount = transaction.Transaction.FromAccount, ToAccount = transaction.Transaction.ToAccount, Payee = payee })
.Where (x=> x.FromAccount.AccountId == 1 || x.ToAccount.AccountId == 1)
.Select(item => new
{
item.transaction.Id,
TransDate = item.transaction.TRANSDATE,
Number = item.transaction.TransactionNumber,
Payee = item.Payee.PAYEENAME,
Status = item.transaction.Status,
Category = item.transaction.CategoryId,
FromAccount = item.FromAccount.AccountName,
ToAccount = item.ToAccount.AccountName,
Withdrawal = 0,
Deposit = 0,
Notes = item.transaction.Notes
})
The parts that I was not understanding was the relationship in code, from the joins and how the Selects then took over and created a hierarchy through the objects. I really wish that I could have used Navigation here, but from my understanding of Navigation's there needs to be a relationship between the columns and nearly all the fields being joined on here, can be null and therefore a foreign key would not satisfy the requirements for a Navigation.
I am still convinced there is a better way, but for now I have placed this here for others who are still learning and wish to see a working solution.

Related

Querying Mongo when a document have one to many relationship in a single well structed query using mongo C# driver

I'll try to explain it as simple as possible.
We have these 5 very simple classes. Any class that not end with the DTO suffix represent a real document living inside a mongo collection.
public class TruckSingleDriver
{
public string Id { get; set; }
public string DriverId { get; set; }
}
public class TruckSingleDriverDTO
{
public string Id { get; set; }
public Driver Driver { get; set; }
}
public class TruckManyDrivers
{
public string Id { get; set; }
public IEnumerable<string> DriversIds { get; set; }
}
public class TruckManyDriversDTO
{
public string Id { get; set; }
public IEnumerable<Driver> Drivers { get; set; }
}
public class Driver
{
public string Id { get; set; }
}
the simplest way to get TruckSingleDriverDTO with one query will be as follow:
public TruckSingleDriverDTO GetTruckSingleDriverDTO(string truckId)
{
var truckCollection = mongo.GetDatabase("mydb").GetCollection<TruckSingleDriver>("Trucks");
var driverCollection = mongo.GetDatabase("mydb").GetCollection<Driver>("Drivers");
TruckSingleDriverDTO truckDTO = truckCollection.AsQueryable()
.Where(truck => truck.Id == truckId)
.Join(driverCollection, truck => truck.DriverId, driver => driver.Id,
(truck, driver) => new { Id = truck.Id, Driver = driver })
.ToEnumerable() //needed although it seems not
.Select(res => new TruckSingleDriverDTO() { Id = res.Id, Driver = res.Driver })
.Single();
return truckDTO;
}
What i want to achieve is to get TruckManyDriversDTO in a single query, is there away to do it?
public TruckManyDriversDTO GetTruckManyDriversDTO(string truckId)
{
var trucks = mongo.GetDatabase("mydb").GetCollection<TruckManyDrivers>("Trucks");
var drivers = mongo.GetDatabase("mydb").GetCollection<Driver>("Drivers");
/*
* here i need your help
* keep in mind that i want it in a single query
* below this, ill show the simple way to achieve it with 2 queries
*/
TruckManyDrivers truck = trucks.Find(t => t.Id == truckId).Single();
IEnumerable<Driver> driverList = drivers.Find(d => truck.DriversIds.Contains(d.Id)).ToEnumerable();
return new TruckManyDriversDTO() { Id = truck.Id, Drivers = driverList };
}
I got help from this site: https://www.csharpschool.com/blog/linq-join
The best solution I could come up with:
public TruckManyDriversDTO GetTruckManyDriversDTO(string truckId)
{
var Trucks = mongo.GetDatabase("mydb").GetCollection<TruckManyDrivers>("Trucks").AsQueryable();
var Drivers = mongo.GetDatabase("mydb").GetCollection<Driver>("Drivers").AsQueryable();
var query = from truck in Trucks where truck.Id == truckId
let truckDrivers = from driver in Drivers
where truck.DriversIds.Contains(driver.Id) select driver
select new { Truck = truck, Drivers = truckDrivers };
TruckManyDriversDTO dto = query.Select(a => new TruckManyDriversDTO() { Id = a.Truck.Id, Drivers = a.Drivers } ).Single();
return dto;
}

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; }
}

Query separate collection in RavenDB Index (WHERE IN)

Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes

How do I negotiate joins and groupings based on nested properties in LINQ?

So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}

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

Categories