Skip to content

Commit d377029

Browse files
committed
WIP
1 parent 894b951 commit d377029

7 files changed

Lines changed: 318 additions & 15 deletions

LoopbackDataProviderPlugin.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net462</TargetFramework>
@@ -11,6 +11,8 @@
1111
<AnalysisLevel>5.0</AnalysisLevel>
1212
<DebugType>pdbonly</DebugType>
1313
<EmbedAllSources>true</EmbedAllSources>
14+
<SignAssembly>true</SignAssembly>
15+
<AssemblyOriginatorKeyFile>LoopbackDataProvider.snk</AssemblyOriginatorKeyFile>
1416
</PropertyGroup>
1517

1618
<ItemGroup>
@@ -23,11 +25,14 @@
2325
<IncludeAssets>compile</IncludeAssets>
2426
<PrivateAssets>all</PrivateAssets>
2527
</PackageReference>
28+
<PackageReference Include="Microsoft.CrmSdk.Data" Version="9.0.2.34">
29+
<IncludeAssets>compile</IncludeAssets>
30+
<PrivateAssets>all</PrivateAssets>
31+
</PackageReference>
2632
<PackageReference Include="GitVersion.MsBuild" Version="5.6.11">
2733
<IncludeAssets>build</IncludeAssets>
2834
<PrivateAssets>all</PrivateAssets>
2935
</PackageReference>
30-
<PackageReference Include="Microsoft.CrmSdk.Data" Version="9.0.2.34" />
3136
</ItemGroup>
3237

3338
</Project>

src/LoopbackDataProvider.snk

596 Bytes
Binary file not shown.

src/LoopbackDataProviderPlugin.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+

2+
using Microsoft.Xrm.Sdk;
3+
using Microsoft.Xrm.Sdk.Data.Exceptions;
4+
using Microsoft.Xrm.Sdk.Data.Extensions;
5+
using Microsoft.Xrm.Sdk.Data.Mappings;
6+
using Microsoft.Xrm.Sdk.Extensions;
7+
using Microsoft.Xrm.Sdk.Messages;
8+
using Microsoft.Xrm.Sdk.Query;
9+
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
15+
namespace LoopbackDataProvider
16+
{
17+
public sealed class LoopbackDataProviderPlugin : IPlugin
18+
{
19+
public void Execute(IServiceProvider serviceProvider)
20+
{
21+
var context = serviceProvider.Get<IPluginExecutionContext>()
22+
?? throw new ArgumentException($"Get<{nameof(IPluginExecutionContext)}>() returned null");
23+
var service = serviceProvider.GetOrganizationService(Guid.Empty)
24+
?? throw new ArgumentException($"GetOrganizationService() returned null");
25+
var tracing = serviceProvider.Get<ITracingService>()
26+
?? throw new ArgumentException($"Get<{nameof(ITracingService)}>() returned null");
27+
28+
tracing.Trace($"Rev: {GitVersionInformation.InformationalVersion}");
29+
30+
foreach (var item in context.InputParameters)
31+
tracing.Trace($"IP: {item.Key}: {item.Value} ({item.Value?.GetType()})");
32+
33+
foreach (var item in context.OutputParameters)
34+
tracing.Trace($"OP: {item.Key}: {item.Value} ({item.Value?.GetType()})");
35+
36+
foreach (var item in context.SharedVariables)
37+
tracing.Trace($"SV: {item.Key}: {item.Value} ({item.Value?.GetType()})");
38+
39+
40+
var retriever = serviceProvider.Get<IEntityDataSourceRetrieverService>()
41+
?? throw new ArgumentException($"Get<{nameof(IEntityDataSourceRetrieverService)}>() returned null");
42+
var dataSource = retriever.RetrieveEntityDataSource()
43+
?? throw new ArgumentException("RetrieveEntityDataSource() returned null");
44+
45+
var targetEntityName = dataSource.Attributes.First(x => x.Value is string).Value.ToString();
46+
47+
48+
var mapping = Mapping.Create(service, context.PrimaryEntityName);
49+
50+
var entityMetadata = service.GetEntityMetadata(context.PrimaryEntityName);
51+
52+
var typeMapFactory = new DefaultTypeMapFactory();
53+
54+
55+
switch (context.MessageName)
56+
{
57+
case "Retrieve":
58+
var entityMap =EntityMapFactory.Create(entityMetadata, typeMapFactory, "mappingEntityAlias");
59+
60+
var target = (EntityReference)context.InputParameters["Target"];
61+
var columnSet = (ColumnSet)context.InputParameters["ColumnSet"];
62+
var relatedEntitiesQuery = (RelationshipQueryCollection)context.InputParameters["RelatedEntitiesQuery"];
63+
if (relatedEntitiesQuery.Any())
64+
throw new NotImplementedException("RelatedEntitiesQuery");
65+
66+
var mappedTarget = Mapping.ReplaceEntityLogicalNames(target, context.PrimaryEntityName, entityMetadata.ExternalName);
67+
var mappedColumns = columnSet.Columns.Select(entityMap.MapAttributeNameExternal).ToArray();
68+
69+
var retrieveResponse = (RetrieveResponse)service.Execute(new RetrieveRequest {
70+
71+
ColumnSet = new (mappedColumns),
72+
Target = mappedTarget,
73+
});
74+
75+
var mappedEntity = retrieveResponse.Entity.ToEntity(entityMap);
76+
context.OutputParameters["Entity"] = mappedEntity;
77+
return;
78+
79+
case "RetrieveMultiple":
80+
81+
var queryMapFactory = new QueryMapFactory(service, typeMapFactory);
82+
83+
var query = (QueryExpression)context.InputParameters["Query"];
84+
var queryMap = queryMapFactory.Create(query);
85+
86+
var convertedQuery = query.ConvertSchema(queryMap);
87+
88+
tracing.Trace($"{nameof(convertedQuery)}: {convertedQuery.EntityName} ({string.Join(", ", convertedQuery.ColumnSet.Columns)})");
89+
tracing.Trace($"{nameof(convertedQuery)}: Conditions: {convertedQuery.Criteria.Conditions.Count()}");
90+
91+
var retrieveMultipleResponse = service.RetrieveMultiple(convertedQuery);
92+
var mappedEntities = new EntityCollection();
93+
94+
mappedEntities.Entities.AddRange(retrieveMultipleResponse.Entities.Select(x=> x.ToEntity(queryMap.PrimaryEntityMap)));
95+
96+
foreach (var item in mappedEntities.Entities)
97+
{
98+
tracing.Trace($"{item.LogicalName} {item.Id} ({string.Join(",", item.Attributes.Select(a=> $"{a.Key}: {a.Value} ({a.Value?.GetType()?.Name})"))})");
99+
}
100+
101+
mappedEntities.MoreRecords = retrieveMultipleResponse.MoreRecords;
102+
mappedEntities.MinActiveRowVersion = retrieveMultipleResponse.MinActiveRowVersion;
103+
mappedEntities.TotalRecordCountLimitExceeded = retrieveMultipleResponse.TotalRecordCountLimitExceeded;
104+
mappedEntities.TotalRecordCount = retrieveMultipleResponse.TotalRecordCount;
105+
mappedEntities.PagingCookie = retrieveMultipleResponse.PagingCookie;
106+
107+
context.OutputParameters["BusinessEntityCollection"] = mappedEntities;
108+
return;
109+
110+
case "Create":
111+
case "Update":
112+
case "Delete":
113+
default:
114+
break;
115+
}
116+
117+
var request = new OrganizationRequest(context.MessageName);
118+
119+
request.Parameters.AddRange(
120+
mapping.ConvertSchema(context.InputParameters));
121+
122+
var response = service.Execute(request);
123+
124+
context.OutputParameters.AddRange(
125+
mapping.ConvertSchema(response.Results));
126+
}
127+
128+
129+
}
130+
}

src/Mapping.cs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using Microsoft.Xrm.Sdk;
2+
using Microsoft.Xrm.Sdk.Extensions;
3+
using Microsoft.Xrm.Sdk.Data.Mappings;
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using Microsoft.Xrm.Sdk.Query;
9+
using Microsoft.Xrm.Sdk.Data.Extensions;
10+
using Microsoft.Xrm.Sdk.Messages;
11+
12+
namespace LoopbackDataProvider
13+
{
14+
sealed class Mapping
15+
{
16+
internal static Mapping Create(IOrganizationService service, string primaryEntityName)
17+
{
18+
var typeMapFactory = new DefaultTypeMapFactory();
19+
var queryMapFactory = new QueryMapFactory(service, typeMapFactory);
20+
return new(queryMapFactory);
21+
}
22+
23+
readonly QueryMapFactory queryMapFactory;
24+
25+
Mapping(QueryMapFactory queryMapFactory)
26+
{
27+
this.queryMapFactory = queryMapFactory;
28+
}
29+
30+
internal IEnumerable<KeyValuePair<string, object>> ConvertSchema(IEnumerable<KeyValuePair<string, object>> kvps)
31+
=> kvps.Select(ip => new KeyValuePair<string, object>(ip.Key, ConvertSchema(ip.Value)));
32+
33+
object ConvertSchema(object obj)
34+
=> obj switch
35+
{
36+
EntityReference entityReference
37+
=> ConvertSchema(entityReference),
38+
39+
Entity entity
40+
=> ConvertSchema(entity),
41+
42+
EntityCollection entityCollection
43+
=> ConvertSchema(entityCollection),
44+
45+
AttributeCollection attributeCollection
46+
=> ConvertSchema(attributeCollection),
47+
48+
QueryExpression queryExpression
49+
=> ConvertSchema(queryExpression),
50+
51+
_ => obj,
52+
};
53+
54+
EntityCollection ConvertSchema(EntityCollection entityCollection)
55+
{
56+
throw new NotImplementedException();
57+
//var retval = queryExpression.ConvertSchema(queryMap);
58+
// return retval;
59+
}
60+
61+
QueryExpression ConvertSchema(QueryExpression queryExpression)
62+
{
63+
var queryMap = queryMapFactory.Create(queryExpression);
64+
var retval = queryExpression.ConvertSchema(queryMap);
65+
return retval;
66+
}
67+
68+
object ConvertSchema(object obj, string logicalName, string newLogicalName)
69+
=> obj switch
70+
{
71+
EntityReference entityReference when entityReference.LogicalName == logicalName
72+
=> ReplaceEntityLogicalNames(entityReference, logicalName, newLogicalName),
73+
74+
Entity entity when entity.LogicalName == logicalName
75+
=> ReplaceEntityLogicalNames(entity, logicalName, newLogicalName),
76+
77+
EntityCollection entityCollection when entityCollection.EntityName == logicalName
78+
=> ReplaceEntityLogicalNames(entityCollection, logicalName, newLogicalName),
79+
80+
AttributeCollection attributeCollection
81+
=> ReplaceEntityLogicalNames(attributeCollection, logicalName, newLogicalName),
82+
83+
QueryExpression queryExpression
84+
=> ReplaceEntityLogicalName(queryExpression, logicalName, newLogicalName),
85+
86+
_ => obj,
87+
};
88+
89+
90+
91+
static QueryExpression ReplaceEntityLogicalName(QueryExpression queryExpression, string logicalName, string newLogicalName)
92+
{
93+
var retval = new QueryExpression()
94+
{
95+
ColumnSet = queryExpression.ColumnSet,
96+
Criteria = queryExpression.Criteria,
97+
EntityName = newLogicalName,
98+
ExtensionData = queryExpression.ExtensionData,
99+
NoLock = queryExpression.NoLock,
100+
PageInfo = queryExpression.PageInfo,
101+
QueryHints = queryExpression.QueryHints,
102+
SubQueryExpression = queryExpression.SubQueryExpression,
103+
Distinct = queryExpression.Distinct,
104+
TopCount = queryExpression.TopCount,
105+
};
106+
107+
retval.LinkEntities.AddRange(queryExpression.LinkEntities);
108+
retval.Orders.AddRange(queryExpression.Orders);
109+
110+
return retval;
111+
}
112+
113+
114+
public static EntityReference ReplaceEntityLogicalNames(EntityReference entityReference, string logicalName, string newLogicalName)
115+
=> new()
116+
{
117+
ExtensionData = entityReference.ExtensionData,
118+
Id = entityReference.Id,
119+
KeyAttributes = entityReference.KeyAttributes,
120+
LogicalName = newLogicalName,
121+
Name = entityReference.Name,
122+
RowVersion = entityReference.RowVersion,
123+
};
124+
125+
static Entity ReplaceEntityLogicalNames(Entity entityReference, string logicalName, string newLogicalName)
126+
{
127+
var retval = new Entity()
128+
{
129+
ExtensionData = entityReference.ExtensionData,
130+
Id = entityReference.Id,
131+
KeyAttributes = entityReference.KeyAttributes,
132+
LogicalName = newLogicalName,
133+
Attributes = ReplaceEntityLogicalNames(entityReference.Attributes, logicalName, newLogicalName),
134+
RowVersion = entityReference.RowVersion,
135+
EntityState = entityReference.EntityState,
136+
HasLazyFileAttribute = entityReference.HasLazyFileAttribute,
137+
LazyFileAttributeKey = entityReference.LazyFileAttributeKey,
138+
LazyFileAttributeValue = entityReference.LazyFileAttributeValue,
139+
LazyFileSizeAttributeKey = entityReference.LazyFileSizeAttributeKey,
140+
LazyFileSizeAttributeValue = entityReference.LazyFileSizeAttributeValue,
141+
};
142+
143+
retval.RelatedEntities.AddRange(ReplaceEntityLogicalNames(retval.RelatedEntities, logicalName, newLogicalName));
144+
retval.FormattedValues.AddRange(retval.FormattedValues);
145+
return retval;
146+
}
147+
148+
static IEnumerable<Entity> ReplaceEntityLogicalNames(IEnumerable<Entity> entities, string logicalName, string newLogicalName)
149+
=> entities.Select(e => ReplaceEntityLogicalNames(e, logicalName, newLogicalName));
150+
151+
static IEnumerable<KeyValuePair<Relationship, EntityCollection>> ReplaceEntityLogicalNames(RelatedEntityCollection kvps, string logicalName, string newLogicalName)
152+
=> kvps.Select<KeyValuePair<Relationship, EntityCollection>, KeyValuePair<Relationship, EntityCollection>>(
153+
_ => throw new NotImplementedException(nameof(RelatedEntityCollection)));
154+
155+
static AttributeCollection ReplaceEntityLogicalNames(AttributeCollection attributeCollection, string logicalName, string newLogicalName)
156+
{
157+
throw new NotImplementedException();
158+
var retval = new AttributeCollection();
159+
// retval.AddRange(
160+
// ReplaceEntityLogicalNames(attributeCollection.AsEnumerable(), logicalName, newLogicalName));
161+
return retval;
162+
}
163+
164+
private static EntityCollection ReplaceEntityLogicalNames(EntityCollection entityCollection, string logicalName, string newLogicalName)
165+
{
166+
var retval = new EntityCollection()
167+
{
168+
EntityName = newLogicalName,
169+
ExtensionData = entityCollection.ExtensionData,
170+
MinActiveRowVersion = entityCollection.MinActiveRowVersion,
171+
MoreRecords = entityCollection.MoreRecords,
172+
PagingCookie = entityCollection.PagingCookie,
173+
TotalRecordCount = entityCollection.TotalRecordCount,
174+
TotalRecordCountLimitExceeded = entityCollection.TotalRecordCountLimitExceeded,
175+
};
176+
177+
retval.Entities.AddRange(ReplaceEntityLogicalNames(entityCollection.Entities.AsEnumerable(), logicalName, newLogicalName));
178+
return retval;
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)