How can I add a value to a collection only if the value doesn't already exist? - ravendb

In ravendb, I'm trying to address a race condition where many urls can be added to a single document at the same time. In order to do this safely (and not create duplicate entries in the collection), I need the condition check and array push to happen atomically at the database. For example, the structure might look like this:
public class MyData
{
public string Id { get; set; }
public List<string> Urls { get; set; }
}
Assume all three of these operations happen asynchronously:
AddValue("foo");
AddValue("bar");
AddValue("foo");
I was looking at Patch, but I'm not seeing how to add in the condition. Could someone show me an example of how I might do this?
Update
Here is what I'm currently using
var listing = _session.Query<ListingData>()
.Where(l => l.ListingId == listingId && l.OwnerId == userId)
.ToList().SingleOrDefault();
if (listing == null)
return DataResult.NotFound;
_session.Advanced.Patch(
listing,
l => l.PhotoUrls,
urls => urls.Add(url));
_session.SaveChanges();
This seems to work for now if everything goes perfectly, but if there were cases were, for example, a application-side retry based on a false negative were to occur, then I think I would end up with duplicates because
There's a round trip I have to do in order to get the entity/id
The operation simply adds the value without checking to see if it exists first

Just use a patch script.
using (var session = store.OpenSession())
{
var listing = _session.Query<ListingData>()
.Where(l => l.ListingId == listingId && l.OwnerId == userId)
.ToList().SingleOrDefault();
session.Advanced.Defer(new PatchCommandData(
id: listing.ListingId,
changeVector: null,
patch: new PatchRequest
{
Script = #"
if (this.PhotoUrls.includes(url))
throw ‘Url already exists’;
this.PhotoUrls.push($newUrl);
",
Values =
{
["newUrl"] = "The Url to add"
}
},
patchIfMissing: null));
session.SaveChanges();
}
See:
https://ravendb.net/docs/article-page/4.1/csharp/client-api/operations/patching/single-document#non-typed-session-api
https://github.com/ravendb/book/blob/v4.0/Ch04/Ch04.md#patching-documents-and-concurrent-modifications

Related

Return a list of elements that are NOT in two previous lists

I have 2 lists of IDs and I need to return a list with the products that aren't in any of those lists:
public IEnumerable<Produto> GetProdutosIdNotInFamily(Guid produtoId)
{
var produtosPai = GetListaPaisId(produtoId);
var produtosFilho = GetListaFilhosId(produtoId);
var prod = _dbContext.Produtos
.Where(u => !produtosPai.Any(p => p.ProdutoFilhoId == u.Id)
&& !produtosFilho.Any(p => p.ProdutoFilhoId == u.Id));
return prod;
}
You can do this in two ways -- One using Contains and other using Any like you provided in your snippet in the post.
Using Contains Method
If you want to use Contains() method, you may be pulling out all the product Ids into a collection and apply LINQ on top of it and get the list that is not part of both your reference lists. Sample code is as shown below
// This is the sample model I am dealing with
public class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
}
// Assuming the below call returns list of 'Dummy' objects
var products = _dbContext.Produtos;
// list1 & list2 are populated in your case already through the method calls
var exclusionList1 = list1.Select(x => x.Id).ToList<int>();
var exclusionList2 = list2.Select(x => x.Id).ToList<int>();
var myList = products.Where(x => !exclusionList1.Contains(x.Id) && !exclusionList1.Contains(x.Id)).ToList();
Contains is an instance method and takes an object as a parameter and the time complexity depends on the collection you're using this on.
Using Any
Just like Where, Any is an extension method. It takes a delegate as a parameter which gives you greater flexibility and control with respect to what you would want to do.
Applying Any to your scenario is as shown below:
var products = _dbContext.Produtos;
var exclusionList1 = GetListaPaisId(produtoId);
var exclusionList2 = GetListaFilhosId(produtoId);
var prod = _dbContext.Produtos.Where(x => !exclusionList1.Any(z => x.Id == z.Id) &&
!exclusionList2.Any(z => x.Id == z.Id)).ToList();
You can choose your approach based on the context under which you are performing this operation.

RavenDB: How to properly query/filter a nested value from a MultiMapIndex?

My application has a requirement that is should be able to filter/search for Pairs by the Number of the related Contact.
A Pair always has a reference to a Contact stored, but the number of the contact is not, and will not, be stored in the reference. So I tried to create a custom index for this, because the Pair and Contact are stored in different collections.
A simplified example of the index looks like this.
public class Pairs_Search : AbstractMultiMapIndexCreationTask<Pairs_Search.Result>
{
public class Result
{
public string Id { get; set; }
public string Workspace { get; set; }
public ContactResult Contact { get; set; }
public bool HasContactDetails { get; set; }
}
public class ContactResult
{
public string Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
}
public Pairs_Search()
{
AddMap<Pair>(pairs => pairs
.Select(p => new
{
p.Id,
p.Workspace,
Contact = new
{
p.Contact.Id,
p.Contact.Name,
Number = 0
},
// Mark this items as WITHOUT contact details.
HasContactDetails = false,
}
)
);
AddMap<Contact>(contacts => contacts
.Select(c => new
{
Id = (string) null,
Workspace = (string) null,
Contact = new
{
c.Id,
Name = c.DisplayName,
c.Number
},
// Mark this items as WITH contact details.
HasContactDetails = true,
}
)
);
Reduce = results => results
// First group by the contact ID. This will
// create a group with 2 or more items. One with the contact
// details, and one or more with pair details.
// They are all marked by a boolean flag 'HasContactDetails'.
.GroupBy(x => x.Contact.Id)
// We are going to enrich each item in the current group, that is
// marked as 'HasContactDetails = false', with the contact number.
// We need that so that we can filter on it later.
.Select(group =>
group
.Select(i => new
{
i.Id,
i.Workspace,
Contact = new
{
i.Contact.Id,
i.Contact.Name,
// Does the current item have the contact details?
Number = i.HasContactDetails
// Yes, in this case we use the previously set contact number.
? i.Contact.Number
// No, find the item with the contact details and grab the number.
: group.Single(x => x.HasContactDetails).Contact.Number
},
// Pass on the flag that indicates wheter or not
// this item has the contact details. We are going
// to need it later.
i.HasContactDetails
}
)
// We don't need the items with the contact details
// anymore, so filter them out.
.Where(x => !x.HasContactDetails)
)
// Flatten all the small lists to one big list.
.SelectMany(x => x);
// Mark the following fields of the result as searchable.
Index(x => x.Contact.Number, FieldIndexing.Search);
}
}
I've setup a full example that reproduces the issues I am having. You can find the example here.
Creating the index works fine. Querying the index works fine also as it properly matched the pair and contact and enriched the index result with the number of the contact. But when I try to use a .Where() or .Search() on the nested Number property it fails to properly filter the result dataset from the index.
The index without any filtering works as you can see in below code example (also available in the full example).
private static async Task ThisOneWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.ToListAsync();
LogResults("ThisOneWorks()", results);
}
// Output:
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Hermione Granger' with number '71'
// ThisOneWorks(): Pair 'Albus Dumbledore' with number '72'
}
Filtering on a non-nested value also works (also available in the full example). As you can see it filters out the one with a different workspace.
private static async Task ThisOneWithWorkspaceFilterWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.ToListAsync();
LogResults("ThisOneWithWorkspaceFilterWorks()", results);
}
// Output:
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Hermione Granger' with number '71'
}
When I try to filter/search on the Workspace and Number properties I would expect two results that are related to the contact Harry Potter. But instead I just get an empty dataset back.
private static async Task ThisOneWithWorkspaceAndNumberFilterDoesntWork()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.Where(x => x.Contact.Number == 70)
.ToListAsync();
LogResults("ThisOneWithWorkspaceAndNumberFilterDoesntWork()", results);
}
// Output:
// ThisOneWithWorkspaceAndNumberFilterDoesntWork(): EMPTY RESULTS!
}
Can anyone tell me what I am doing wrong here? Any help would be greatly appreciated!
The way to go about it is to store ContactResult in a different collection,
which is what is called a related document in this case,
and when you create the index then you 'Index the Related Document'
Learn from the demo example in:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
The example is for a basic map index but the principle is the same for Multi-Map.
Remove the public class ContactResult from the index class
and define the index with something like:
select new Result
{
....
Number = LoadDocument<Contact>(Pair.Contact).Number
....
}

SaveChanges() outside of foreach saves only the last object added

Seems that the SaveChanges() only saves the last object added into the table:
static void Main(string[] args)
{
Category c = new Category();
using (GenericDBEntities db = new GenericDBEntities())
{
foreach (Match i in db.Matches)
{
if (!db.Categories.Any())
{
c.CategoryInternalId = i.CategoryId;
c.CategoryName = i.CategoryName;
c.SportId = i.SportId;
db.Categories.Add(c);
}
else
{
foreach (Category a in db.Categories)
{
if (i.CategoryId != a.CategoryInternalId)
{
c.CategoryInternalId = i.CategoryId;
c.CategoryName = i.CategoryName;
c.SportId = i.SportId;
db.Categories.Add(c);
}
else
{
return;
}
}
}
}
db.SaveChanges();
I have tried it in a few different ways which all have the same or less result, the for loop saves them all, no matter if condition is met or not. Why is it only saving the last object of the Match table? What am I missing?
Clarification: The task is to go through Match table, which has duplicate CategoryIDs and to, if it is not already in the Category table, add it, so it won't store duplicates again, the problem is Category table is initially empty, hence the newbie logic, still learnin'!
You need to create the Category object for each inserted category, otherwise you have just one category object which you are editing all the time and hence just this one (the last edit) will be saved to the DB.
If you want to add all new categories from Matches based on CategoryId you can use something like this:
using (GenericDBEntities db = new GenericDBEntities())
{
var newCategories = db.Matches
.Where(m => !db.Categories
.Select(c => c.CategoryInternalId)
.Distinct().Contains(m.CategoryId))
.Select(m => new { m.CategoryId, m.CategoryName, m.SportId })
.GroupBy(m => m.otherid)
.Select(g => g.FirstOrDefault())
.ToList();
foreach (var i in newCategories)
{
var c = new Category()
{
CategoryInternalId = i.CategoryId,
CategoryName = i.CategoryName,
SportId = i.SportId
};
db.Categories.Add(c);
}
db.SaveChanges();
}
Now you'll get all the new categories from the Matches table in one go. This is a first draft, meaning that the performance can be further tuned if needed.

Orchard Cms Fetching UserPart Field Data in LazyField<T>

I've been Following this post To get my head around Lazy field of T, Which I think I understand, But I'm having trouble getting associated Field Data for a Part loaded this way
Aim - To show photo of blog post author on a blog post.
I want to add a content part "Content Author"
The part Editor should appear as a drop down list of orchard users.
(regardless of the content owner cms users should be able to pick the author)
I have added an image upload field to the User Content Type
I want to show the image of the user on the front end in the view for the Content Author Part
For the first part I have created the content type and used the lazy Filed of UserPart to get the username. However when I try and get the associated fields for the UserPart. There dosent seem to be any.
public class ContentAuthorRecord : ContentPartRecord
{
public virtual string AuthorEmail { get; set; }
}
public class ContentAuthorPart : ContentPart<ContentAuthorRecord>
{
internal readonly LazyField<UserPart> Owner = new LazyField<UserPart>();
public string AuthorEmail
{
get { return Record.AuthorEmail; }
set { Record.AuthorEmail = value; }
}
public UserPart Author
{
get { return Owner.Value; }
set { Owner.Value = value; }
}
public string AuthorName
{
get
{
if (Author == null)
return "Riders for health";
else
{
return Author.UserName;
}
}
}
}
public class ContentAuthorHandler :ContentHandler
{
private readonly IContentManager _contentManager;
public ContentAuthorHandler(IRepository<ContentAuthorRecord> repository, IContentManager contentManager)
{
_contentManager = contentManager;
OnActivated<ContentAuthorPart>(SetUpCustomPart);
Filters.Add(StorageFilter.For(repository));
}
private void SetUpCustomPart(ActivatedContentContext content, ContentAuthorPart part)
{
// Setup the getter of the lazy field
part.Owner.Loader(() => _contentManager.Query<UserPart, UserPartRecord>().List().FirstOrDefault(x => x.Email == part.AuthorEmail));
}
}
I would expect to be able to access the field with something like
(ImageUploadField.Fields.ImageUploadField)Author.Fields.FirstOrDefault(x
=> x.Name == "Photo");
form the within the part class
( although this makes every thing a bit brittle, hard coding a field name, but I'm not sure how eles to go about it)
Further Info
I have a HeaderPart with a Image field added via the cms (not in code) in the display handler I fetch the field like this
protected override DriverResult Display(HeaderPart part, string displayType, dynamic shapeHelper)
{
if (part.HeaderType == HeaderType.Full_width_hero_image)
{
var field = (ImageUploadField) part.Fields.FirstOrDefault(f => f.Name == "HeaderImage");
if (field != null)
{
return ContentShape("Parts_Header_ImageHero",
() => shapeHelper.Parts_Header_ImageHero(ImagePath: field.ImagePath, ImageTitle: field.FileName));
}
return null;
}
if (part.HeaderType == HeaderType.Full_width_hero_video)
{
return ContentShape("Parts_Header_VideoHero", () => shapeHelper.Parts_Header_VideoHero(VideoUrl: part.VideoUrl));
}
if (part.HeaderType == HeaderType.Body_width_video)
{
return ContentShape("Parts_Header_VideoBody", () => shapeHelper.Parts_Header_VideoBody(VideoUrl: part.VideoUrl));
}
return null;
}
This works, But I can do the same for a part loaded into a lazy field.
Cast to dynamic first, then the syntax becomes much simpler: ((dynamic)part.ContentItem).NameOfTheType.NameOfTheField.NameOfTheProperty
If you have added the fields to the User content type via the CMS interface, it may have added the fields to a different part to the one you expect. If you are adding fields to the User content type, by default it will have added the fields to a new part called 'User', not 'UserPart'. Try to following to search all parts in the content item:
(ImageUploadField.Fields.ImageUploadField)Author.ContentItem.Parts
.SelectMany(p => p.Fields).FirstOrDefault(f => f.Name == "Photo");
or directly from the 'User' part:
(ImageUploadField.Fields.ImageUploadField)Author.ContentItem.Parts
.First(p => p.PartDefinition.Name == p.ContentItem.ContentType).Fields
.FirstOrDefault(f => f.Name == "Photo");

How to ensure good performance with a MassTransit Saga that adds an item to an NHibernate-persisted IList

I have a saga that has 3 states -
public static State Initial { get; set; }
public static State ReceivingRows { get; set; }
public static State Completed { get; set; }
It transitions from Initial to ReceivingRows when it gets a BofMessage (where Bof = Beginning of file). After the BofMessage, it receives a large number of RowMessages where each describes a row in a flat file. Once all RowMessages are sent, an EofMessage is sent and the state changes to Completed. Observe -
static void DefineSagaBehavior()
{
Initially(When(ReceivedBof)
.Then((saga, message) => saga.BeginFile(message))
.TransitionTo(ReceivingRows));
During(ReceivingRows, When(ReceivedRow)
.Then((saga, message) => saga.AddRow(message)));
During(ReceivingRows, When(ReceivedRowError)
.Then((saga, message) => saga.RowError(message)));
During(ReceivingRows, When(ReceivedEof)
.Then((saga, message) => saga.EndFile(message))
.TransitionTo(Completed));
}
public override void OnAddRow(ParcelRowMessage message)
{
// ensure isCauvReturned is "Y"
var fields = message.Value;
var isCauvReturned = fields[33] == "Y";
if (!isCauvReturned)
return;
// add row with just parcel number
var parcelNumber = fields[1];
var row = parcelNumber;
_rows.Add(row);
}
This works except that it has n-squared performance. Investigating with NHProf reveals that each row add causes the entire list of rows to be:
A) selected from the database
B) deleted from the database
C) reinserted into the database.
This seems like very bad behavior to me. All that is needed to add a row is to… well, add a single row to the database! The add operation is literally the only thing I’m doing with the row list. This does not scale when we have 10,000's of items in the list.
Does anyone know how to give this saga more sane performance behavior?
BTW - here's how the IList is mapped if you need it -
HasMany(x => x.Rows)
.Table("OwnerHistorySagaRow")
.KeyColumn("CorrelationId")
.Element("Row")
.Cascade.AllDeleteOrphan();
Thank you!
I know this is a dead item, but here's the real answer for getting a bag to work properly.
Bag(x => x.Rows, c =>
{
c.Key(k =>
{
k.Column("RowCorrelationId");
k.ForeignKey("FK_State_Row");
k.NotNullable(true);
});
c.Fetch(CollectionFetchMode.Join);
c.Lazy(CollectionLazy.NoLazy);
c.Cascade(Cascade.All);
}, r => r.OneToMany());
The class type for Rows is IList<Row>.
Please have a look at
https://community.jboss.org/wiki/NHibernateUsersFAQ#NHibernate_is_deleting_my_entire_collection_and_recreating_it_instead_of_updating_the_table