Error when creating multiple N:1 relationships for an entity - dynamics-crm

I am using the MetadataService to create 4 entities and create relationships between them.
The entities are: TrexCalendar, TrexFrom, TrexTo, TrexAddress
The relationships (all 1:Many) are:TrexFrom - TrexAddress, TrexCalendar - TrexFrom, TrexTo - TrexAddress, TrexCalendar - TrexTo
When I run my code the entities are all created successfully and the first, second and fourth relationships are created successfully.
Creating the third relationship fails with the following details:
0x80047007 Entity: new_trexaddress is parented to Entity with id:
7a6af338-bc23-e011-ad8c-9f5d300a22fe. Cannot create another parental
relation with Entity: new_trexto Platform
7a6af338-bc23-e011-ad8c-9f5d300a22fe is the id for the TrexFrom entity.
So it looks like the SDK won't allow me to create a 1:N relationship between TrexTo and TrexAddress because a 1:N relationship exists between TrexFrom and TrexAddress.
What's weird is that I am able to create this relationship manually using the Dynamics web interface.
Any ideas what might be going on? How can I create both relationships programmatically?
I'm using the following code to create the relationships:
OneToManyMetadata relationship = new OneToManyMetadata
{
ReferencedEntity = "new_trexto",
ReferencingEntity = "new_trexaddress"
SchemaName = "new_trexto_trexaddress",
AssociatedMenuBehavior = new CrmAssociatedMenuBehavior { Value = AssociatedMenuBehavior.UseCollectionName },
CascadeAssign = new CrmCascadeType { Value = CascadeType.NoCascade },
CascadeDelete = new CrmCascadeType { Value = CascadeType.RemoveLink },
CascadeMerge = new CrmCascadeType { Value = CascadeType.NoCascade },
CascadeReparent = new CrmCascadeType { Value = CascadeType.NoCascade },
CascadeShare = new CrmCascadeType { Value = CascadeType.UserOwned },
CascadeUnshare = new CrmCascadeType { Value = CascadeType.NoCascade }
};
LookupAttributeMetadata lookup = new LookupAttributeMetadata
{
SchemaName = lookupName,
RequiredLevel = new CrmAttributeRequiredLevel(AttributeRequiredLevel.Recommended),
DisplayName = CrmServiceUtility.CreateSingleLabel("TrexTo - TrexAddress", 1033)
};
CreateOneToManyRequest request = new CreateOneToManyRequest
{
OneToManyRelationship = relationship,
Lookup = lookup
};
try
{
metadataService.Execute(request);
Debug.Print("Relationship created successfully");
}
catch (System.Web.Services.Protocols.SoapException ex)
{
Debug.Print(ex.Detail.InnerText);
}

If both relationships are set as "Parental", you'll probably need to set one of them as "Referential"

Related

Translating LINQ query into CRM QueryExpression while using 'select new'

I've got the following container class:
public class AssetAndItsDepositsContainer
{
public jp_asset Asset { get; set; }
public jp_deposit Deposit { get; set; }
}
Is there a way to take the following LINQ query:
from a in serviceContext.jp_assetSet
join d in serviceContext.jp_depositsSet on a.Id equals d.jp_assetid.Id
where a.statecode == jp_assetState.Active &&
a.jp_isonhold = true
select new AssetAndItsDepositsContainer()
{
Asset = a,
Deposit = d
})
.ToList();
And "translate" it by using QueryExpression?
This is what I came up with so far, but I don't know how to mimic the select new expression:
QueryExpression query = new QueryExpression(jp_asset.EntityLogicalName);
query.ColumnSet = new ColumnSet(true);
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, (int)jp_assetState.Active);
query.Criteria.AddCondition("jp_isonhold", ConditionOperator.Equal, true);
LinkEntity link = query.AddLink(jp_deposit.EntityLogicalName, "Id", "jp_assetid", JoinOperator.Inner);
// Now what?
var res = service.RetrieveMultiple(query).Entities; // gets only jp_assets
You can't access an entire LinkEntity, you can only access its attributes as AliasedValue properties on the main Entity itself.
I would simply retrieve the Id of the second record you're looking for as part of an EntityReference and then execute a Retrieve.
You can actually construct valid Entity objects from linked entities. The only requirement is that the primary key of the linked entity must be available in the result set.
So, your code snippet could be extended like this:
var query = new QueryExpression(jp_asset.EntityLogicalName);
query.ColumnSet.AllColumns = true;
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, (int)jp_assetState.Active);
query.Criteria.AddCondition("jp_isonhold", ConditionOperator.Equal, true);
LinkEntity link = query.AddLink(jp_deposit.EntityLogicalName, "Id", "jp_assetid", JoinOperator.Inner);
link.EntityAlias = "d";
link.Columns.AddColumns("jp_depositid", "jp_name");
IEnumerable<Entity> deposits = Service.RetrieveMultiple(query).Entities
.Select(e => new Entity("jp_deposit")
{
Id = (Guid)e.GetAttributeValue<AliasedValue>("d.jp_depositid").Value,
["jp_name"] = e.GetAttributeValue<AliasedValue>("d.jp_name")?.Value
});
Note
The example above will only work reliable for inner joins. In the result set the primary key (ID) for the linked entity will always be available. Therefore it is safe to get its value using the .Value syntax. All other attribute values can be retrieved using ?.Value.
For left joins you would need to filter the result set first before performing the Select.

CRM Linq find all parents that have 0 children

How can I find (preferably using CRM Linq) parent entities that have 0 children. For example how can I find all accounts that have 0 contacts.
If you are going to use the query expression route, which I would recommend then the following code will be useful
var entityAlias = "con";
var query = new QueryExpression
{
EntityName = "account",
ColumnSet = new ColumnSet(true),
Criteria =
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression(entityAlias, "contactid",ConditionOperator.Null)
}
}
LinkEntities =
{
new LinkEntity
{
EntityAlias = entityAlias,
LinkFromEntityName = "account",
LinkFromAttributeName = "accountid",
LinkToEntityName = "contact",
LinkToAttributeName = "parentcustomerid",
Columns = new ColumnSet("parentcustomerid", "contactid"),
JoinOperator = JoinOperator.LeftOuter,
}
},
};
var response = service.RetrieveMultiple(query);
var accounts = response.Entities;
In this code I have not limited the columns, this will reduce performance and you should only return the columns needed.
If there is the case for more than 5000 records are going to be returned then you will need to use paging and loop the query to find all the entities,
This can be found here:
https://msdn.microsoft.com/en-us/library/gg327917.aspx
However if you are certain you want to use LINQ then you can use the following code:
public static IEnumerable<Account> FindAccountsWithNoContacts()
{
var contactRelationship = new Relationship("contact_customer_accounts");
foreach(var account in XrmContext.AccountSet)
{
XrmContext.LoadProperty(contactRelationship);
if(!account.RelatedEntities.ContainsKey(contactRelationship)
yield return account;
}
}
My problem with the LINQ code is that all the enities, both the account and contact entities, will be loaded into memory. With large entity sets this can cause OutOfMemoryException, whereas the query expression route will pass the query to the Dynamics server to execute; which should make the execution of the code faster.
The thing you are looking for is left outer join. Which is unfortunately not possible in CRM using LINQ. However you can do it using query expression or FetchXML.
Here is a link that can help you:
https://community.dynamics.com/crm/b/gonzaloruiz/archive/2014/02/23/all-about-outer-join-queries-in-crm-2011-and-crm-2013

Get field name which is related to contact from each entity in dynamics crm using c#

I have one function which gets data as per module name related contacts.
Code:
if (modulename == "lead" || modulename == "opportunity")
{
contactfield= "parentcontactid";
}
else if (modulename == "salesorder" || modulename == "quote" || modulename == "incident" || modulename == "invoice")
{
contactfield= = "customerid";
}
else
{
}
quotequery = new QueryExpression()
{
Distinct = false,
EntityName = modulename,
ColumnSet = new ColumnSet(true),
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("ownerid", ConditionOperator.Equal, userid),
new ConditionExpression(contactfield, ConditionOperator.Equal, lstcredential.Contactid.ToString())
},
}
}
}
};
queryentityCollection = _serviceProxy.RetrieveMultiple(quotequery).Entities;
But for all module there is different different name which is related to contact.for e.g.in lead it is parentcontactid but for invoice it is customerid.
So Is there any solution for fetch attribute name related to contact from entity name?because if else for each entity is not solution as I do in starting.
Please suggest me answer.
If you are looking for a specific contact lookup on a finite set of entities I would hard code it in a name value pair collection similar to what you're doing except maybe a key value pair store:
private Dictionary<String, String> _contactMappings = new Dictionary<String, String>{"lead", "parentcontactid"}, {"incident", "contactid"};
An alternative would be querying metadata to look for contact lookups, and caching the results, but you wouldn't know which is the correct lookup. For example incident has 3 lookups to contact (contactid, responsiblecontactid, and primarycontactid).

Many-To-Many Entity Framework Update

I have an object that has a many-to-many relationship with another object. I am trying to write an update statement that doesn't result in having to delete all records from the many-to-many table first.
My data is:
StoredProcedure - StoredProcedureId, Name
Parameter - ParameterId, Name
StoredProcedure_Parameter - StoredProcedureId, ParameterId, Order
I have a UI for updating a stored procedured object (adding/removing parameters or changing the order of the parameters).
When I save, I end up at:
var storedProcedure = context.Sprocs.FirstOrDefault(s => s.SprocID == sproc.StoredProcedureId);
if (storedProcedure == null)
{
//do something like throw an exception
} else
{
storedProcedure.Name = sproc.Name;
//resolve Parameters many to many here
//remove all Params that are not in sproc.Params
//Add any params that are in sproc.Params but not in storedProcedure.Params
//Update the Order number for any that are in both
}
I know I could simply call .Clear() on the table and then reinsert all of the values with their current state (ensuring that all parameters that were removed by the UI are gone, new ones are added, and updated Orders are changed). However, I feel like there must be a better way to do this. Do many-to-many updates with EF usually get resolved by deleting all of the elements and reinserting them?
Here there is my code that I use and it works. The difference is that instead o having your 3 tables( StoredProcedure, StoredProcedure_Parameter and Parameter ) I have the following 3 tables: Order, OrdersItem(this ensure the many-to-many relation) and Item. This is the procedure that I used for updating or add an order, or after I change an existing OrderItem or add a new one to the Order.
public void AddUpdateOrder(Order order)
{
using (var db = new vitalEntities())
{
if (order.OrderId == 0)
{
db.Entry(order).State = EntityState.Added;
}
else
{
foreach (var orderItem in order.OrdersItems)
{
if (orderItem.OrderItemsId == 0)
{
orderItem.Item = null;
if (order.OrderId != 0)
orderItem.OrderId = order.OrderId;
db.Entry(orderItem).State = EntityState.Added;
}
else
{
orderItem.Order = null;
orderItem.Item = null;
db.OrdersItems.Attach(orderItem);
db.Entry(orderItem).State = EntityState.Modified;
}
}
db.Orders.Attach(order);
db.Entry(order).State = EntityState.Modified;
}
SaveChanges(db);
}
}

Explicit construction of entity type in query is not allowed [duplicate]

Using Linq commands and Linq To SQL datacontext, Im trying to instance an Entity called "Produccion" from my datacontext in this way:
Demo.View.Data.PRODUCCION pocoProduccion =
(
from m in db.MEDICOXPROMOTORs
join a in db.ATENCIONs on m.cmp equals a.cmp
join e in db.EXAMENXATENCIONs on a.numeroatencion equals e.numeroatencion
join c in db.CITAs on e.numerocita equals c.numerocita
where e.codigo == codigoExamenxAtencion
select new Demo.View.Data.PRODUCCION
{
cmp = a.cmp,
bonificacion = comi,
valorventa = precioEstudio,
codigoestudio = lblCodigoEstudio.Content.ToString(),
codigopaciente = Convert.ToInt32(lblCodigoPaciente.Content.ToString()),
codigoproduccion = Convert.ToInt32(lblNroInforme.Content.ToString()),
codigopromotor = m.codigopromotor,
fecha = Convert.ToDateTime(DateTime.Today.ToShortDateString()),
numeroinforme = Convert.ToInt32(lblNroInforme.Content.ToString()),
revisado = false,
codigozona = (c.codigozona.Value == null ? Convert.ToInt32(c.codigozona) : 0),
codigoclinica = Convert.ToInt32(c.codigoclinica),
codigoclase = e.codigoclase,
}
).FirstOrDefault();
While executing the above code, I'm getting the following error that the stack trace is included:
System.NotSupportedException was caught
Message="The explicit construction of the entity type 'Demo.View.Data.PRODUCCION' in a query is not allowed."
Source="System.Data.Linq"
StackTrace:
en System.Data.Linq.SqlClient.QueryConverter.VisitMemberInit(MemberInitExpression init)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.Visit(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.VisitSelect(Expression sequence, LambdaExpression selector)
en System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.Visit(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.VisitFirst(Expression sequence, LambdaExpression lambda, Boolean isFirst)
en System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.ConvertOuter(Expression node)
en System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
en System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
en System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
en System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
en Demo.View.InformeMedico.realizarProduccionInforme(Int32 codigoExamenxAtencion, Double precioEstudio, Int32 comi) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 602
en Demo.View.InformeMedico.UpdateEstadoEstudio(Int32 codigo, Char state) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 591
en Demo.View.InformeMedico.btnGuardar_Click(Object sender, RoutedEventArgs e) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 683
InnerException:
Is that now allowed in LINQ2SQL?
Entities can be created outside of queries and inserted into the data store using a DataContext. You can then retrieve them using queries. However, you can't create entities as part of a query.
I am finding this limitation to be very annoying, and going against the common trend of not using SELECT * in queries.
Still with c# anonymous types there is a workaround, by fetching the objects into an anonymous type, and then copy it over into the correct type.
For example:
var q = from emp in employees where emp.ID !=0
select new {Name = emp.First + " " + emp.Last, EmployeeId = emp.ID }
var r = q.ToList();
List<User> users = new List<User>(r.Select(new User
{
Name = r.Name,
EmployeeId = r.EmployeeId
}));
And in the case when we deal with a single value (as in the situation described in the question) it is even easier, and we just need to copy directly the values:
var q = from emp in employees where emp.ID !=0
select new { Name = emp.First + " " + emp.Last, EmployeeId = emp.ID }
var r = q.FirstOrDefault();
User user = new User { Name = r.Name, EmployeeId = r.ID };
If the name of the properties match the database columns we can do it even simpler in the query, by doing select
var q = from emp in employees where emp.ID !=0
select new { emp.First, emp.Last, emp.ID }
One might go ahead and write a lambda expression that can copy automatically based on the property name, without needing to specify the values explictly.
Here's another workaround:
Make a class that derives from your LINQ to SQL class. I'm assuming that the L2S class that you want to return is Order:
internal class OrderView : Order { }
Now write the query this way:
var query = from o in db.Order
select new OrderView // instead of Order
{
OrderID = o.OrderID,
OrderDate = o.OrderDate,
// etc.
};
Cast the result back into Order, like this:
return query.Cast<Order>().ToList(); // or .FirstOrDefault()
(or use something more sensible, like BLToolkit / LINQ to DB)
Note: I haven't tested to see if tracking works or not; it works to retrieve data, which is what I needed.
I have found that if you do a .ToList() on the query before trying to contruct new objects it works
I just ran into the same issue.
I found a very easy solution.
var a = att as Attachment;
Func<Culture, AttachmentCulture> make =
c => new AttachmentCulture { Culture = c };
var culs = from c in dc.Cultures
let ac = c.AttachmentCultures.SingleOrDefault(
x => x.Attachment == a)
select ac == null ? make(c) : ac;
return culs;
I construct an anonymous type, use IEnumerable (which preserves deferred execution), and then re-consruct the datacontext object. Both Employee and Manager are datacontext objects:
var q = dc.Employees.Where(p => p.IsManager == 1)
.Select(p => new { Id = p.Id, Name = p.Name })
.AsEnumerable()
.Select(item => new Manager() { Id = item.Id, Name = item.Name });
Within the book "70-515 Web Applications Development with Microsoft .NET Framework 4 - Self paced training kit", page 638 has the following example to output results to a strongly typed object:
IEnumerable<User> users = from emp in employees where emp.ID !=0
select new User
{
Name = emp.First + " " + emp.Last,
EmployeeId = emp.ID
}
Mark Pecks advice appears to contradict this book - however, for me this example still displays the above error as well, leaving me somewhat confused. Is this linked to version differences? Any suggestions welcome.
I found another workaround for the problem that even lets you retain your result as IQueryale, so it doesn't actually execute the query until you want it to be executed (like it would with the ToList() method).
So linq doesn't allow you to create an entity as a part of query? You can shift that task to the database itself and create a function that will grab the data you want. After you import the function to your data context, you just need to set the result type to the one you want.
I found out about this when I had to write a piece of code that would produce a IQueryable<T> in which the items don't actually exist in the table containing T.
pbz posted a work around by creating a View class inherited from an entity class that you could be working with. I'm working with a dbml model of a table that has > 200 columns. When I try and return the whole table I get "Root Element missing" errors. I couldn't find anyone who wanted to deal with my particular issue so I was looking at rewriting my entire approach. Just creating a view class for the entitiy class worked in my case.
As pbz suggests : Create a view class that inherits from your entity class. For me this is tbCamp so :
internal class tbCampView : tbCamp
{
}
Then use the view class in your query :
using (var dc = ConnectionClass.Connect(Dev))
{
var camps = dc.tbCamps.Select(s => new tbCampView
{
active = s.active,
idCamp = s.idCamp,
campName = s.campName
});
SmartTableViewer(camps, dg1);
}
private void SmartTableViewer<T>(IEnumerable<T> allRecords)
{
// Build sorted rows back into new table
var table = new DataTable();
// Create columns based on type
if (allRecords is IEnumerable<tbCamp> tbCampRecords)
{
// Get the columns you want
table.Columns.Add("idCamp");
table.Columns.Add("campName");
foreach (var record in tbCampRecords)
{
// Make a new row
var r = table.NewRow();
// Add the contents to each column of the row
r["idCamp"] = record.idCamp;
r["campName"] = record.campName;
// Add the row to the table.
table.Rows.Add(r);
}
}
else
{
MessageBox.Show("Unhandled type. Add support for new data type in SmartTableViewer()");
return;
}
// Update table in grid
dg1.DataSource = table.DefaultView;
}
Here is what happens when you try and create an entity class object in the query.
I didn't want to have to use an anonymous type if I could help it because I wanted the type to be tbCamp. Since tbCampView is of type tbCamp the is operator works well. see Brian Hasden's answer Passing a generic List<> in C#
I'm surprised this is even an issue but with larger tables I run into this error so I thought I would just show it here :
When trying to read this table into memory I get the following error. There are < 2000 rows but the columns are > 200 for each. I don't know if that is an issue or not.
If I just want a few columns I need to create a custom class and handle that which isn't that big of a pain. With the approach pbz provided I don't have to worry about it.
Here is the entire project in case it helps someone.
public partial class Form1 : Form
{
private const bool Dev = true;
public Form1()
{
InitializeComponent();
}
private void btnGetAllCamps_Click(object sender, EventArgs e)
{
using (var dc = ConnectionClass.Connect(Dev))
{
IQueryable<tbCampView> camps = dc.tbCamps.Select(s => new tbCampView
{
// Project columns as needed.
active = s.active,
idCamp = s.idCamp,
campName = s.campName
});
// pass in as a
SmartTableViewer(camps);
}
}
private void SmartTableViewer<T>(IEnumerable<T> allRecords)
{
// Build sorted rows back into new table
var table = new DataTable();
// Create columns based on type
if (allRecords is IEnumerable<tbCamp> tbCampRecords)
{
// Get the columns you want
table.Columns.Add("idCamp");
table.Columns.Add("campName");
foreach (var record in tbCampRecords)
{
//var newRecord = record;
// Make a new row
var r = table.NewRow();
// Add the contents to each column of the row
r["idCamp"] = record.idCamp;
r["campName"] = record.campName;
// Add the row to the table.
table.Rows.Add(r);
}
}
else
{
MessageBox.Show("Unhandled type. Add support for new data type in SmartTableViewer()");
return;
}
// Update table in grid
dg1.DataSource = table.DefaultView;
}
internal class tbCampView : tbCamp
{
}
}

Resources