LINQ beginner, table joining query for document version - linq

I have a Document table and a Version table. Both have identical information. Whenever a document is created it is inserted into the document table. Whenever that document is then edited a line is added to the Versions table.
It has a field dateApproved which will be null until it is approved. I am currently working on a MVC 4 Approval page and I have never touched linq before and it needs to be done in linq.
How can I join these two tables Document/Version and only show items where dateApproved is null?
from v in Versions
select v
from v in Document
select v
EDIT #1
A user adds a brand new document, this document is then added to the document table. At this point the document needs approval. The dateApproved field in the Documents table is null. Lets say this document gets approved, then the user makes a change and since this document already exists a line is added to the Versions table. Now this document exists in both tables the original document having a revision 0 with a dateApproved, and the Versions table have a revision 1 with a dateApproved as null.
What I need is the documents where the dateApproved is null weather this null is in the Documents table or the Versions table. After it is approved we leave the line in the Version table, and update the line in the Documents table with the approved version.
The key primary key is DocumentID.
EDIT #2
Thanks to Peter Kiss it is now displaying all the files that need to be approved. There is one more hiccup I am running into. It is returning 3 results which is correct. 2 files are brand new and 1 has been edited.
The 2 brand new files it displays the information correctly as from the documents table. The edited file it is displaying the information from the documents table but I need the info from the revisions table. Is it possible to make it return the version table information if the item exists in the version table and return the document table information if it does not exist in the version table (ie brand new).
Hope I explained that properly.

var documents =
ctx.Documents
.Where(x => x.dateApproved == null
|| x.Versions.Any
(y => y.dateApproved == null));
The documents variable will contain only those documents which are having unapproved versions. Then when you iterating through this collection the current document can reach it's Versions (all) through a navigation property called Versions. In that collection you can filter the unapproved versions.
All this only happens when the mapping is set up correctly (aka foreign keys) in your context.
After Edit #2
class DocumentToApprove
{
public string Name { get; set; }
/* other stuff */
public int version { get; set; }
}
var documents = ctx.Documents
.Where(x => x.dateApproved == null
|| x.Versions.Any(y => y.dateApproved == null));
var toApprove = from d in documents
let v = d.Versions.FirstOrDefault(y => y.Approved == null)
select new DocumentToApprove
{ Name = d.Name
,
/* other stuff */
Version = v == null ? 1 : v.VersionNumber
};

Select only records which are having dateApproved is null. and collect it in some kind of list listofVersions.
var listofVersions=(from v in Versions
where v.dateApproved==null
select v).ToList();
You can do the same for Document. Just substitute Version with Document.
Note: I just collect it with var to make it simple. You can create a list of Versions/Document if you wish.

Related

Laravel, Get row with highest version column from database

At the moment i have a 1 to many relationship between 2 models. A Machine model and a Document Model.
A document can have multiple versions and i always related to my machine. Now i only want to show the documents with the latest or highest version.
I've tried to get the version from the document but then i can only return 1 document to my view instead of returning multiple documents. I've also tried to get the documents the following way but i get stuck with the steps i need to take after creating the loop:
$machine = Machine::find($id);
$documents = $machine->documents;
foreach($documents as $document)
{
}
dd($documents);
return view('machine.detail', compact('machine'));
So to specify my question: How can i return only the unique documents with the latest version to my view. So if file 1 has versions 1,2,3,10 and file 2 has versions 1,2,3 i want to return version 10 of file 1 and version 3 of file 2.
If I understand your question correctly you can do the following.
// Add this relation also on your model where your hasMany is on properly Machine.php
public function latestDocument()
{
return $this->hasOne(Document::class)
->latest('version_column');
}
// Controller
$machine = Machine::with(['latestDocument'])->findOrFail($id);
$document = $machine->latestDocument;
return view('machine.detail', compact('machine'));
If you have a unique key for each document that is shared for all the versions something like this should work.
$documents = $machine->documents->groupBy('name of your unique key')->map(function($documents) {
$latestVersion = $documents->max('version');
return $documents->firstWhere('version', $latestVersion);
});
Explanation: The get the latest version from each document we first need to group all documents by a key to separate all the unqiue documents into multiple collections that contain the versions.
Then we need to map them into a new collection where we only get the document with the latest version.
Note: Dont forget to change $machine = Machine::find($id); to $machine = Machine::with('documents')->find($id); so you load the documents before hand. This will prevent the n+1 query problem

.Where in LinQ not working correctly

I have Documents table and Signs table. Document record can be related with many records in Signs table.
Now, I want to get all records of Documents table when document ID appears in Signs table.
Here I get all documents:
var documents = (from c in context.documents select c);
Here I get all my signs and save into List:
var myDocuments = (from s in context.signs where s.UserId== id select s.ID).ToList();
This list contains collection on document ID.
And here, I'm trying to get all documents that exists in myDocuments list:
documents.Where(item => myDocuments.Contains(item.ID));
But, when I do .ToList() allways return all records (in database only exists one compatible record)
What is wrong in LinQ statement?
The problem is that this statement doesn't modify the contents of documents, it merely returns the results (which you're not doing anything with):
documents.Where(item => myDocuments.Contains(item.ID));
documents is still the full list.
Change this line to something like:
var matchingIDDocs = documents.Where(item => myDocuments.Contains(item.ID));
And then use matchingIDDocs in place of "documents" later in your code.

Drupal 7 | Query through multiple node references

Let me start with structure first:
[main_node]->field_reference_to_sub_node->[sub_node]->field_ref_to_sub_sub_node->[sub_sub_node]
[sub_sub_node]->field_type = ['wrong_type', 'right_type']
How to efficiently query all [sub_sub_node] ids with right_type, referenced by main_node (which is current opened node)?
Doing node_load on foreach seems a bit of overkill for this. Anybody has some better solutions? Greatly appreciated!
If you want to directly query the table of the fields:
$query = db_select('node', 'n')->fields('n_sub_subnode', array('nid'));
$query->innerJoin('table_for_field_reference_to_sub_node', 'subnode', "n.nid = subnode.entity_id AND subnode.entity_type='node'");
$query->innerJoin('node', 'n_subnode', 'subnode.subnode_target_id = n_subnode.nid');
$query->innerJoin('table_for_field_ref_to_sub_sub_node', 'sub_subnode', "n_subnode.nid = sub_subnode.entity_id AND sub_subnode.entity_type='node'");
$query->innerJoin('node', 'n_sub_subnode', 'sub_subnode.sub_subnode_target_id = n_sub_subnode.nid');
$query->innerJoin('table_for_field_type', 'field_type', "n_sub_subnode.nid = field_type.entity_id AND field_type.entity_type='node'");
$query->condition('n.nid', 'your_main_node_nid');
$query->condition('field_type.field_type_value', 'right_type');
Here is the explanation of each line:
$query = db_select('node', 'n')->fields('n_sub_subnode', array('nid'));
We start by querying the base node table, with the alias 'n'. This is the table used for the 'main_node'. The node ids which will be returned will be however from another alias (n_sub_subnode), you will see it below.
$query->innerJoin('table_for_field_reference_to_sub_node', 'subnode', "n.nid = subnode.entity_id AND subnode.entity_type='node'");
The first join is with the table of the field_reference_to_sub_node field, so you have to replace this with the actual name of the table. This is how we will get the references to the subnodes.
$query->innerJoin('node', 'n_subnode', 'subnode.subnode_target_id = n_subnode.nid');
A join back to the node table for the subnodes. You have to replace the 'subnode_target_id' with the actual field for the target id from the field_reference_to_sub_node table. The main purpose of this join is to make sure there are valid nodes in the subnode field.
$query->innerJoin('table_for_field_ref_to_sub_sub_node', 'sub_subnode', "n_subnode.nid = sub_subnode.entity_id AND sub_subnode.entity_type='node'");
The join to the table that contains references to the sub_sub_node, so you have to replace the 'table_for_field_ref_to_sub_sub_node' with the actual name of the table. This is how we get the references to the sub_sub_nodes.
$query->innerJoin('node', 'n_sub_subnode', 'sub_subnode.sub_subnode_target_id = n_sub_subnode.nid');
The join back to the node table for the sub_sub_nodes, to make sure we have valid references. You have to replace the 'sub_subnode_target_id' with the actual field for the target id from the 'field_ref_to_sub_sub_node' table.
$query->innerJoin('table_for_field_type', 'field_type', "n_sub_subnode.nid = field_type.entity_id AND field_type.entity_type='node'");
And we can now finally join the table with the field_type information. You have to replace the 'table_for_field_type' with the actual name of the table.
$query->condition('n.nid', 'your_main_node_nid');
You can put now a condition for the main node id if you want.
$query->condition('field_type.field_type_value', 'right_type');
And the condition for the field type. You have to replace the 'field_type_value' with the actual name of the table field for the value.
Of course, if you are really sure that you always have valid references, you can skip the joins to the node table and directly join the field tables using the target id and the entity_id fields (basically the target_id from on field table has to be the entity_id for the next one).
I really hope I do not have typos, so please check the queries carefully.

Using linq how do i remove multiple records from a cross reference table

My Database contains 4 tables:
TABLE TBLCARTITEM (CART_ID, ITEM_ID, PROMOTION_ID, many more cart item fields)
TABLE XREFCARTITEMPROMOTION (CART_ID, ITEM_ID, PROMOTION_ID)
TABLE TBLPROMOTION (PROMOTION_ID, PROMOTION_TYPE_ID, many more promotion fields)
TABLE TBLITEM (ITEM_ID, many more item fields)
The XREFCARTIEMPROMOTION table is a cross reference table that creates a many-to-many relationship between TBLCARTITEM and TBLPROMOTION. TBLITEM is related to both TBLCARTITEM and XREFCARTITEMPROMOTION.
I am trying to use linq to remove multiple records from the XREFCARTIEMPROMOTION table specified above. Right now i can only remove a single record.
My script looks like so:
using (WSE webStoreContext = new WSE()){
XREFCARTITEM dbItem = WebStoreDelegates.selectCartItems.Invoke(webStoreContext).ByItemID(itemId).ByCartID(cartId).ToList().SingleOrDefault();
if (dbItem.TBLITEM.TBLPROMOTION != null)
dbItem.TBLPROMOTION.Remove(WebStoreDelegates.selectPromotions.Invoke(webStoreContext).ByID(dbItem.TBLITEM.TBLPROMOTION.PROMOTION_ID).ToList().SingleOrDefault());
}
the selectCartItems Delegate:
public static Func<WSE, IQueryable<XREFCARTITEM>> selectCartItems =
CompiledQuery.Compile<WSE, IQueryable<XREFCARTITEM>>(
(cart) => from c in cart.XREFCARTITEM.Include("TBLITEM").Include("TBLPROMOTION")
select c);
the selectPromotions Delegate:
public static Func<WSE, IQueryable<TBLPROMOTION>> selectPromotions =
CompiledQuery.Compile<WSE, IQueryable<TBLPROMOTION>>(
(cart) => from c in cart.TBLPROMOTION
select c);
Filters byItemID and byCartID will bring back all instances of this item in this cart.
Filter byID just brings back a single promotion.
My removal process is only removing a single record out of the XREFCARTITEMPROMOTION table. I would like to remove all the filtered records from my dbitem's XREFCARTITEMPROMOTION table at this point.
I have tried setting the entity key to null, but this doesn't seem to make a difference. dbItem.TBLITEM.TBLPROMOTIONReference.EntityKey = null;
My question is how do i remove multiple records from a cross reference table given the code above?
Thanks in advance.
Answered my own question: replace -
if (dbItem.TBLITEM.TBLPROMOTION != null) {
dbItem.TBLPROMOTION.Remove(WebStoreDelegates.selectPromotions
.Invoke(webStoreContext).ByID(dbItem.TBLITEM.TBLPROMOTION.PROMOTION_ID)
.ToList().SingleOrDefault());
}
with -
if (item.TBLPROMOTION != null) {
item.TBLPROMOTION.Clear();
webStoreContext.SaveChanges();
}
This will remove all many-to-many promotion entries tied to this item

LINQ not updating on .SubmitChanges()

Is there any reason something like this would not work?
This is the logic I have used many times to update a record in a table with LINQ:
DataClasses1DataContext db = new DataClasses1DataContext();
User updateUser = db.Users.Single(e => e.user == user);
updateUser.InUse = !updateUser.InUse;
db.Log = new System.IO.StreamWriter(#"c:\temp\linq.log") { AutoFlush = true };
db.SubmitChanges();
(updateUser.InUse is a bit field)
For some reason it isn't working. When I check the linq.log it is completely blank.
Could there be a problem with my .dbml? Other tables seem to work fine but I've compared properties in the .dbml and they all match.
It's as if the db.SubmitChanges(); does not detect any updates being required.
The table could not be updated properly because it had no primary key. (Actually it had the column but the constraint was not copied when I did a SELECT INTO my dev table). The DataContext class requires a primary key for updates.
Is the InUse property a "normal" one as far as LINQ is concerned? (e.g. it's not autogenerated or anything funky like that?)
Alternatively, I don't suppose it's a Nullable<bool> is it, with a current value of null? If so, your update line isn't actually doing anything - for nullable Booleans, !null = null.

Resources