Copy Entity Framework Object - c#

I have a EF4.1 class X and I want to make copy of that plus all its child records.
X.Y and X.Y.Z
Now if I do the following it returns error.
The property 'X.ID' is part of the object's key information and cannot be modified.
public void CopyX(long ID)
{
var c = db.Xs.Include("Y").Include("W").Include("Y.Z").SingleOrDefault(x => x.ID == ID);
if (c != null)
{
c.ID = 0;
c.Title = "Copy Of " + c.Title;
for (var m = 0; m < c.Ys.Count; m++)
{
c.Ys[m].ID = 0;
c.Ys[m].XID=0-m;
for (var p = 0; p < c.Ys[m].Zs.Count; p++)
{
c.Ys[m].Zs[p].XID = 0 - m;
c.Ys[m].Zs[p].ID = 0 - p;
}
}
for (var i = 0; i < c.Ws.Count; i++)
{
c.Ws[i].ID = 0 - i;
c.Ws[i].XID = 0;
}
db.Entry<Content>(c).State = System.Data.EntityState.Added;
db.SaveChanges();
}
}
Or Is there other way of making copy of entity objects.
NOTE: there are multiple properties in each W,X,Y,Z.

In entity-framework-5, this is insanely easy with the DbExtensions.AsNotracking().
Returns a new query where the entities returned will not be cached in the DbContext or ObjectContext.
This appears to be the case for all objects in the object graph.
You just have to really understand your graph and what you do and don't want inserted/duplicated into the DB.
Lets assume we have objects like:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public AddressLine { get; set; }
public int StateID { get; set; }
public ICollection<State> { get; set; }
}
So in order to Duplicate a person, I need to duplicate the addresses, but I don't want to duplicate the States.
var person = this._context.Persons
.Include(i => i.Addresses)
.AsNoTracking()
.First();
// if this is a Guid, just do Guid.NewGuid();
// setting IDs to zero(0) assume the database is using an Identity Column
person.ID = 0;
foreach (var address in person.Addresses)
{
address.ID = 0;
}
this._context.Persons.Add(person);
this._context.SaveChanges();
If you then wanted to then reuse those same objects again to insert a third duplicate, you'd either run the query again (with AsNoTracking()) or detach the objects (example):
dbContext.Entry(person).State = EntityState.Detached;
person.ID = 0;
foreach (var address in person.Addresses)
{
dbContext.Entry(address).State = EntityState.Detached;
address.ID = 0;
}
this._context.Persons.Add(person);
this._context.SaveChanges();

You need to make correct deep copy of the whole entity graph - the best way is to serialize the original entity graph to memory stream and deserialize it to a new instance. Your entity must be serializable. It is often used with DataContractSerializer but you can use binary serialization as well.

C is not a copy it is the record, the error you are getting is because you are trying to update it's primary key, even if you weren't it still wouldn't work. You need to make a new X entity and then copy the values from the properties of the retrieved entity and then insert the new entity.

Not sure if it works in 4.1, from http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4:
public static T CopyEntity<T>(MyContext ctx, T entity, bool copyKeys = false) where T : EntityObject
{
T clone = ctx.CreateObject<T>();
PropertyInfo[] pis = entity.GetType().GetProperties();
foreach (PropertyInfo pi in pis)
{
EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);
foreach (EdmScalarPropertyAttribute attr in attrs)
{
if (!copyKeys && attr.EntityKeyProperty)
continue;
pi.SetValue(clone, pi.GetValue(entity, null), null);
}
}
return clone;
}
You can copy related entites to your cloned object now too; say you had an entity: Customer, which had the Navigation Property: Orders. You could then copy the Customer and their Orders using the above method by:
Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false);
foreach(Order order in myCustomer.Orders)
{
Order newOrder = CopyEntity(myObjectContext, order, true);
newCustomer.Orders.Add(newOrder);
}

I use Newtonsoft.Json, and this awesome function.
private static T CloneJson<T>(T source)
{
return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}

Related

Entity Framework Recursive Relationship Hierarchical Data

Does Entity Framework support some kind of recursive LINQ or do I have to write my queries in SQL? (Using ParentId - Typical category subcategory problem)
I think you can solve it by using Include method inside extension method to get only piece of hierarchy or get it all. Even it can generate pretty ugly SQL.
using(var context = new HierarchyContext())
{
var depth = context
.Categories
.IncludeHierarchy(3, nameof(Category.Children));
var root = depth.Single(c => c.Id == 2);
}
public static IQueryable<T> IncludeHierarchy<T>(this IQueryable<T> source,
uint depth, string propertyName)
where T : Category
{
var temp = source;
for (var i = 1; i <= depth; i++)
{
var sb = new StringBuilder();
for (var j = 0; j < i; j++)
{
if (j > 0)
{
sb.Append(".");
}
sb.Append(propertyName);
}
var path = sb.ToString();
temp = temp.Include(path);
}
var result = temp;
return result;
}
public class Category
{
// Primary key
public int Id { get; set; }
// Category name
public string Name { get; set; }
// Foreign key relationship to parent category
public int ParentId { get; set; }
// Navigation property to parent category
public virtual Category Parent { get; set; }
// Navigation property to child categories
public virtual ICollection<Category> Children { get; set; }
}

Entity framework, Navigation Properties, call and store values in the database table

I am trying to parse a text file into 3 tables using C Sharp and T-SQL. Below are the table definitions -
1) TTransaction (TransactionID Identity(1,1), some other attributes)
2) TMatch (MatchID Identity(1,1), some other attributes)
3) TTransactionXTMatch (TransactionID ,MatchID )
Using C sharp, I parsed the data into tables TTransaction and TMatch. I am not sure how to use the navigation properties of Entity Framework to populate these 2 ID's in the TTransactionXTMatch (Bridge) Table. There is one to many relationship between TTransaction and TMatch.
See the code below:
TTransaction txn = new TTransaction();
txn.TRN = txnNo;
txn.Amount = Convert.ToDecimal(Amount);
txn.TransactionText = Convert.ToString(txnText);
txn.TransactionLocation = TxnLOC;
context.TTransactions.Add(txn);
context.SaveChanges();
TMatch Mtc = new TMatch();
Mtc.RiskWord = RiskWord;
Mtc.GoodGuyWord = GoodGuyWord;
Mtc.Origin = Origin;
Mtc.Location = Location;
context.TMatches.Add(Mtc); //Adding to the database
context.SaveChanges();
I am wondering how do call the TTransactionXTMatch table since it doesn't come up in the model and there is no class created for it. It does show in the navigation properties but I am not sure how to use that.
Can someone please shed some light on this. Kindly let me know if you need additional info on what I am doing.
Thanks in advance !
Here is a Code first sample for you. In this sample note that, there is a bunch of Customers created first, then a bunch of products (real world would look like that).
Then to show Many-To-Many, we add random number of products to a customer through Products property (navigation property) in a loop and EF does the rest.
string defaultConString = #"server=.\SQLExpress;Database=CodeFirstDbSample;Trusted_Connection=yes;";
void Main()
{
CreateSampleCodeFirstData();
ListData();
}
private void CreateSampleCodeFirstData()
{
var ctx = new MyContext(defaultConString);
for (int i = 0; i < 10; i++)
{
var c = new Customer { CustomerName="c" + i };
ctx.Customers.Add( c );
}
for (int i = 0; i < 10; i++)
{
var p = new Product { ProductName="p" + i };
ctx.Products.Add( p );
}
ctx.SaveChanges();
for (int i = 0; i < 10; i++)
{
var customer = ctx.Customers.Single (c => c.CustomerName == "c"+i);
var products = ctx.Products.OrderBy (p => Guid.NewGuid()).Take(3);
customer.Products = new List<Product>();
customer.Products.AddRange( products );
}
ctx.SaveChanges();
}
private void ListData()
{
var ctx = new MyContext(defaultConString);
Console.WriteLine ("By Customer");
Console.WriteLine ("".PadRight(50,'-'));
foreach (Customer c in ctx.Customers.Include("Products"))
{
Console.WriteLine ("{0}: {1}", c.CustomerId, c.CustomerName);
Console.WriteLine ("\t\t{0}",
string.Join(",", c.Products.Select (p => p.ProductName)));
}
Console.WriteLine ("".PadRight(50,'='));
Console.WriteLine ();
Console.WriteLine ("By Product");
Console.WriteLine ("".PadRight(50,'-'));
foreach (Product p in ctx.Products.Include("Customers"))
{
Console.WriteLine ("{0}: {1}", p.ProductId, p.ProductName);
Console.WriteLine ("\t\t{0}",
string.Join(",", p.Customers.Select (c => c.CustomerName) ));
}
Console.WriteLine ("".PadRight(50,'='));
Console.WriteLine ();
}
public class MyContext : DbContext
{
public MyContext(string connectionString)
: base(connectionString) {}
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual List<Customer> Customers { get; set; }
}
public class Customer
{
public int CustomerId{ get; set; }
public string CustomerName{ get; set; }
public virtual List<Product> Products { get; set; }
}
IS your project using code first or database first migrations?
It is possible that the table was created without a migration meaning that Entity Framework would not work with this table.

Why isn't this Sum() in my index working?

I have the following test:
public class ListingEventTest
{
public ListingEventTest()
{
Artists = new List<ArtistTest>();
}
public List<ArtistTest> Artists { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public double Popularity { get; set; }
}
public class ArtistTest
{
public string Id { get; set; }
public Stat Stats { get; set; }
}
public class Stat
{
public double Popularity { get; set; }
}
public class ArtistsWithStats_ByName : AbstractIndexCreationTask<ListingEventTest>
{
public ArtistsWithStats_ByName()
{
Map = listingEvents => from listingEvent in listingEvents
let artists = LoadDocument<ArtistTest>(listingEvent.Artists.Select(x => x.Id))
select new
{
Popularity = artists.Average(x => x.Stats.Popularity),
listingEvent.Name
};
}
}
[TestFixture]
public class IndexCanDoSums
{
[Test]
public async void WhenListingEventArtistsHaveStatsIndexReturnsPopularity()
{
var store = new EmbeddableDocumentStore
{
UseEmbeddedHttpServer = true,
Configuration =
{
RunInUnreliableYetFastModeThatIsNotSuitableForProduction = true,
RunInMemory = true,
}
}.Initialize();
IndexCreation.CreateIndexes(typeof(ArtistsWithStats_ByName).Assembly, store);
using (var session = store.OpenAsyncSession())
{
for (int i = 0; i < 10; i++)
{
var le = new ListingEventTest
{
Name = "test-" + i
};
await session.StoreAsync(le);
for (int j = 0; j < 2; j++)
{
var artist = new ArtistTest
{
Stats = new Stat
{
Popularity = 0.89d
}
};
await session.StoreAsync(artist);
le.Artists.Add(artist);
}
await session.SaveChangesAsync();
}
}
Thread.Sleep(2000);
using (var session = store.OpenAsyncSession())
{
var query = session
.Advanced.AsyncLuceneQuery<ListingEventTest>("ArtistsWithStats/ByName");
var result = await query.ToListAsync();
result.First().Popularity.Should().NotBe(0);
}
}
}
When I query this index Popularity is always 0.
Any ideas?
Some funny things going on here.
First, you are storing ArtistTest under the ListingEventTest document, not as separate documents, so in your index there is no need to call LoadDocument, you could just do:
from listingEvent in listingEvents
from artist in listingEvent.Artists
select ...
Second, a Map-only index is a lot like a SQL index where you're just calling out the columns you want to be able to query on. Here, you're doing a calculation on a set of buried properties and you have a top-level property where you want to store that information, but how that ends up working is that your calculated property value goes into the Lucene index (so you could query by Popularity if you want) but the data that is returned is straight from the unaltered document. The map defines what goes into Lucene, which points to the document id, and then the document store returns the documents as the results.
This could be modified somewhat by calling Store(x => x.Popularity) in the index's constructor, which would store the value to be recalled later, but honestly offhand I'm not sure if your calculated value or the document's value (which is zero) would win.
Given that, it becomes pretty confusing to have a document property for the sole purpose of trying to fill it during indexing, which is why it's usually a better option to have a class that represents the mapped state, and then implementing AbstractIndexCreationTask<TDocument, TReduceResult> where the TReduceResult class would only contain the result of your mapping, namely the Name and Popularity columns.
Then when you query from, you can use .ProjectFromIndexFieldsInto<T>() to get your results from the stored index results, not from the document store.

How to update related entity also when updating one with Attach in EF5?

I have a table Players who have N PlayerAliases (1-N) in database.
When I'm editing player information, I want to update some Players table fields AND update PlayerAliases table fields.
I won't add/delete fields in PlayerAliases, just update. How can I do that in EF?
Code below, from ViewModel and Save function examples. Players model should be similar to ViewModel example..
public class PlayerViewModel
{
public int PlayerID {get;set;}
public string Name {get;set;}
public string Address {get;set;}
public IEnumerable<Alias> ListAliases {get;set;}
}
public bool UpdatePlayer(PlayerViewModel vm){
var e = new Players { PlayerID = vm.PlayerID };
db.Players.Attach(e);
e.Name = vm.Name;
e.Address = vm.Address;
if (vm.ListAliases != null)
{
// ??????????????
// How to update ListAliases ??
}
return db.SaveChanges() > 0;
}
I found how.
Attaching directly to related table.
if (vm.ListAliases != null)
{
foreach (var item in vm.ListAliases )
{
var eAlias = new PlayerAliases() { CodPlayerAlias = item.CodPlayerAlias };
db.PlayerAliases.Attach(eAlias);
eAlias.Column1 = item.Column1;
eAlias.Column2 = item.Column2;
}
}

Get all business units

I am trying to retrieve all business units from CRM 2013.
Tried the following query
var query = _serviceContext.BusinessUnitSet
.Where(b => b.EntityState == 0)
.Select(x => new
{
Name = x.Name,
Id = x.Id
}
)
.ToList();
Using this query I am receiving an error just stating:
{System.ServiceModel.FaultCode} {attributeName} {System.Collections.Generic.SynchronizedReadOnlyCollection<System.ServiceModel.FaultReasonText>}
When googling on the subject I found information about how to retrieve single business units (which seems to be different from retrieving a "normal" entity), but not how to get them all (link).
Any help as to how I would retrieve all business units would be much appreciated.
Try this using QueryExpression
public class BusinessUnit
{
public Guid Id { get; set; }
public String Name { get; set; }
}
public void GetAllBusinessUnits(Action<QueryExpression> queryModifier = null)
{
foreach (BusinessUnit m in RetrieveAllBusinessUnit(this.Service, 1000, queryModifier))
{
//Console.WriteLine(m.Name);
}
}
public static IEnumerable<BusinessUnit> RetrieveAllBusinessUnit(IOrganizationService service, int count = 1000, Action<QueryExpression> queryModifier = null)
{
QueryExpression query = new QueryExpression("businessunit")
{
ColumnSet = new ColumnSet("businessunitid", "name"),
PageInfo = new PagingInfo()
{
Count = count,
PageNumber = 1,
PagingCookie = null,
}
};
if (queryModifier != null)
{
queryModifier(query);
}
while (true)
{
EntityCollection results = service.RetrieveMultiple(query);
foreach (Entity e in results.Entities)
{
yield return new BusinessUnit()
{
Id = e.GetAttributeValue<Guid>("businessunitid"),
Name = e.GetAttributeValue<String>("name")
};
}
if (results.MoreRecords)
{
query.PageInfo.PageNumber++;
query.PageInfo.PagingCookie = results.PagingCookie;
}
else
{
yield break;
}
}
}
I presume you want to get all active business units from system. So you must use IsDisabled property to get them. The property you use EntityState is used for tracking the entity state in context, not to indicate state of entity in CRM. See BusinessUnit Entity for more info about BU entity.

Categories

Resources