How I can read EF DbContext metadata programmatically? - ef-code-first

I have application which uses EF-CodeFirst 5 (dll ver 4.4.0.0, on .net 4.0).
I need to be able to read entity metadata, so that I can, for a given entry type get following information:
which properties are one-many relations (referenced entities)
which properties are many-one relations (collections of entities referencing current one)
also nice but not absolutely necessary: which properties are many-many relations (collections of relations)
I can get this info by writing foreach loops on lists of properties and then "recognizing" them by relying on all of the references being virtual, but I feel that is not "proper" way. I know that EdmxWriter can provide that information in xml format, but it does so by accessing InternalContext which is not publicly accessible and I want to get strongly typed lists/arrays directly, without using that xml. Which API should I use (if there is one for this, it seems that I cannot find it)?

Gorane, this should get you started...
(I haven't played much with it - it takes a bit of experimenting in the debugger to see which properties / info and how to get it)
using (var db = new MyDbContext())
{
var objectContext = ((IObjectContextAdapter)db).ObjectContext;
var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
foreach (var set in container.BaseEntitySets)
{
// set.ElementType.
foreach (var metaproperty in set.MetadataProperties)
{
// metaproperty.
}
}
// ...or...
var keyName = objectContext
.MetadataWorkspace
.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace)
.BaseEntitySets
.First(meta => meta.ElementType.Name == "Question")
.ElementType
.KeyMembers
.Select(k => k.Name)
.FirstOrDefault();
}
and more specifically...
foreach (var set in container.BaseEntitySets)
{
var dependents = ((EntitySet)(set)).ForeignKeyDependents;
var principals = ((EntitySet)(set)).ForeignKeyPrincipals;
var navigationProperties = ((EntityType)(set.ElementType)).NavigationProperties;
foreach (var nav in navigationProperties)
{
// nav.RelationshipType;
}
}
Some of these properties seem to not be exposed to 'general public' so you'd need to use reflection - or find some smarter way - but a good deal of info is in there.
And some more info in these links...
How to get first EntityKey Name for an Entity in EF4
How can I extract the database table and column name for a property on an EF4 entity?
EDIT:
Using your list of navigationProperties as starting point, I got everything I needed like this:
ManyToManyReferences = navigationProperties.Where(np =>
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
OneToManyReferences = navigationProperties.Where(np =>
(np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne) &&
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
ManyToOneReferences = navigationProperties.Where(np =>
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
(np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne))
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
OneToOneReferences = navigationProperties.Where(np =>
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One &&
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One)
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
CreateLambdaExpression method is not my courtesy, credits go to Jon Skeet, code was created with help of this answer
Here is my CreateLambdaExpression method:
public static Expression<Func<TEntity, object>> CreateLambdaExpression<TEntity>(string propertyName)
{
ParameterExpression parameter = Expression.Parameter(typeof (TEntity), typeof (TEntity).Name);
Expression property = Expression.Property(parameter, propertyName);
return Expression.Lambda<Func<TEntity, object>>(property, new[] {parameter});
}

Related

Get WorkflowDesigner from a ModelItem

I am trying to create a custom IExpressionEditor. In order to new one up I need a WorkflowDesigner, All I have is the ModelItem representing my custom activity. Is it possible to access the WorkflowDesigner from a given ModelItem?
List<ModelItem> variables = new List<ModelItem>();
List<ModelItem> nameSpaces = new List<ModelItem>();
// get the activity from the datacontext
CustomActivityDesigner cad = this.DataContext as CustomActivityDesigner;
// try to get the variables
// look for variables collection cant seem to find them
ModelProperty mp = cad.ModelItem.Properties["Variables"];
if(mp != null && mp.PropertyType == typeof(Collection<Variable>))
{
mp.Collection.ToList().ForEach(i => variables.Add(i));
}
// get name spaces
ModelProperty mp2 = cad.ModelItem.Properties["NameSpaces"];
if(mp2 != null && mp2.PropertyType == typeof(Collection<NameSpace>))
{
mp2.Collection.ToList().ForEach(i => nameSpaces.Add(i));
}
// finally need the WorkflowDesigner object
WorkflowDesigner designer = Modelitem.Root....??? as WorkflowDesigner
// now we have what we need we can create the IExpressionEditor
CustomExpressionEditior ce = new CustomExpressionEditior(designer, variables, nameSpaces)
Following the Using a Custom Expression Editor as reference, it seems you should be able to create a Custom Expression Service (which will be creating the Expression Editor instances) and register it to the Services collection on the WorkflowDesigner.
Once it's registered in the WorkflowDesigner's Services collection, you'll be able to:
Get the editing context for the ModelItem by using ModelItemExtensions.GetEditingContext
Access the Services property of the returned EditingContext
Retrieve the Custom Expression Service you registered on the WorkflowDesginer
Hope it helps!

Copy EntityCollection to Dictionary in CRM

I want to convert or copy my EntityCollection to Dictionary.
my code is below.
Please suggest on the same.
Dictionary<string, string> dictionary = new Dictionary<string, string>();
EntityCollection contentCollection = orgService.RetrieveMultiple(new FetchExpression(contentQuery));
if (contentCollection != null && contentCollection.Entities != null && contentCollection.Entities.Count > 0)
{
foreach (Entity contents in contentCollection.Entities)
{
dictionary.Add(content.Attributes(0).key,content.Attributes(0).value );
}
}
It can be as short as this:
EntityCollection contentCollection = orgService.RetrieveMultiple(new FetchExpression(contentQuery));
Dictionary<Guid, Entity> dictionary = contentCollection.Entities.ToDictionary(entity => entity.Id);
You do not need to check contentCollection nor its Entities collection, because method RetrieveMultiple always returns non-null values or fails throwing an exception.
Alternatively you could write it as one statement:
var dictionary = orgService
.RetrieveMultiple(new FetchExpression(contentQuery))
.Entities
.ToDictionary(entity => entity.Id);
I have an open source project could make this slightly more concise:
var dictionary = orgService
.GetEntities<Entity>(new FetchExpression(contentQuery)
.ToDictionary(e => e.Id);
You can download it from GitHub: https://github.com/daryllabar/XrmUnitTest (Reference the DLaB.Xrm library)
or NuGet: Install-Package DLaB.Xrm -Version 1.0.1
The Get Entities Handles the Retrieve and returns a List (for late bound) or List(or whatever type you are retrieving for early bound)

Tridion Core service - working with a Hierarchical Taxonomy

I'm using the Tridion Core Service (Tridion 2011 SP1) to retrieve a list of keywords for given Category ID.
CoreService2010Client client = new CoreService2010Client();
XElement xmlCategoryKeywords = client.GetListXml(category.Id,
new KeywordsFilterData());
This returns what seems to be a flat XML structure representing our taxonomy which is 4 levels deep.
The documentation details an approach for working with this:
var categoryKeywords = xmlCategoryKeywords.Elements().Select(element =>
element.Attribute("ID").Value).Select(id => (KeywordData)client.Read(id, null)
);
foreach (KeywordData keyword in categoryKeywords)
{
Console.WriteLine("\t Keyword ID={0}, Title={1}", keyword.Id, keyword.Title);
}
However this will only list each Keyword. The KeywordData object contains property ParentKeywords so it would be possible to build the hierarchy in memory.
Is it possible to retrieve XML from the Core Service with a hierarchical structure? Or an easier way to work with this data?
One way is to use TaxonomiesOwlFilterData:
string publicationId = "tcm:0-3-1";
var filter = new TaxonomiesOwlFilterData();
filter.RootCategories = new[] {new LinkToCategoryData{ IdRef = "tcm:3-158-512"},};
var list = ClientAdmin.GetListXml(publicationId, filter);
As you see it is called on publication, but you can narrow it down to one or more categories. It will return you scary XML list that you can further process like this:
XNamespace tcmc = publicationId + "/Categories#";
XNamespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
XNamespace tcmt = "http://www.tridion.com/ContentManager/5.2/Taxonomies#";
var taxonomyTree = new Dictionary<string, List<string>>();
var keywordNodes = list.Descendants(tcmc + "cat");
foreach (var keywordNode in keywordNodes)
{
var parents = new List<string>();
var parentNodes = keywordNode.Descendants(tcmt + "parentKeyword");
if (parentNodes.Count() > 0)
{
foreach (var parentNode in parentNodes)
{
parents.Add(parentNode.Attribute(rdf + "resource").Value);
}
}
taxonomyTree.Add(keywordNode.Attribute(rdf + "about").Value, parents);
}
As a result you will get unordered list of your keywords and corresponding parents that you can further process as you like. Item that has no parent is obviously a parent keyword. It might not be the most beatiful solution, but at least you will need only one call to server and not read each keyword.
You could process each branch, level by level. Here's some code I've been playing around with that does that:
CoreService2010Client client = new CoreService2010Client("basicHttp_2010");
KeywordsFilterData keywordsDataFilter = new KeywordsFilterData()
{
BaseColumns = ListBaseColumns.IdAndTitle,
IsRoot = true
};
UsingItemsFilterData usingItemsFilter = new UsingItemsFilterData()
{
BaseColumns = ListBaseColumns.IdAndTitle,
ItemTypes = new[] { ItemType.Keyword },
InRepository = new LinkToRepositoryData() { IdRef = "tcm:0-1-1" }
};
XElement parents = client.GetListXml("tcm:1-272-512", keywordsDataFilter);
foreach (XElement parent in parents.Descendants())
{
// Do something with the parent (top level) KW
XElement children = client.GetListXml(parent.Attribute("ID").Value, usingItemsFilter);
foreach (XElement child in children.Descendants())
{
// Do something with the child KW
}
}
I've found in the past that processing a flat list in to a hierarchy (in my case a list of all SGs in a Publication) created a massive overhead compared to processing a branch at a time. Of course I should caveat that by saying that I tried that with an old (early 5.x) version of Tridion so things may have improved since then.
Tridion 2011 SP1 comes with a new CoreService EndPoint. CoreService 2011. Its recommended to use the latest endpoint. Latest endpoint has new functionalists also bug fixes. SP1 also has a default coreservice client proxy that u can use directly in your code.

How to save related new objects that comes from differents DBContext CTP5

I'm working on a MVC3 application and I created my POCO classes from my database with the DbContext Code Generator. All goes fine until I got stuck in this situation. Note that I use the repository pattern and I use for every entity a dedicated repository whether get a new instance of the DbContext.
Now, I'm in this situation:
object A has a relation one-to-many with B (A can have one or many B)
object B has a relation many-to-one with C (C can have one or many B)
object B has a relation many-to-one with D (D can have one or many B)
I should add a new object B, consider that object C and D are yet existing, so I must only do the relation and the object A can be created or updated. In the specific consider that A is customer and B is subscriptions (C and D are virtual objects properties in B).
Now If I try to save I got duplicates in C and D tables, while the management of the object seems to work.
So, I thinked that I should detach the entities before do the relation, but when I call the SaveChanges() I got this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
Here's the code:
Customer customer = customerRepository.Get(ID);
if (customer == null)
{
customer = new Customer();
customer.Email = Request.Form["Email"].ToString();
}
Subscription subscription = new Subscription();
subscription.Active = true;
subscription.DateSubscription = DateTime.Today;
Object C = objectCRepository.Get(Request.Form["IDObjectC"]);//Get C object from database
Object D = objectDRepository.Get(Request.Form["IDObjectD"]);//Get D object from database
if (C != null)
{
//I tried also to detach the objects before adding to subscription
subscription.C = C;
subscription.D = D;
customer.Subscriptions.Add(subscription);
if (customer.IDCustomer == 0)
customerRepository.Add(customer);
else
UpdateModel(customer);
customerRepository.Save();
}
And here the add and the save method of the customer repository:
public override void Add(Cliente cliente)
{
db.Cliente.Add(cliente);
}
public override void Save()
{
foreach (var entry in db.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Modified || e.State == EntityState.Added || e.State == EntityState.Unchanged || e.State == EntityState.Detached))
{
string state = ObjectContext.GetObjectType(entry.Entity.GetType()).Name + " " + entry.State.ToString();
if (ObjectContext.GetObjectType(entry.Entity.GetType()).Name.Equals("C") || ObjectContext.GetObjectType(entry.Entity.GetType()).Name.Equals("D"))
{
entry.State = EntityState.Unchanged;
}
dbContext.SaveChanges();
}
I tried also to use this for objects C and D.
((System.Data.Entity.Infrastructure.IObjectContextAdapter)dbContext).ObjectContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, entry);
And the error received is
The element at index 0 in the collection of objects to refresh has a null EntityKey property value or is not attached to this ObjectStateManager.
I noticed that in CTP5 was added the option AsNoTracking(), I tried to use it, but nothing!
I checked also the Concurrency mode for every properties involved in the operation and all are set to None.
I finished ideas :(!
Any help would appreciated! Thanks!
Actually I solved myself using on loading the AsNoTracking() method and before saving the entities I change the state to Unchanged.
//On loading
Context.Object.AsNoTracking().SingleOrDefault(l => l.Property == Property);
//On saving
Object.State = EntityState.Unchanged;
Object.SaveChanges();
Hope this helps someone.

Entity Framework delete child object

I have two tables without any cascade deleting. I want to delete parent object with all child objects. I do it this way
//get parent object
return _dataContext.Menu.Include("ChildMenu").Include("ParentMenu").Include("Pictures").FirstOrDefault(m => m.MenuId == id);
//then i loop all child objects
var picList = (List<Picture>)menu.Pictures.ToList();
for (int i = 0; i < picList.Count; i++)
{
if (File.Exists(HttpContext.Current.Server.MapPath(picList[i].ImgPath)))
{
File.Delete(HttpContext.Current.Server.MapPath(picList[i].ImgPath));
}
if (File.Exists(HttpContext.Current.Server.MapPath(picList[i].ThumbPath)))
{
File.Delete(HttpContext.Current.Server.MapPath(picList[i].ThumbPath));
}
//**what must i do here?**
//menu.Pictures.Remove(picList[i]);
// DataManager dm = new DataManager();
// dm.Picture.Delete(picList[i].Id);
//menu.Pictures.de
//_dataContext.SaveChanges();
//picList[i] = null;
}
//delete parent object
_dataContext.DeleteObject(_dataContext.Menu.Include("ChildMenu").Include("ParentMenu")
.Include("Pictures").FirstOrDefault(m => m.MenuId == id););
_dataContext.SaveChanges();
It is enough to set the <OnDelete Action="Cascade" /> for the master association end in the CSDL part of the model.
In this case your code will work.
My situation was slightly different, and it took a while to get it right so I thought it worth documenting. I have two related tables, Quote and QuoteExtension:
Quote (Parent, Primary Key QuoteId)
QuoteExtension (Calculated fields for Quote, Primary and Foreign Key QuoteId)
I didn't have to set the OnDelete action to get it to work - but Craig's comment (if I could vote that up more I would!) led me to discover the issue. I was attempting to delete the Quote when QuoteExtension was not loaded. Therefore I found two ways that worked:
var quote = ent.Quote.Include("QuoteExtension").First(q => q.QuoteId == 2311);
ent.DeleteObject(quote);
ent.SaveChanges();
Or:
var quote = ent.Quote.First(q => q.QuoteId == 2311);
if (quote.QuoteExtension != null)
ent.Refresh(RefreshMode.ClientWins, quote.QuoteExtension);
ent.DeleteObject(quote);
ent.SaveChanges();
Interestingly trying to delete QuoteExtension manually didn't work (although it may have if I had included ent.SaveChanges() in the middle - this tends to happen only at the end of a unit of work in this system so I wanted something that didn't rely on this.

Resources