here is example on how to store cross-referenced entities in LiteDB. LiteDB stores the cross-referenced entities perfectly fine, but problem comes when I am trying to find/load entities back. My goal is NOT ONLY the requested entity but also referenced ones. There is quick tutorial section "DbRef for cross references" on LiteDB webpage how one can realize it. LiteDB has "Include" option (which is called before "FindAll") which says which referenced entities must be loaded as well. I am trying to achieve it in this code example but with no results, i.e, the code raises Exception("D_Ref") meaning "D_Ref" reference is not loaded:
namespace _01_simple {
using System;
using LiteDB;
public class A {
public int Id { set; get; }
public string Name { set; get; }
public B B_Ref { set; get; }
}
public class B {
public int Id { set; get; }
public string Name { set; get; }
public C C_Ref { set; get; }
}
public class C {
public int Id { set; get; }
public string Name { set; get; }
public D D_Ref { set; get; }
}
public class D {
public int Id { set; get; }
public string Name { set; get; }
}
class Program {
static void Main(string[] args) {
test_01();
}
static string NameInDb<T>() {
var name = typeof(T).Name + "s";
return name;
}
static void test_01() {
if (System.IO.File.Exists(#"MyData.db"))
System.IO.File.Delete(#"MyData.db");
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var Bs = db.GetCollection<B>(NameInDb<B>());
var Cs = db.GetCollection<C>(NameInDb<C>());
var Ds = db.GetCollection<D>(NameInDb<D>());
LiteDB.BsonMapper.Global.Entity<A>().DbRef(x => x.B_Ref, NameInDb<B>());
LiteDB.BsonMapper.Global.Entity<B>().DbRef(x => x.C_Ref, NameInDb<C>());
LiteDB.BsonMapper.Global.Entity<C>().DbRef(x => x.D_Ref, NameInDb<D>());
var d = new D { Name = "I am D." };
var c = new C { Name = "I am C.", D_Ref = d };
var b = new B { Name = "I am B.", C_Ref = c };
var a = new A { Name = "I am A.", B_Ref = b };
Ds.Insert(d);
Cs.Insert(c);
Bs.Insert(b);
As.Insert(a);
}
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var all_a = As
.Include(x => x.B_Ref)
.FindAll();
foreach (var a in all_a) {
if (a.B_Ref == null)
throw new Exception("B_Ref");
if (a.B_Ref.C_Ref == null)
throw new Exception("C_Ref");
if (a.B_Ref.C_Ref.D_Ref == null)
throw new Exception("D_Ref");
}
}
}
}}
after small research I've resolved the issue simply by adding extra "Include" parameterize by "x => x.B_Ref.C_Ref" lambda where x.B_Ref.C_Ref is a path in hierarchy of references:
var all_a = As
.Include(x => x.B_Ref)
.Include(x => x.B_Ref.C_Ref)
.FindAll();
Here is complete example
namespace _01_simple {
using System;
using LiteDB;
public class A {
public int Id { set; get; }
public string Name { set; get; }
public B B_Ref { set; get; }
}
public class B {
public int Id { set; get; }
public string Name { set; get; }
public C C_Ref { set; get; }
}
public class C {
public int Id { set; get; }
public string Name { set; get; }
public D D_Ref { set; get; }
}
public class D {
public int Id { set; get; }
public string Name { set; get; }
}
class Program {
static void Main(string[] args) {
test_01();
}
static string NameInDb<T>() {
var name = typeof(T).Name + "s";
return name;
}
static void test_01() {
if (System.IO.File.Exists(#"MyData.db"))
System.IO.File.Delete(#"MyData.db");
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var Bs = db.GetCollection<B>(NameInDb<B>());
var Cs = db.GetCollection<C>(NameInDb<C>());
var Ds = db.GetCollection<D>(NameInDb<D>());
LiteDB.BsonMapper.Global.Entity<A>().DbRef(x => x.B_Ref, NameInDb<B>());
LiteDB.BsonMapper.Global.Entity<B>().DbRef(x => x.C_Ref, NameInDb<C>());
LiteDB.BsonMapper.Global.Entity<C>().DbRef(x => x.D_Ref, NameInDb<D>());
var d = new D { Name = "I am D." };
var c = new C { Name = "I am C.", D_Ref = d };
var b = new B { Name = "I am B.", C_Ref = c };
var a = new A { Name = "I am A.", B_Ref = b };
Ds.Insert(d);
Cs.Insert(c);
Bs.Insert(b);
As.Insert(a);
}
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var all_a = As
.Include(x => x.B_Ref)
.Include(x => x.B_Ref.C_Ref)
.Include(x => x.B_Ref.C_Ref.D_Ref)
.FindAll();
foreach (var a in all_a) {
if (a.B_Ref == null)
throw new Exception("B_Ref");
if (a.B_Ref.C_Ref == null)
throw new Exception("C_Ref");
if (a.B_Ref.C_Ref.D_Ref == null)
throw new Exception("D_Ref");
}
}
}
}}
I hope it saves someone's time.
Update: LiteDB author says there is no support for Cascading Include. But it is planned in the next version (see issue). Consider, once, let say, B_Ref is a Lite of B, then there is no mechanism to force deeper Include.
Related
I need to populate a dropdown in my UI and hence added List object to the view model in my c# application. I am fetching the data in my controller code for the dropdown. What's the best way to assign data to the viewmodel object. Is linq an option?
I basically need to assign fundclasses to fundTrackRecord.FundClass
The main Viewmodel:
public class FundPerformanceVM
{
public FundPerformanceVM()
{
TrackRecord = new List<TrackRecordVM>();
}
public int FundId { get; set; }
public string FundName { get; set; }
public List<FundClassVM> FundClass { get; set; }
public string BenchmarkName1 { get; set; }
public string BenchmarkName2 { get; set; }
public List<TrackRecordVM> TrackRecord { get; set; }
public List<Tuple<string, string, string>> FundStatistics { get; set; }
}
public class FundClassVM
{
public int FundClassId { get; set; }
public string FundClass { get; set; }
}
Controller code:
var service = GetViewService<V_LEGAL_FUND_CLASS_SUMMARY>();
foreach (KeyValuePair<int, IEnumerable<FUND_PERFORMANCE>> entry in allPerformance)
{
var fundClasses = service.GetAll().Where(x => x.FUND_ID == entry.Key).Select(x => new { x.LEGAL_FUND_CLASS_ID, x.LEGAL_FUND_CLASS}).ToList();
var fundTrackRecord = new FundPerformanceVM();
fundTrackRecord.FundClass = ??;
If I understood correctly the structure of your model, you can try this:
fundTrackRecord.FundClass = fundClasses.Select(fc => new FundClassVM
{
FundClassId = fc.LEGAL_FUND_CLASS_ID,
FundClass = fc.LEGAL_FUND_CLASS
}).ToList();
You can also do this directly, replacing the code:
var fundClasses = service.GetAll().Where(x => x.FUND_ID == entry.Key).Select(x => new { x.LEGAL_FUND_CLASS_ID, x.LEGAL_FUND_CLASS}).ToList();
var fundTrackRecord = new FundPerformanceVM();
With:
var fundTrackRecord = new FundPerformanceVM();
fundTrackRecord.FundClass = service.GetAll().
Where(x => x.FUND_ID == entry.Key).
Select(fc => new FundClassVM
{
FundClassId = fc.LEGAL_FUND_CLASS_ID,
FundClass = fc.LEGAL_FUND_CLASS
}).ToList();
Please check from Json output i wanted using JavaScriptSerializer. Then check class helper i created using json2csharp.com. Problem is in controller. I am not getting correct output as per my required Jason. I am doing correct in controller? Where the problem can be? Please ask question if you want to know something specific, sorry its hard to describe more clearly.
Helper Class Code:
public class ItemsFromFile
{
public string ASIN { get; set; }
public string Title { get; set; }
public List<Product> products { get; set; }
}
public class ItemsDeatails
{
public List<ItemsFromFile> ItemsFromFile { get; set; }
}
public class File
{
public string nameLocator { get; set; }
public ItemsDeatails itemsDeatails { get; set; }
}
public class RootObject
{
public string Token { get; set; }
public File file { get; set; }
}
Controller code:
if (type == "Salefreaks")
{
var token = ctx.BulkStores.FirstOrDefault(x => x.StoreName == store && x.Type == 1).Token;
var ItemsFromFile = new ItemsFromFile()
{
products = new List<Product>()
};
var ItemsDeatails = new ItemsDeatails()
{
};
var File = new File()
{
nameLocator = "testimport1"
};
var RootObject = new RootObject()
{
Token = token
};
var singleItems = ctx.BulkScannedItems.Where(x => x.UserSellerScanRequestId == id).ToList();
foreach (var item in singleItems)
{
ItemsFromFile.products.Add(new Product { ASIN = item.ASIN, Title = item.EbayTitle });
}
var json = new JavaScriptSerializer().Serialize(RootObject);
}
Required Json Output code:
{
"Token": "7f3099b0-36b1",
"file": {
"nameLocator": "testimport1",
"itemsDeatails": {
"ItemsFromFile": [
{
"ASIN": "B011KVFT9Y",
"Title": "Disney Batman Durable Party Beach Outdoor Adventure Camp Chair w/ Storage Bag"
},
{
"ASIN": "B01D4KRBW2",
"Title": "High Quality Diy Oil Painting Paint Number Kit Theme-Romantic Street A Frameless"
}
]
}
}
}
You can initialize internal objects in the code in the constructor as well.
public class RootObject
{
public string Token { get; set; }
public File file { get; set; }
}
public class File
{
public File()
{
this.itemsDeatails = new ItemsDeatails();
}
public string nameLocator { get; set; }
public ItemsDeatails itemsDeatails { get; set; }
}
public class ItemsDeatails
{
public ItemsDeatails(){
this.ItemsFromFile = new List<ItemsFromFile>();
}
public List<ItemsFromFile> ItemsFromFile { get; set; }
}
public class ItemsFromFile
{
public ItemsFromFile(){
this.products = new List<Product>();
}
public List<Product> products { get; set; }
}
public class Product {
public string ASIN { get; set; }
public string Title { get; set; }
}
Initialize your items properly. And create Root Object from the ground up.
Populate the internal classes first and then later ones.
var itemDetails = new ItemsDeatails();
itemDetails.ItemsFromFile = new ItemsFromFile();
var singleItems = ctx.BulkScannedItems.Where(x => x.UserSellerScanRequestId == id).ToList();
foreach (var item in singleItems)
{
itemDetails.ItemsFromFile.products.Add(new Product { ASIN = item.ASIN, Title = item.EbayTitle });
}
var fl = new File(){
nameLocator = "testimport1",
itemsDeatails = itemDetails
}
var token = ctx.BulkStores.FirstOrDefault(x => x.StoreName == store && x.Type == 1).Token;
var root = new RootObject()
{
Token = token,
file = fl
}
var json = new JavaScriptSerializer().Serialize(root);
Ensure that all your objects are assigned appropriately.
var token = ctx.BulkStores.FirstOrDefault(x => x.StoreName == store && x.Type == 1).Token;
var RootObject = new RootObject() {
Token = token,
file = new File() {
nameLocator = "testimport1",
itemsDeatails = new ItemsDeatails() {
ItemsFromFile = new List<ItemsFromFile>()
}
}
};
var itemsFromFile = new ItemsFromFile();
itemsFromFile.products = new List<Product>();
var singleItems = ctx.BulkScannedItems.Where(x => x.UserSellerScanRequestId == id).ToList();
foreach (var item in singleItems) {
itemsFromFile.products.Add(new Product { ASIN = item.ASIN, Title = item.EbayTitle });
}
RootObject.file.itemsDeatails.ItemsFromFile.Add(itemsFromFile);
var json = new JavaScriptSerializer().Serialize(RootObject);
That being said, it appears that you do not need the list of products inside of the ItemsFromFile class. This definition likely makes more sense:
public class ItemsFromFile {
public string ASIN { get; set; }
public string Title { get; set; }
}
Then your code would be something like this:
var token = ctx.BulkStores.FirstOrDefault(x => x.StoreName == store && x.Type == 1).Token;
var RootObject = new RootObject() {
Token = token,
file = new File() {
nameLocator = "testimport1",
itemsDeatails = new ItemsDeatails() {
ItemsFromFile = new List<ItemsFromFile>()
}
}
};
var singleItems = ctx.BulkScannedItems.Where(x => x.UserSellerScanRequestId == id).ToList();
foreach (var item in singleItems) {
RootObject.file.itemsDeatails.ItemsFromFile.Add(new ItemsFromFile { ASIN = item.ASIN, Title = item.EbayTitle });
}
I donĀ“t know where is the mistake why it says that does not contain a defintion of ImporteSolicitado, interesesDemora and importeReintegro when they are colums of c and the last one of d
var importes = (from c in _context.ReintegroSolicitado
join d in _context.ReintegroRecibido on c.Expediente.ID equals d.Expediente.ID
group new {c,d} by new { c.Expediente.Codigo} into cd
select new { ImporteSolictadoFinal = cd.Sum(b => b.ImporteSolicitado + b.InteresesDemora), ImporteReintegroFinal = cd.Sum(e => e.ImporteReintegro) });
your group element contains two property c and d. So you need refer to
this property as
...
select new {
ImporteSolictadoFinal = cd.Sum(b => b.c.ImporteSolicitado + b.c.InteresesDemora),
ImporteReintegroFinal = cd.Sum(e => e.d.ImporteReintegro) }
...
This is very tough to get right with query posted. I did my best, but it is probably not exactly correct.
var importes = (from c in _context.reintegroSolicitado
join d in _context.reintegroRecibido on c.expediente.ID equals d.expediente.ID
select new { reintegroSolicitado = c, reintegroRecibido = c})
.GroupBy(x => new { c = x.reintegroSolicitado , d = x.reintegroRecibido})
.Select(cd => new { ImporteSolictadoFinal = cd.Sum(b => b.reintegroSolicitado.ImporteSolicitado + b.reintegroSolicitado.InteresesDemora), ImporteReintegroFinal = cd.Sum(e => e.reintegroRecibido.ImporteReintegro) });
}
}
public class Context
{
public List<ReintegroSolicitado> reintegroSolicitado { get; set; }
public List<ReintegroSolicitado> reintegroRecibido { get; set; }
public Expediente expediente { get; set; }
}
public class ReintegroSolicitado
{
public Expediente expediente { get; set; }
public int ImporteSolicitado { get; set; }
public int InteresesDemora { get; set; }
public int ImporteReintegro { get; set; }
}
public class Expediente
{
public int ID { get; set; }
public int Codigo { get; set; }
}
I have a problem with Automapper. I set up a test windows form application and below is the code. Also look at the comments after each MessageBox:
public class FirstClass
{
public string FirstProp { get; set; }
public IList<FirstClassChild> Children { get; set; }
}
public class FirstClassChild
{
public string FirstChildProp { get; set; }
}
public class SecondClass
{
public string FirstProp { get; set; }
public string SecondProp { get; set; }
public IList<SecondClassChild> Children { get; set; }
}
public class SecondClassChild
{
public string FirstChildProp { get; set; }
public string SecondChildProp { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
AutoMapper.Mapper.CreateMap<FirstClass, SecondClass>();
AutoMapper.Mapper.CreateMap<FirstClassChild, SecondClassChild>();
var f = new FirstClass { FirstProp = "FirstClass" };
f.Children = new List<FirstClassChild> { new FirstClassChild { FirstChildProp = "FirstClass" } };
var s = new SecondClass { FirstProp = "SecondClass", SecondProp = "SecondClass" };
s.Children = new List<SecondClassChild> { new SecondClassChild { FirstChildProp = "SecondClass", SecondChildProp = "SecondClass" } };
AutoMapper.Mapper.Map(f, s);
var fc = new FirstClassChild { FirstChildProp = "FirstClass" };
var sc = new SecondClassChild { FirstChildProp = "SecondClass", SecondChildProp = "SecondClass" };
AutoMapper.Mapper.Map(fc, sc);
MessageBox.Show(sc.FirstChildProp);//FirstClass as expected
MessageBox.Show(sc.SecondChildProp);//SecondClass as expected
MessageBox.Show(s.FirstProp);//FirstClass as expected
MessageBox.Show(s.SecondProp);//SecondClass as expected
MessageBox.Show(s.Children.First().FirstChildProp);//FirstClass as expected
MessageBox.Show(s.Children.First().SecondChildProp);//Empty not expected!!
}
}
What can I do to avoid this? Is this behavior expected?
Anyway can anyone guide me how make SecondClass childs SecondChildProp to remain "SecondClass" as it is before the mapping occurs.
I asked a similar question here and found another similar one here.
I think #PatrickSteele makes a very good point: how is AutoMapper supposed to map a source list to a dest list of existing objects, when the dest list may not necessarily bear any resemblance to the source list? i.e. "But what if one list has 3 and the other list has 5?"
If you are sure that FirstClass and SecondClass have the same number of Children, and if the FirstClass's Nth Child always corresponds to SecondClass's Nth child, you could try something like this:
Mapper.CreateMap<FirstClass, SecondClass>()
.ForMember(m => m.Children, o => o.Ignore())
.AfterMap((src, dest) =>
{
for (var i = 0; i < dest.Children.Count; i++)
Mapper.Map(src.Children[i], dest.Children[i]);
});
or if FirstChildProp is some kind of unique key:
Mapper.CreateMap<FirstClass, SecondClass>()
.ForMember(m => m.Children, o => o.Ignore())
.AfterMap((src, dest) =>
{
foreach (var dChild in dest.Children)
{
var sChild = src.Children.Single(c => c.FirstChildProp == dChild.FirstChildProp);
Mapper.Map(sChild, dChild);
}
});
I hope it's more clear what I want to do from the code than the title. Basically I am grouping by 2 fields and want to reduce the results into a collection all the ProductKey's constructed in the Map phase.
public class BlockResult
{
public Client.Names ClientName;
public string Block;
public IEnumerable<ProductKey> ProductKeys;
}
public Block()
{
Map = products =>
from product in products
where product.Details.Block != null
select new
{
product.ClientName,
product.Details.Block,
ProductKeys = new List<ProductKey>(new ProductKey[]{
new ProductKey{
Id = product.Id,
Url = product.Url
}
})
};
Reduce = results =>
from result in results
group result by new {result.ClientName, result.Block} into g
select new BlockResult
{
ClientName = g.Key.ClientName,
Block = g.Key.Block,
ProductKeys = g.SelectMany(x=> x.ProductKeys)
};
}
I get some weird System.InvalidOperationException and a source code dump where basically it is trying to initialize the list with an int (?).
If I try replacing the ProductKey with just IEnumerable ProductIds (and make appropriate changes in the code). Then the code runs but I don't get any results in the reduce.
You probably don't want to do this. Are you really going to need to query in this manner? If you know the context, then you should probably just do this:
var q = session.Query<Product>()
.Where(x => x.ClientName == "Joe" && x.Details.Block == "A");
But, to answer your original question, the following index will work:
public class Products_GroupedByClientNameAndBlock : AbstractIndexCreationTask<Product, Products_GroupedByClientNameAndBlock.Result>
{
public class Result
{
public string ClientName { get; set; }
public string Block { get; set; }
public IList<ProductKey> ProductKeys { get; set; }
}
public class ProductKey
{
public string Id { get; set; }
public string Url { get; set; }
}
public Products_GroupedByClientNameAndBlock()
{
Map = products =>
from product in products
where product.Details.Block != null
select new {
product.ClientName,
product.Details.Block,
ProductKeys = new[] { new { product.Id, product.Url } }
};
Reduce = results =>
from result in results
group result by new { result.ClientName, result.Block }
into g
select new {
g.Key.ClientName,
g.Key.Block,
ProductKeys = g.SelectMany(x => x.ProductKeys)
};
}
}
When replicating I get the same InvalidOperationException, stating that it doesn't understand the index definition (stack trace omitted for brevity).
Url: "/indexes/Keys/ByNameAndBlock"
System.InvalidOperationException: Could not understand query:
I'm still not entirely sure what you're attempting here, so this may not be quite what you're after, but I managed to get the following working. In short, Map/Reduce deals in anonymous objects, so strongly typing to your custom types makes no sense to Raven.
public class Keys_ByNameAndBlock : AbstractIndexCreationTask<Product, BlockResult>
{
public Keys_ByNameAndBlock()
{
Map = products =>
from product in products
where product.Block != null
select new
{
product.Name,
product.Block,
ProductIds = product.ProductKeys.Select(x => x.Id)
};
Reduce = results =>
from result in results
group result by new {result.Name, result.Block}
into g
select new
{
g.Key.Name,
g.Key.Block,
ProductIds = g.SelectMany(x => x.ProductIds)
};
}
}
public class Product
{
public Product()
{
ProductKeys = new List<ProductKey>();
}
public int ProductId { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public string Block { get; set; }
public IEnumerable<ProductKey> ProductKeys { get; set; }
}
public class ProductKey
{
public int Id { get; set; }
public string Url { get; set; }
}
public class BlockResult
{
public string Name { get; set; }
public string Block { get; set; }
public int[] ProductIds { get; set; }
}