How can I clone (or copy) a object to another one, but don't copy PK attribute? - linq-to-sql

I'm trying to copy all object attibutes to another object, fox example:
Person p1 = new Person();
p1.name = "John";
p1.sex = 'M';
Person p2 = new Person();
p2 = Util.Clone(p1);
The problem is that Person entity has an identity PK 'codPerson' and I don't want to copy this PK. Is there a way to clone/copy an object, but don't copy its PK attribute??
Thanks!!!

Perhaps you might consider the following:
Ensure Util.Clone(Person p) doesn't
copy the codPerson attribute
Clear the attribute after the Clone method
is called
Create a new Person object while specifically initializing specific properties.

At the most basic level you can't - given an arbitrary object o (and the question implies you're looking for generic solutions) you have no way to determine which field is a primary key.
So you step up a level - by adding some constraints i.e. that you will inform your tools what the primary key field is (or fields are) and hence enable use of a generic method.
So, you could explicitly specify the PK field (name) to the code that does the clone (I assume that you're using reflection to avoid explicitly copying all the fields). You could identify the PK by using annotation of some sort on the classes being cloned and have the clone code exclude properties with the relevant annotation (the annotation implies that the field won't be cloned). There may be other methods
You mention Linq - are you using a specific bit of Linq ?
Beyond that there's not a lot one can suggest without more details - ah but the question is tagged with Linq to SQL (which I missed) ok...
There's nothing obvious in a Linq to SQL class that will help - nor with the "table" but a quick look at the generated code in .designer.cs shows that a key field has annotations similar to the following (taken from a set of classes I have to hand):
[Column(Storage="_ID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
Therefore when you're do your reflection on the class to enumerate the properties to copy you'll want to look for the column and the "IsPrimaryKey" property within the column - unfortunately the details of how to do that are some distance outside my comfort zone!

You could manually set the properties on the new object to be equal to the old one.
For example:
Person p2 = new Person {
Name = p1.Name,
Gender = p1.Gender,
//...
};

you can use .net Reflection.
//using System.Reflection;
var yourEntity = new Person {Name = "Green", Surname= "White"};
var cloneEntity = new Person();
var allPi = typeof(Person).GetProperties();
foreach (var pi in allPi)
{
if (pi.Name != "codPerson" && pi != null && pi.CanWrite)
pi.SetValue(cloneEntity , pi.GetValue(yourEntity , null));
}

Related

Entity Framework 4.0 Code-First Dynamic Query

I would like to query a table based on a list of KeyValuePair. With a Model-First approach, I could do the following:
var context = new DataContext();
var whereClause = new StringBuilder();
var objectParameters = new List<ObjectParameter>();
foreach(KeyValuePair<string, object> pair in queryParameters)
{
if (whereClause.Length > 0)
whereClause.Append(" AND ");
whereClause.Append(string.Format("it.[{0}] = #{0}", pair.Key));
parameters.Add(new ObjectParameter(pair.Key, pair.Value));
}
var result = context.Nodes.Where(whereClause.ToString(), parameters.ToArray());
Now I'm using a Code-First approach and this Where method is not available anymore. Fortunately, I saw an article somewhere (I can't remember anymore) which suggested that I could convert the DbContext to a IObjectContextAdapter then call CreateQuery like this:
var result = ((IObjectContextAdapter)context)
.ObjectContext.CreateQuery<Node>(whereClause.ToString(), parameters.ToArray());
Unfortunately, this throws an error:
'{ColumnName}' could not be resolved in the current scope or context. Make sure that all referenced variables are in scope, that required schemas are loaded, and that namespaces are referenced correctly.
Where {ColumnName} is the column specified in the whereClause.
Any ideas how I can dynamically query a DbSet given a list of key/value pairs? All help will be greatly appreciated.
I think your very first problem is that in the first example you are using Where on the entity set but in the second example you are using CreateQuery so you must pass full ESQL query and not only where clause! Try something like:
...
.CreateQuery<Node>("SELECT VALUE it FROM ContextName.Nodes AS it WHERE " + yourWhere)
The most problematic is full entity set name in FROM part. I think it is defined as name of the context class and name of the DbSet exposed on the context. Another way to do it is creating ObjectSet:
...
.ObjectContext.CreateObjectSet<Node>().Where(yourWhere)

LINQ to SQL: If I add a object to Linq Entity set doesn't it get added

Let's say I have a Company table and a Division table. Each Division has a Foreign key to a company so each company can have many Division children.
If I grab a Company object with Linq-to-sql I have a access to it's Divisions property, a entity set of Division objects. If I add a new Division object to it and call SubmitChanges() won't it automatically go into the Division table or am I forced to call InsertOnSubmit?
There are multiple ways to insert objects into the database with LINQ to SQL. For instance:
db.Divisions.InsertOnSubmit(new Division()
{
Company = db.Company.Single(c => c.Id == 1),
// other properties
});
db.SubmitChanges();
Or:
var company = db.Company.Single(c => c.Id == 1);
company.Divisions.Add(new Division()
{
// other properties
});
db.SubmitChanges();
As you can see, you can use the InsertOnSubmit of the Table<Division> Divisions property on the data context, but you can also use the Add method of the EntitySet<Division> Divisions property on the Company entity. They both do -about- the same. Nice about the latter approach is that you don't need to 'link' the company to the new division, because LINQ to SQL can figure that out for you.
I hope this answers your question.

How to best assign object relationships in LinqToSql

Lets assume the simple case, a simple relationship between table A and table B, where B has an A_Id field in it.
Now, assume I have an object of type A (currentA), and am creating a brand new B object.
B newB = new B() { A_id = currentA.Id };
Is this the correct way to set that relationship? Or should I do:
B newB = new B() { A = currentA };
In the second case, does B.A_Id get set automatically?
Perhaps I need to explicitly set both?
B newB = new B() { A = currentA, A_Id = currentA.Id };
I am looking for the most elegant way to set this new relationship such that I can correctly save it to the database later, and can exploit it immediately... for example I might want to do:
MessageBox(String.Format("B's parent is now {0}", B.A.Name));
It was not intuitive to me at first, but the answer is to:
currentA.Bs.Add(new B());
It seems that if you Add an object to an EntitySet, it will take care of assigning the appropriate relationship properties on the Added object.

SQLMetal Multiple Foreign Keys Pointing to One Table Issue

Edit - Cleaning up question to better reflect the actual issue:
I'm using SQLMetal to generate database classes from our SQL Server db. Recently, I needed to add a table that had multiple foreign keys pointing to the same table. Using LINQPad to play around with the new tables, I was able to access properties for both foreign keys like so:
record.FK_AId
record.FK_BId
record.FKA
record.FKB
...which is just how I would expect it. The problem is, the classes generated by SQLMetal produce these properties:
record.FK_AId
record.FK_BId
record.FKA
record.FKBTableNameGoesHere
Now I could just cange the generated classes so FKBTableNameGoesHere would be FK_B, but the generated files are changed very often by different team members, so that would be a huge pain. Is there an easy fix for this?
Thanks in advance.
Edit 2
So, I thought the solution would be to just create a partial class that had a property named what I wanted it to be, and let the getter/setter point to the poorly named property. That worked for selecting, but not using it in where clauses and such. ANYONE have a solution??
So, my solution was to just add another partial class and add a property with the get/set pointed to the oddly named FKBTableNameGoesHere property. That way we don't have to keep modifying the generated classes. Not exactly solving the problem, but should make it clearer to developers what the property means. Anyone see any potential issues with that solution?
Edit - So, apparently this only works for selecting data and not filtering based on it. Not as easy of a fix as I had hoped. Anyone have any other suggestions?
Edit 2 - Jeeze, thought this would be somewhat of a common problem but I guess not. Anyway, turns out I was on the right track with this. I found this post:
Multiple foreign keys to the same table
Which gave me the idea that I couldn't just link directly to the getter/setter for another property since there's probably a lot more than that going on behind the scenes. This guys solution wasn't exactly the answer, but it sent me in the rigth direction. Adding the association attributes is what finally did it:
public partial class ProblemClass
{
[Association(Name = "FK__SomeLinkHere", Storage = "_OriginalPoorlyNamedStorageVariable", ThisKey = "FK_1_Id", OtherKey = "Id", IsForeignKey = true)]
public FKType MyNewBetterName
{
get
{
return this._OriginalPoorlyNamedStorageVariable.Entity;
}
set
{
this.OriginalPoorlyNamedStorageVariable = value;
}
}
}
Going to leave the bounty open for anyone who can still come up with a cleaner solution.
Ok, I'll propose a new answer (little late, sorry) that will work even if the name of the association changes.
This method will look for the association property of the main entity and then It will look for the value in the master table. Imagine that:
Table: Orders referenced with table Customers by Orders.CustomerID equals Customers.Id. So we pass the Meta information of the main table, the field CustomerID (which is the referenced field) and the field Name (which is the value we want).
/// <summary>
/// Gets the value of "referencedValueFieldName" of an associated table of the "fieldName" in the "mainTable".
/// This is equivalent of doing the next LINQ query:
/// var qryForeignValue =
/// from mt in modelContext.mainTable
/// join at in modelContext.associatedTable
/// on mt.fieldName equals at.associatedField
/// select new { Value = at.referencedValueField }
/// </summary>
/// <param name="mainTable">Metadata of the table of the fieldName's field</param>
/// <param name="fieldName">Name of the field of the foreign key</param>
/// <param name="referencedValueFieldName">Which field of the foreign table do you the value want</param>
/// <returns>The value of the referenced table</returns>
/// <remarks>This method only works with foreign keys of one field.</remarks>
private Object GetForeignValue(MetaTable mainTable, string fieldName, string referencedValueFieldName) {
Object resultValue = null;
foreach (MetaDataMember member in mainTable.RowType.DataMembers) {
if ((member.IsAssociation) && (member.Association.IsForeignKey)) {
if (member.Association.ThisKey[0].Name == fieldName) {
Type associationType = fPointOfSaleData.GetType();
PropertyInfo associationInfo = associationType.GetProperty(member.Name);
if (associationInfo == null)
throw new Exception("Association property not found on member");
Object associationValue = associationType.InvokeMember(associationInfo.Name, BindingFlags.GetProperty, null, fPointOfSaleData, null);
if (associationValue != null) {
Type foreignType = associationValue.GetType();
PropertyInfo foreignInfo = foreignType.GetProperty(referencedValueFieldName);
if (foreignInfo == null)
throw new Exception("Foreign property not found on assiciation member");
resultValue = foreignType.InvokeMember(foreignInfo.Name, BindingFlags.GetProperty, null, associationValue, null);
}
break;
}
}
}
return resultValue;
}
And the call:
AttributeMappingSource mapping = new System.Data.Linq.Mapping.AttributeMappingSource();
MetaModel model = mapping.GetModel(typeof(WhateverClassesDataContext));
MetaTable table = model.GetTable(typeof(Order));
Object value = GetForeignValue(table, "CustomerId" /* In the main table */, "Name" /* In the master table */);
The problem is that only works with foreign keys with only one referenced field. But, changing to multiple keys is quite trivial.
This is a method to obtain a value of a field of the master table, you can changed to return the whole object.
PS: I think I make some mistakes about my English, it's quite difficult for me.
This question came in a long time ago, but I just ran into this problem. I'm using a DBML and I solved this problem by editing the relationships. If you expand the ParentProperty, you can set the property name by changing the Name property.
Here's the XML from the DBML (the Member attribute changed):
<Association Name="State_StateAction1" Member="DestinationState" ThisKey="DestinationStateId" OtherKey="Id" Type="State" IsForeignKey="true" />
Here is a variant of Ocelot20's version:
public partial class ProblemClass
{
partial void OnCreated()
{
this._MyNewBetterName = default(EntityRef<FKType>);
}
protected EntityRef<FKType> _MyNewBetterName;
[Association(Name = "FK__SomeLinkHere", Storage = "_MyNewBetterName", ThisKey = "FK_1_Id", OtherKey = "Id", IsForeignKey = true)]
public EntityRef<FKType> MyNewBetterName
{
get
{
return this._MyNewBetterName.Entity;
}
set
{
this.MyNewBetterName = value;
}
}
}
With this you don't even need to know the original storage name, however I'm not sure if the setter will work (I only used getter, but it worked like a charm). You can even pack this relation definition to a base class and make the partial inherit it (should you need to reuse the relation across multiple models\contexts this should make it easier), but the catch is, you will have to define OnCreated() inside each partial as those cannot be inherited in c# (see this).

N-Tiered LinqToSql Question

I am hoping you can help. I am developing a tiered website using Linq to Sql. I created a new class(or object) in DBML designer called memberState. This object is not an actual table in the database. I have this method in my middle layer:
public override IEnumerable(memberState) GetMembersByState(string #state)
{
using (BulletinWizardDataContext context = DataContext)
{
IEnumerable(memberState) mems = (from m in context.Members
join ma in context.MemberAddresses
on m.UserId equals ma.UserId
join s in context.States
on ma.StateId equals s.StateId
where s.StateName == #state
select new memberState
{
userId = m.UserID,
firstName = m.FirstName,
middleInitial = m.MiddleInitial,
lastName = m.LastName,
createDate = m.CreateDate,
modifyDate = m.ModifyDate
}).ToArray(memberState)();
return mems;
}
}
The tables in my joins (Members, States, and MemberAddresses are actual tables in my Database). I created the object memberStates so I could use it in the query above (notice the Select New memberState. When the data is updated on the web page how do I persist the changes back to the Member Table? My Member Table consists of the following columns: UserId, FirstName, MiddleInitial, LastName, CreateDate, ModifyDate. I am not sure how save the changes back to the database.
Thanks,
If I remember correctly, you can create a view from the different tables (Members, States, and MemberAddresses) and add that to the data context. Then any modifications to data in the view object can be saved, and linq to sql will handle the commit correctly as long as all the relationships are clearly setup/defined in both the database and in the data context.
If you have a Member table, the dbml will most likely contain a Member class. To update a member in the database, you will have to create a new Member object, and the Attach it to the BulletinWizardDataContext.Members collection. Something similar to the following code should the trick (I have not tested the code):
using (BulletinWizardDataContext context = DataContext)
{
Member m = new Member() { UserId = userId };
context.Members.Attach(m);
m.FirstName = firstName;
// Set other properties
context.SubmitChanges();
}
Attach must be called before setting the properties. Also, Linq2Sql has some issues with Attach in the case where the properties of your object are set to default values (i.e. 0 for numeric values, false for booleans, null for string etc.). In this case Attach will not generate the correct SQL.
var m = myContext.Members.Single(m=> m.UserID == myMemState.userID);
m.FirstName = myMemState.firstName;
m.MiddleInitial = myMemState.middleInitial;
...
That would be the quick way. It does an additional roundtrip to the db, but will work well. If that's an issue for you, then do Attach like Jakob suggested. For that you have to have to do some extra steps, like reviewing the configuration for optimistic updates and make sure you have the original fields when doing the attach.