Is it possible to index the properties of a collection in LiteDb - c#

I have a result object in litedb with a collection property, as below:
public class Result
{
public int Id { get; set; }
public ICollection<Entity> ExtractedEntities { get; set; }
}
public class Entity
{
public string Value { get; set; }
public int Id { get; set; }
}
Is it possible to index the properties of the entity class so that I can do something like:
collection.Find(r => r.ExtractedEntities.Any(ee => ee.Value == "test" && ee.Id == 1));
Thanks

Yes.
using(var db = new LiteDatabase(#"C:\Temp\MyData.db"))
{
var col = db.GetCollection<Customer>("customers");
// Index document using document Name property
col.EnsureIndex(x => x.Name);
// Use LINQ to query documents
var results = col.Find(x => x.Name.StartsWith("Jo"));
// Let's create an index in phone numbers (using expression). It's a multikey index
col.EnsureIndex(x => x.Phones, "$.Phones[*]");
// and now we can query phones
var r = col.FindOne(x => x.Phones.Contains("8888-5555"));
}
You find this in the documentation here or here

Related

MongoDB C# Bulk partial update

I have the following code (does not compile currently)
public async Task<IResult<bool>> UpdateStock(List<Product> products)
{
var bulkOps = products.Select(record => new UpdateOneModel<Product>(
Builders<Product>.Filter.Where(x => x.ProductCode == record.ProductCode),
Builders<Product>.Update.Set(x => x.StockPointStocks.ElementAt(-1), record.StockPointStocks)
)
{
IsUpsert = true
})
.Cast<WriteModel<Product>>()
.ToList();
await _databaseContext.ProductCollection().BulkWriteAsync(bulkOps);
return await Result<bool>.SuccessAsync();
}
What i'm trying to do from a given list of products bulk update the StockPointStocks and insert where we don't currently have the product in the DB, however i'm unsure how to finish off the second part of the Update.Set or if this is even the correct way of doing this.
public class Product
{
public Product()
{
Id = ObjectId.GenerateNewId();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public ObjectId Id { get; set; }
public string ProductCode { get; set; }
public List<StockPointStock> StockPointStocks { get; set; }
public List<FutureStock> FutureStock { get; set; }
}
public class StockPointStock
{
public string Stockpoint { get; set; }
public int Stock { get; set; }
public DateTime LastUpdated { get; set; }
}
In order to replace the StockPointStocks array with the value provided to the parameter, you only need to perform a Set on the list property:
public async Task<IResult<bool>> UpdateStock(List<Product> products)
{
var bulkOps = products.Select(record => new UpdateOneModel<Product>(
Builders<Product>.Filter.Where(x => x.ProductCode == record.ProductCode),
Builders<Product>.Update.Set(x => x.StockPointStocks, record.StockPointStocks)
)
{
IsUpsert = true
})
.ToList();
await _databaseContext.ProductCollection().BulkWriteAsync(bulkOps);
return await Result<bool>.SuccessAsync();
}
This replaces the property with the new value and leaves all other properties unchanged.
For an upsert, ProductCode and StockPointStocks are set automatically, but you will also need to set the remaining properties for the newly created Product document. To do so, you can use SetOnInsert, e.g.:
public async Task<IResult<bool>> UpdateStock(List<Product> products)
{
var bulkOps = products.Select(record => new UpdateOneModel<Product>(
Builders<Product>.Filter.Where(x => x.ProductCode == record.ProductCode),
Builders<Product>.Update
.Set(x => x.StockPointStocks, record.StockPointStocks)
.SetOnInsert(x => x.FutureStock, record.FutureStock)
)
{
IsUpsert = true
})
.ToList();
await _databaseContext.ProductCollection().BulkWriteAsync(bulkOps);
return await Result<bool>.SuccessAsync();
}
SetOnInsert changes the properties only if an insert occurs for an upsert operation.
Please note that you can also omit the Cast<WriteModel<Product>>() call.

Error when using Select() instead of Include() in a query

I have the following query:
var catInclude = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Include(x => x.CatItems)
.SingleOrDefault(p => p.Id == request.ProvId
cancellationToken: cancellationToken);
As I don't want to get all properties from CatItems with Include(), I have created the following query:
var catSelect = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p ==> new
{ Provider = p,
Items = p.CatItems.Select(x => new List<CatItems> { new CatItems
{ Id = x.Id, Name = x.Name, Price = x.Price } }
})})
SingleOrDefault(cancellationToken: cancellationToken);
But something is wrong in the 2nd query because here return _mapper.ProjectTo<CatDto>(cat) I get the following error:
Argument 1: cannot convert from '<anonymous type: Db.Entities.Cat Prov, System.Colletions.Generic.IEnumerable<System.Colletions.Generic.List<Models.CatItems> > Items>' to 'System.Linq.IQueryable'
Here is my CatDto:
public class CatDto
{
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
Here are my entities:
public class Prov
{
public int Id { get; set; }
public Cat Cat { get; set; }
}
public class Cat
{
public int Id { get; set; }
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
public class CatItems
{
public int Id { get; set; }
public int CatId { get; set; }
public DateTime CreatedOn { get; set; }
}
Is there a way to recreate the 2nd query and use it?
Main difference that instead of returning List of CatItems, your code returns IEnumerable<List<CatItems>> for property Items.
So, just correct your query to project to List:
var catSelect = await _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p => new CatDto
{
ProvId = p.ProvId,
Items = p.CatItems.Select(x => new CatItems
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.ToList()
})
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
I mean, even the exception is pretty self-explanatory. Nevertheless:
You are performing a .Select(...). It returns an Anonymous type. So, your catSelect is an anonymous type, thus the AutoMapper fails.
The quickest fix is to just cast (Cat)catSelect before mapping.
Or, you can dig deeper into how does AutoMapper play with anonymous types.
I feel like you can make most of the classes inherent Id and why is public cat CAT {get; set;} i thought you were supposed to initialize some kind of value

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

Possible to add a condition for linked table fields in LINQ

Can someone suggest me a solution to add condition for reference table items in linq.
I have a master table called TourPackage, which include
public class TourPackage{
public int TourID { get; set; }
public string TourName { get; set; }
public List<IncludedItems> IncludedItems { get; set; }
}
Every tour package contain some selected items reference like
public class IncludedItems {
public int TourID { get; set; }
public int IncludedID { get; set; }
public Included Included { get; set; }
}
All included item should have a reference to Included table for lookup reference
public class Included {
public int IncludedID { get; set; }
public string IncludedValue { get; set; }
}
now i have set of IncludedID like [1,2,3], Is it possible to filter TourPackage based on IncludedID.
Thanks in advance
You can use following code
I have sample array(i.e example) which contains ID's we check if current Id(i.e ele.Included.IncludedID) is present in the array of id's.
listex.Where(x => x.IncludedItems.Any(ele => example.Contains(ele.Included.IncludedID))).ToList();
sample:-
int[] example = new int[3];
example[0] = 123;
example[1] = 456;
example[2] = 789;
List<TourPackage> listex = new List<TourPackage>();
List<TourPackage> filterList = listex.Where(x => x.IncludedItems.Any(ele => example.Contains(ele.Included.IncludedID))).ToList();
Have you tried using something like:
var myIds = new List<int> {123,456};
var result = context.TourPackages
.Where(x => x.IncludedItems.Any(a => a.Included !=null && myIds.Contains(a.Included.IncludedId)))
.ToList();
You might have to include some relations manually depending if you're lazy loading is setup or not.
More info at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

Categories