How to update LINQ-to-SQL Single XML Record? - c#

So basically I have this field in XML that I want to update. It is parsed out through XML and I am not sure how to update this single record from this single instance.
var dataContext =
new RequestFormsDBDataContext(ConfigManager.Data.ConnectionString);
var userForm = (
from i in dataContext.RequestFormItems
join c in dataContext.RequestFormInstances on i.TypeGuid equals c.TypeGuid
where i.Id == FormID
select new {
i.Id,
XmlData = XElement.Parse(i.XML)
}
).ToList();
// Parsing out XML Data
var userFormParced = (
from i in userForm
select new FormItem {
FormId = i.Id,
DateTimeCompleted = i.XmlData.Element("DateTimeCompleted").Value
}
).FirstOrDefault();
RFDateTimeCompleted = userFormParced.DateTimeCompleted;
// Code that isnt working
userFormParced.DateTimeCompleted = "New Data";
dataContext.SubmitChanges();

This won't work because you aren't changing the instance that you retrieve from the database. You are creating a new object using the values from the original - twice - and then changing it. The LINQ-to-SQL context has no knowledge that you're changing database rows, just some unrelated XML that you've constructed.
Firstly, the value you retrieve from the database is being put into an XElement
XmlData = XElement.Parse(i.XML)
and then you retrieve the 'value' from a node and put it into another new object
select new FormItem
{
FormId = i.Id,
DateTimeCompleted = i.XmlData.Element("DateTimeCompleted").Value
}
It's lost any reference to the LINQ-to-SQL context by this stage. Instead you need to change it in place.
If you're using XML in your database, you should be using XML columns which map to XML properties in your LINQ-to-SQL objects. You're not, so we'll implement a workaround.
Try something like this.
// just get the item
var userForm = (
from i in dataContext.RequestFormItems
join c in dataContext.RequestFormInstances on i.TypeGuid equals c.TypeGuid
where i.Id == FormID
select i).FirstOrDefault();
// parse the XML
var xml = XElement.Parse(userForm.XML);
RFDateTimeCompleted = xml.Element("DateTimeCompleted").Value;
xml.Element("DateTimeCompleted").Value = "New Data";
// and finally, because you're again just changing XML
// unrelated to the context, update the original object
userForm.XML = xml.ToString();
context.SubmitChanges();

Related

Foreach updates every record in a LINQ result

I use a WCF Data Service to get data using pagination.
I have to provide a specific object (ExtraData) for the clients, but in the database, there is no such data. It is a combined data of a few tables and it has 1 row to make it cross-joinable.
As WCF Data Services does not allow dynamic object creation, the returned data must be inside the query.
The service return the updated data in the form what the clients require.
I have the following method:
[WebGet]
public IQueryable<ExtraData> GetExtraData(string groupID)
{
var query= (from data in context.Data
join information in context.Information on information.ID equals data.InformationID into tempInformation
from information in tempInformation.DefaultIfEmpty()
from extraData in context.ExtraData // cross-joining the dummy
where data.GroupID == groupID
select new
{
ExtraData = extraData,
Data = data,
InformationText = information.Text
}).ToList();
//After the execution, I intend to modify the result (as it is a dummy record yet):
query.ForEach(
item =>
{
item.ExtraData.DataID = item.Data.ID;
item.ExtraData.Name = item.Data.NameAux;
item.ExtraData.Group = elem.Data.ExtraGroup;
}
);
return (from item in query
select item.ExtraData).AsQueryable();
}
Unfortunately, it modifies every record each time, so I end up having the same record multiple times.
What should I modify to make the ExtraData records unique?
UPDATE:
Inside the foreach, I get this data:
3ca65876-c88f-4849-bef5-170e62f084ec Name16
b705ebc3-8245-4c16-8045-a79ef15192d2 Name16
b8bb423c-02ff-4e9a-b941-a20a9c69dd12 Name Second 16
4e3d3496-4b36-4dab-b471-a43ffb075345 Other16
f93a2358-818e-4929-a51a-46a7b7080bd4 Test16
a4bca994-73d2-4d0e-a18a-2539067a7498 Test Second 16
c7474a92-430a-46ad-bc3d-7e526dfb2647 New Test 16
6117f1b6-3f6b-4fae-b448-2778d68d0877 New Test Mod 16
8e831455-4305-4ee3-b56d-3b0e23131df8 Test Mod 16
In the result set, I get this:
<entry><id>http://localhost/MyService/Service.svc/ExtraData(guid'8e831455-4305-4ee3-b56d-3b0e23131df8')</id><category term="ExtraData" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="ExtraData" href="ExtraData(guid'8e831455-4305-4ee3-b56d-3b0e23131df8')" /><title /><updated>2015-11-10T10:07:36Z</updated><author><name /></author><content type="application/xml"><m:properties><d:ID m:type="Edm.Guid">8e831455-4305-4ee3-b56d-3b0e23131df8</d:ID><d:Name>Test Mod 16</d:Name><d:Group m:type="Edm.Int32">1</d:Group></m:properties></content></entry>
<entry><id>http://localhost/MyService/Service.svc/ExtraData(guid'8e831455-4305-4ee3-b56d-3b0e23131df8')</id><category term="ExtraData" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="DSPaciens" href="ExtraData(guid'8e831455-4305-4ee3-b56d-3b0e23131df8')" /><title /><updated>2015-11-10T10:07:36Z</updated><author><name /></author><content type="application/xml"><m:properties><d:ID m:type="Edm.Guid">8e831455-4305-4ee3-b56d-3b0e23131df8</d:ID><d:Name>Test Mod 16</d:Name><d:Group m:type="Edm.Int32">1</d:Group></m:properties></content></entry>
Well, we were very close to the solution :)
The key element was what #usr wrote:
There's just one such object per row in the database. You're writing
the the same objects many times.
Unfortunately, the selected anonymous object are read-only, so I had to create a wrapper-class that holds the necessary data:
public class CombinedData
{
public ExtraData ExtraData { get; set; }
public Data Data { get; set; }
public string InformationText {get; set; }
}
Then use it inside the query:
var query= (from data in context.Data
join information in context.Information on information.ID equals data.InformationID into tempInformation
from information in tempInformation.DefaultIfEmpty()
from extraData in context.ExtraData // cross-joining the dummy
where data.GroupID == groupID
select new CombinedData
{
ExtraData = extraData,
Data = data,
InformationText = information.Text
}).ToList();
Then create a new ExtraData object inside the ForEach loop:
query.ForEach(
item =>
{
item.ExtraData=new ExtraData();
item.ExtraData.DataID = item.Data.ID;
item.ExtraData.Name = item.Data.NameAux;
item.ExtraData.Group = elem.Data.ExtraGroup;
}
);
Now, it works. Thanks for pointing me to the right direction :)

Getting Missing Rows inside a table and copy them to another table

var FileProducts = from ProductsRow in ProductRangesDt.AsEnumerable()
join Filee in FileTb.AsEnumerable() on ProductsRow["GEN_CODE"].ToString() equals Filee["GEN_CODE"].ToString()
select new
{
PRODUCT_ID = ProductsRow["PRODUCT_ID"],
PRODUCT_NAME = ProductsRow["PRODUCT_NAME"],
PROVIDER_ID = ProductsRow["PROVIDER_ID"],
PROVIDER_NAME = ProductsRow["PROVIDER_NAME"],
GEN_CODE = ProductsRow["GEN_CODE"],
MIN_QUANTITY = Filee["MIN_QUANTITY"],
MAX_QUANTITY = Filee["MAX_QUANTITY"],
DISCOUNT_VALUE = Filee["DISCOUNT_VALUE"]
};
var s = (from b in FileProducts
select b.PRODUCT_ID).Distinct(); // count=285
var Products = (from ProductsRow in ProductRangesDt.AsEnumerable()
select ProductsRow["PRODUCT_ID"]).Distinct(); // count=7159
var result = Products.Except(s); // it's count should be 7159-285
I want to get all the products ID that are in Products and don't exist in FileProducts how can i do this ? result always return 0 as count
From the MSDN documentation about Except extension method:
This method is implemented by using deferred execution. The immediate
return value is an object that stores all the information that is
required to perform the action. The query represented by this method
is not executed until the object is enumerated either by calling its
GetEnumerator method directly or by using foreach in Visual C# or For
Each in Visual Basic.
So in order to get the real value form your Set differentiation, you need to enumerate your result either by a call to the Count()-Method (result.Count()) on using foreach (foreach (var r in result) { ... }).
I can't test with your data, but with test data at my disposition, the Except-extension did delivered the correct results.

Linq getting child items

When I do a lunq query on an EF model, does it not get child entities we well? I have a Transaction table, which has link to a Payee, and a transaction type entity. Also, each transaction has a list of transaction lines...
But the code bellow - all the child objects seen to be NULL, yet the data in the actual entity (Date) seems OK. But in the line: t.account.account_id; .... 'account' is NULL.
public static List<AccountTransactionDto> GetTransaction()
{
var trans = (from t in Db.account_transaction
select t).ToList();
List<AccountTransactionDto> al = new List<AccountTransactionDto>();
foreach(var t in trans)
{
AccountTransactionDto a = new AccountTransactionDto();
a.AccountId = t.account.account_id;
a.AccountTransactionId = t.account_transaction_id;
a.PayeeId = t.payee.payee_id;
a.TransactionDate = t.transaction_date;
a.TransactionTypeId = t.z_transaction_type.transaction_type_id;
foreach(var tl in t.account_transaction_line)
{
AccountTransactionLineDto l = new AccountTransactionLineDto();
l.AccountTransactionLineId = tl.account_transaction_line_id;
l.Amount = tl.amount;
l.BudgetId = tl.budget.budget_id;
l.CostCenterId = tl.cost_centre.cost_centre_id;
l.SubCategoryId = tl.sub_category.sub_category_id;
a.AccountTransactionLine.Add(l);
}
al.Add(a);
}
return al;
}
You have two options. You can enable the Lazy Loading via:
Db.ContextOptions.LazyLoadingEnabled = true;
Or if you change the query line to this (exact syntax may not be correct for Include):
var trans = (from t in Db.account_transaction
select t).Include("account_transaction.account_transaction_line");
Then it should pull back the child records with the parent record in a single result set. But this has performance penalties if there is a great amount of data.
Lazy loading needs to be enabled on your data context.
Db.ContextOptions.LazyLoadingEnabled = true;
or you need to explicitly tell EF to load the association. i.e.
var trans = (from t in Db.account_transaction.Include('account').Include('payee') select t).ToList();

How to retrieve the name of the attribute dynamically without specifying the name of the attribute ?

I am developing asp.net mobile application. I am using LINQ to XML to query XML file. I am using the following query to retrieve the name & value of the query dynamically as follows
var TotalManifolds = from MF in FieldRoot.Element("FIELD-DEFINITION").Element("MANIFOLDS").Elements("MANIFOLD")
join SLT in FieldRoot.Element("FIELD-DEFINITION").Element("SLOTS").Elements("SLOT")
on (string)MF.Attribute("MID") equals (string)SLT.Attribute("PARENT")
select new
{
SlotName = (string)SLT.Attribute("NAME").Value,
SlotValue = (string)SLT.Attribute("NAME").Value
};
In the following statement of above query I want to retrieve the name of the attribute dynamically without explicitly specifying the name of the attribute
SlotName = (string)SLT.Attribute("NAME").Value
Here I am explicitly specifying the name. I want to code which can dynamically retrieve the name of the attribute. I am new to Linq to xml. Can you please tell how this can be done programatically ? or can you provide me the link through which I can resolve the above issue ?
It seems you are looking for something like:
// ...
select new
{
SlotName = SLT.Attributes().First().Name,
SlotValue = SLT.Attributes().First().Value
};
If I understand you correctly, you could always pass a variable in to the LINQ query:
var string attrName = "NAME"; // specify whatever value you need ...
// wrap the query below in a function, if it will be reused...
var TotalManifolds = from MF in FieldRoot.Element("FIELD-DEFINITION").Element("MANIFOLDS").Elements("MANIFOLD")
join SLT in FieldRoot.Element("FIELD-DEFINITION").Element("SLOTS").Elements("SLOT")
on (string)MF.Attribute("MID") equals (string)SLT.Attribute("PARENT")
select new
{
SlotName = (string)SLT.Attribute(attrName).Value,
SlotValue = (string)SLT.Attribute(attrName).Value
};

Query Db from within a Linq Query - C#

I perform a query on my XML file with Linq and when I parse and obtain data from the XML document, I need to go against a DB to populate 2 properties of my object. I can perform 2 calls as my snipet shows, but I would like to make just one call and obtain the result to populate the 2 properties
XDocument recentOrdersXDoc = GetResults(...);
var q = from c in recentOrdersXDoc.Descendants("prop")
let handle = c.Element("handle")
select new ReturnResult()
{
ClientTemplateID = (string)c.Element("TemplateID"),
Handle = resultref != null ? (string)resultref.Attribute("handle") : null,
ClientID = DataContext.GetClientID((string)c.Element("TemplateID")),
ClientName = DataContext.GetClientName((string)c.Element("TemplateID")),
};
To populate ClientID and ModifiedDate I need to make 2 calls. There is a table called Clients which has these 2 columns, ClientID and ClientName. Also can i access ClientTemplateID property when I need to pass it as a param in GetClientID and GetClientName, as in my code above I have to obbtain the result from the XDocument.
How about something like:
var q = from c in recentOrdersXDoc.Descendants("prop")
let handle = c.Element("handle")
let clientTemplateID = (string)c.Element("TemplateID")
let client = DataContext.Clients
.Where(x=>x.ClientTemplateID == clientTemplateID)
.Select(x=>new {x.ClientID, x.ClientName}).Single()
select new ReturnResult()
{
ClientTemplateID = clientTemplateID,
Handle = resultref != null ?
(string)resultref.Attribute("handle") : null,
ClientID = client.ClientID,
ClientName = client.ClientName
};
This still only reads the two columns you need (I had to make some assumptions on names, though).

Categories