In concomitanza con Test unitari e Iniezione delle dipendenze I (e il mio collega principale) esploriamo i Repository. Tuttavia, non possiamo arrivare a un solido piano di azione per l'implementazione.
In uno scenario di base abbiamo un repository che incapsula un singolo contesto e una o più entità. I metodi pubblici di tale repository restituiscono un risultato List o un singolo entity. IE
public class SomeEntity
{
public int Id { get; set; }
public string SomeValue { get; set; }
}
public class SomeContext : DbContext
{
public virtual DbSet<SomeEntity> SomeEntities { get; set; }
}
public class SomeRepo
{
public SomeRepo()
{
_context = new SomeContext();
}
public SomeEntity GetEntity(int id)
{
return _context.SomeEntities.Find(id);
}
public IQueryable<SomeEntity> GetAllEntities()
{
return _context.SomeEntities;
}
}
Questo è tutto a posto e funziona bene per noi il 99% delle volte. Il quandry è quando ci sono più entità in un Repo e è richiesto un join. Al momento eseguiamo qualcosa come il seguente in una classe UOW che utilizza il repository;
public SomeModel SomeMethod()
{
var entity1 = _repo.GetEntity1();
var entity2 = _repo.GetEntity2();
return from a in entity1
join b in entity2 on a.id equals b.id
select new SomeModel
{
Foo = a.foo,
Bar = b.bar
};
}
Dalle molte discussioni / post / blog / ecc. contraddittorie sui Repository e sui nostri sentimenti personali, questo non sembra giusto. Ciò che non sembra giusto è fare il join all'interno del repository e quindi restituire qualcosa che non è una delle entità.
Il nostro tipico design è di avere un contesto racchiuso all'interno di un repository che è Dependency Injected in una classe UOW. In questo modo possiamo avere un Test unitario che deride il Repo restituendo risultati di DB falsi.
Sapendo che questa è una domanda carica, quale potrebbe essere un buon modello per noi?
Per un esempio più realistico di uno scenario di join (non sono contento di questo codice, è stato un lavoro urgente fare accadere qualcosa, ma è un buon esempio dello scenario che dobbiamo affrontare):
public class AccountingContext : DbContext
{
public DbSet<Vendor> Vendors { get; set; }
public DbSet<Check> Checks { get; set; }
public DbSet<ApCheckDetail> CheckDetails { get; set; }
public DbSet<Transaction> Transactions { get; set; }
public AccountingContext(string connString) : base(connString)
{
}
}
public class AccountingRepo : IAccountingRepo
{
private readonly AccountingContext _accountingContext;
public AccountingRepo(IConnectionStringMaker connectionStringMaker, ILocalConfig localConfig)
{
// code to generate connString
_accountingContext = new AccountingContext(connString);
}
public IEnumerable<Check> GetChecksByDate(DateTime checkDate)
{
return _accountingContext.Checks
.Where(c => c.CHKDATE.Value == checkDate.Date &&
!c.DELVOIDDATE.HasValue);
}
public IEnumerable<Vendor> GetVendors(IEnumerable<string> vendorId)
{
return _accountingContext.Vendors
.Where(v => vendorId.Contains(v.VENDCODE))
.Distinct();
}
public IEnumerable<ApCheckDetail> GetCheckDetails(IEnumerable<string> checkIds)
{
return _accountingContext.CheckDetails
.Where(c => checkIds.Contains(c.CheckId));
}
public IEnumerable<Transaction> GetTransactions(IEnumerable<string> tranNos, DateTime checkDate)
{
var ids = tranNos.ToList();
var sb = new StringBuilder();
sb.Append($"'{ids.First()}'");
for (int i = 1; i < ids.Count; i++)
{
sb.Append($", '{ids[i]}'");
}
var sql = $"Select TranNo = TRANNO, InvoiceNo = INVNO, InvoiceDate = INVDATE, InvoiceAmount = INVAMT, DiscountAmount = DISCEARNED, TaxWithheld = OTAXWITHAMT, PayDate = PAYDATE from APTRAN where TRANNO in ({sb})";
return _accountingContext.Set<Transaction>().SqlQuery(sql).ToList();
}
}
public class AccountingInteraction : IAccountingInteraction
{
private readonly IAccountingRepo _accountingRepo;
public AccountingInteraction(IAccountingRepo accountingRepo)
{
_accountingRepo = accountingRepo;
}
public IList<CheckDetail> GetChecksToPay(DateTime checkDate, IEnumerable<string> excludeVendCats)
{
var todaysChecks = _accountingRepo.GetChecksByDate(checkDate).ToList();
var todaysVendors = todaysChecks.Select(c => c.APCODE).Distinct().ToList();
var todaysCheckIds = todaysChecks.Select(c => c.CheckId).ToList();
var vendors = _accountingRepo.GetVendors(todaysVendors).ToList();
var apCheckDetails = _accountingRepo.GetCheckDetails(todaysCheckIds).ToList();
var todaysCheckDetails = apCheckDetails.Select(a => a.InvTranNo).ToList();
var tranDetails = _accountingRepo.GetTransactions(todaysCheckDetails, checkDate).ToList();
return (from c in todaysChecks
join v in vendors on c.APCODE equals v.VENDCODE
where !c.DELVOIDDATE.HasValue &&
!excludeVendCats.Contains(v.VENDCAT) &&
c.BACSPMT != 1 &&
v.DEFPMTTYPE == "CHK"
select new CheckDetail
{
VendorId = v.VENDCODE,
VendorName = v.VENDNAME,
CheckDate = c.CHKDATE.Value,
CheckAmount = c.CHKAMT.Value,
CheckNumber = c.CHECKNUM.Value,
Address1 = v.ADDR1,
Address2 = v.ADDR2,
City = v.CITY,
State = v.STATE,
Zip = v.ZIP,
Company = c.COMPNUM.Value,
VoidDate = c.DELVOIDDATE,
PhoneNumber = v.OFFTELE,
Email = v.EMAIL,
Remittances = (from check in todaysChecks
join d in apCheckDetails on check.CheckId equals d.CheckId
join t in tranDetails on d.InvTranNo equals t.TranNo
where check.CheckId == c.CheckId
select new RemittanceModel
{
InvoiceAmount = t.InvoiceAmount,
CheckAmount = d.PaidAmount,
InvoiceDate = t.InvoiceDate,
DiscountAmount = t.DiscountAmount,
TaxWithheldAmount = t.TaxWithheld,
InvoiceNumber = t.InvoiceNo
}).ToList()
}).ToList();
}
}