generic ralationship in neo4j c# - neo4j

I need to establish a relationship between two different node type like this:
public class Fallow<T,U>: Relationship,
IRelationshipAllowingSourceNode<T>,
IRelationshipAllowingTargetNode<U>
{
public Fallow(NodeReference targetNode)
: base(targetNode)
{
}
public const string TypeKey = "FALLOW";
public DateTime relationDate { get; set; }
public override string RelationshipTypeKey
{
get { return TypeKey; }
}
}
I have an error:
Error 1 'Biber10.Neo4j.Fallow<T,U>' cannot implement both 'Neo4jClient.IRelationshipAllowingParticipantNode<T>' and 'Neo4jClient.IRelationshipAllowingParticipantNode<U>' because they may unify for some type parameter substitutions C:\Users\turgut\Documents\Visual Studio 2013\Projects\Biber10\Biber10.Neo4j\Fallow.cs 10 18 Biber10.Neo4j
How do I fix it?.
Thanks.

We've moved away from the use of Relationship like this, the best example of the thing you're trying to do would be something like this:
public class Fallow
{
public const string TypeKey = "FALLOW";
public DateTime RelationDate { get; set; }
}
Used like so:
//Just using this to create a demo node
public class GeneralNode
{
public string AValue { get; set; }
}
var gc = new GraphClient(new Uri("http://localhost.:7474/db/data/"));
gc.Connect();
//Create
var node1 = new GeneralNode { AValue = "val1"};
var node2 = new GeneralNode { AValue = "val2" };
var fallow = new Fallow { RelationDate = new DateTime(2016, 1, 1)};
gc.Cypher
.Create($"(n:Value {{node1Param}})-[:{Fallow.TypeKey} {{fallowParam}}]->(n1:Value {{node2Param}})")
.WithParams(new
{
node1Param = node1,
node2Param = node2,
fallowParam = fallow
})
.ExecuteWithoutResults();
//Get
var query = gc.Cypher
.Match($"(n:Value)-[r:{Fallow.TypeKey}]->(n1:Value)")
.Return(r => r.As<Fallow>());
var results = query.Results;
foreach (var result in results)
{
Console.WriteLine("Fallow: " + result.RelationDate);
}

Related

How to create DbModificationClause with CASE WHEN .... THEN

all
I was create IDbCommandTreeInterceptor and got the problem: EF provider wrong sql generation. As a result, I want to get this SQL
UPDATE [dbo].[Devices] SET [DeletedDate] = CASE
WHEN [DeletedDate] IS NULL THEN GETUTCDATE()
ELSE [DeletedDate] END
Code for testing. Interseptor class for fake delettion.
public class SoftDeleteInterseptor : IDbCommandTreeInterceptor
{
private const string DELETED_DATE_COLUMN = "DeletedDate";
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
{
return;
}
var deleteCommand = interceptionContext.OriginalResult as DbDeleteCommandTree;
if (deleteCommand != null)
{
interceptionContext.Result = HandleDeleteCommand(deleteCommand);
return;
}
}
private DbCommandTree HandleDeleteCommand(DbDeleteCommandTree deleteCommand)
{
if (!IsPropertyExists(deleteCommand, DELETED_DATE_COLUMN))
{
return deleteCommand;
}
var deletedProperty = DbExpressionBuilder.Property(
DbExpressionBuilder.Variable(deleteCommand.Target.VariableType, deleteCommand.Target.VariableName),
DELETED_DATE_COLUMN
);
var caseValue = DbExpressionBuilder.Case(
new DbExpression[] { deletedProperty.IsNull() },
new DbExpression[] { EdmFunctions.CurrentUtcDateTime() },
deletedProperty);
var setClauses = new List<DbModificationClause> { DbExpressionBuilder.SetClause(deletedProperty, caseValue) };
return new DbUpdateCommandTree(
deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace,
deleteCommand.Target,
deleteCommand.Predicate,
setClauses.AsReadOnly(), null);
}
private bool IsPropertyExists(DbModificationCommandTree command, string property)
{
var table = (EntityType)command.Target.VariableType.EdmType;
return table.Properties.Any(p => p.Name == property);
}
}
Create configuration class for register DbInterseptor.
public class CustomDbConfiguration : DbConfiguration
{
public CustomDbConfiguration()
{
AddInterceptor(new SoftDeleteInterseptor());
}
}
public partial class CustomDbContext : DbContext
{
static IDCompleteDbContext()
{
DbConfiguration.SetConfiguration(new CustomDbConfiguration());
}
public virtual DbSet<CommandEntity> CommandEntities { get; set; }
}
public class CommandEntity
{
public int Id {get; set;}
public DateTime? DeletedDate {get; set;}
public string Name {get; set;}
}
When delete entity, entity did not deleted
var context = new CustomDbContext();
var entity = context.CommandEntities.First();
context.CommandEntities.Remove(entity);
context.SubmitChanges();
Did not work. EF provider generate wrong SQL: UPDATE [dbo].[Devices] SET [DeletedDate] = [DeletedDate] IS NULL#0[DeletedDate] WHERE ([Id] = #1) #0: '01.05.2018 7:45:22' (Type = DateTime2) #1: '20' (Type = Int32)

BreezeSharp Attach Property key not found

I'm implementing an application with Breezesharp. I ran into a issue when insert the entity in the EntityManager. The error is:
There are no KeyProperties yet defined on EntityType: 'TransportReceipt:#Business.DomainModels'
I already faced this error with my first entity type "Customer" and implement a mismatching approach as suggested here. In that case I made the get operation against my WebApi with success. But now I'm creating the TransportReceipt entity inside my application.
Mapping mismatch fix
public static class ExtendMap
{
private static bool? executed;
public static void Execute(MetadataStore metadataStore) {
if (ExtendMap.executed == true)
{
return;
}
var customerBuilder = new EntityTypeBuilder<Customer>(metadataStore);
customerBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptBuilder = new EntityTypeBuilder<TransportReceipt>(metadataStore);
transportReceiptBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptAttachmentBuilder = new EntityTypeBuilder<TransportReceiptAttachment>(metadataStore);
transportReceiptAttachmentBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var uploadedFileBuilder = new EntityTypeBuilder<UploadedFile>(metadataStore);
uploadedFileBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
ExtendMap.executed = true;
}
}
My base dataservice core code
public abstract class SimpleBaseDataService
{
public static string Metadata { get; protected set; }
public static MetadataStore MetadataStore { get; protected set; }
public string EntityName { get; protected set; }
public string EntityResourceName { get; protected set; }
public EntityManager EntityManager { get; set; }
public string DefaultTargetMethod { get; protected set; }
static SimpleBaseDataService()
{
try
{
var metadata = GetMetadata();
metadata.Wait();
Metadata = metadata.Result;
MetadataStore = BuildMetadataStore();
}
catch (Exception ex)
{
var b = 0;
}
}
public SimpleBaseDataService(Type entityType, string resourceName, string targetMethod = null)
{
var modelType = typeof(Customer);
Configuration.Instance.ProbeAssemblies(ConstantsFactory.BusinessAssembly);
try
{
this.EntityName = entityType.FullName;
this.EntityResourceName = resourceName;
this.DefaultTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? "GetAllMobile" : targetMethod);
var dataService = new DataService($"{ConstantsFactory.Get.BreezeHostUrl}{this.EntityResourceName}", new CustomHttpClient());
dataService.HasServerMetadata = false;
this.EntityManager = new EntityManager(dataService, SimpleBaseDataService.MetadataStore);
this.EntityManager.MetadataStore.AllowedMetadataMismatchTypes = MetadataMismatchTypes.AllAllowable;
// Attach an anonymous handler to the MetadataMismatch event
this.EntityManager.MetadataStore.MetadataMismatch += (s, e) =>
{
// Log the mismatch
var message = string.Format("{0} : Type = {1}, Property = {2}, Allow = {3}",
e.MetadataMismatchType, e.StructuralTypeName, e.PropertyName, e.Allow);
// Disallow missing navigation properties on the TodoItem entity type
if (e.MetadataMismatchType == MetadataMismatchTypes.MissingCLRNavigationProperty &&
e.StructuralTypeName.StartsWith("TodoItem"))
{
e.Allow = false;
}
};
}
catch (Exception ex)
{
var b = 0;
}
}
}
This is who I'm trying to add the new entity
//DataService snippet
public void AttachEntity(T entity)
{
this.EntityManager.AttachEntity(entity, EntityState.Added);
}
//Business
this.TransportReceipt = new TransportReceipt { id = Guid.NewGuid(), date = DateTime.Now, customerId = Customer.id/*, customer = this.Customer*/ };
this.Attachments = new List<TransportReceiptAttachment>();
this.TransportReceipt.attachments = this.Attachments;
TransportReceiptDataService.AttachEntity(this.TransportReceipt);
When I try to add add the entity to the EntityManager, I can see the custom mapping for all my entity classes.
So my question is what I'm doing wrong.
Ok. That was weird.
I changed the mapping for a new fake int property and works. I'll test the entire save flow soon and I'll share the result here.
Update
I moved on and start removing Breezesharp. The Breezesharp project is no up-to-date and doesn't have good integration with Xamarin. I'll appreciate any comment with your experience.

how to query connected graph using neo4jclient like Master detail SQL in EF

I am new to the Neo4jClient as well as Neo4J so was not sure how to query for the data and get a master detail like data in the neo4j. Let me explain this with an example:
lets suppose I have a graph as below:
root -[:DEFINES] -> Shipment 1
-[:HAS_CONSIGNMENT]->Consignment 1
-[:HAS_ITEM]->Load Item 11
-[:HAS_ITEM]->Load Item 12
-[:HAS_CONSIGNEE]->Consignee 1
-[:HAS_CONSIGNMENT]->Consignment 2
-[:HAS_ITEM]->Load Item 21
-[:HAS_ITEM]->Load Item 22
-[:HAS_CONSIGNEE]->Consignee 2
now suppose I want to get all the graph t o populate my Domain Model like below
public class Shipment
{
public List<Consignment> Consignments {get; set;}
}
public class Consignment
{
public List<LoadItem> LoadItems {get; set;}
public Consignee ShippedTo {get; set;}
}
public class LoadItem
{
}
i know that I can probably build a Cypher query like below
How to retrieve connected graph using neo4jclient
query = client.Cypher.Start(new { root = client.RootNode }).
Match("root-[:DEFINES]->load-[:HAS_CONSIGNMENT]->consignments -[:HAS_ITEM]->loadItem").Match("consignments-[:HAS_CONSIGNEE]->consignee").
Where((Load load) => load.Id == myId).
Return(
(load,consignments, loaditems)=>
new {
loadInfo = load.As<Node<Load>>(),
consignments = consignments.CollectAs<Consignment>(),
loadItems = loaditems.CollectAs<LoadItem>()
});
but I am not sure how this can be converted to represent the second level of list that gives me that Consignment 2 has Load Item 21 & 22 where as Consignment 1 has Item 11 & 12.
can some one please help me understand how this works as I primarily have been working in the EF and the graph query is really new to me.
Regards
Kiran
Right, this is the way I've got this working (I'm pretty sure Tatham will come along with a better answer - so hold out for a bit)
public static ICollection<Shipment> Get()
{
var query = GraphClient.Cypher
.Start(new {root = GraphClient.RootNode})
.Match(
string.Format("root-[:{0}]->shipment-[:{1}]-consignments-[:{2}]->loadItem", Defines.TypeKey, HasConsignment.TypeKey, HasItem.TypeKey),
string.Format("consignments-[:{0}]->consignee", HasConsignee.TypeKey)
)
.Return((shipment, consignments, loadItem, consignee) =>
new
{
Shipment = shipment.As<Node<Shipment>>(),
Consignment = consignments.As<Consignment>(),
LoadItem = loadItem.CollectAs<LoadItem>(),
Consignee = consignee.As<Consignee>(),
});
var results = query.Results.ToList();
var output = new List<Node<Shipment>>();
foreach (var result in results)
{
var shipmentOut = output.SingleOrDefault(s => s.Reference == result.Shipment.Reference);
if (shipmentOut == null)
{
shipmentOut = result.Shipment;
shipmentOut.Data.Consignments = new List<Consignment>();
output.Add(shipmentOut);
}
result.Consignment.LoadItems = new List<LoadItem>();
result.Consignment.LoadItems.AddRange(result.LoadItem.Select(l => l.Data));
shipmentOut.Data.Consignments.Add(result.Consignment);
}
return output.Select(s => s.Data).ToList();
}
This will get you all the Shipments and Consignments etc.
However I note you do: .Where((Load load) => load.Id == myId) which implies you know the shipment id.
As a consequence, we can simplify the code a little bit - as we don't need to use 'root', and we can pass in the Shipment ID.
public static Shipment Get2(NodeReference<Shipment> shipmentNodeReference)
{
var query = GraphClient.Cypher
.Start(new {shipment = shipmentNodeReference})
.Match(
string.Format("shipment-[:{0}]-consignments-[:{1}]->loadItem", HasConsignment.TypeKey, HasItem.TypeKey),
string.Format("consignments-[:{0}]->consignee", HasConsignee.TypeKey)
)
.Return((shipment, consignments, loadItem, consignee) =>
new {
Shipment = shipment.As<Node<Shipment>>(),
Consignment = consignments.As<Consignment>(),
LoadItem = loadItem.CollectAs<LoadItem>(),
Consignee = consignee.As<Consignee>(),
});
var results = query.Results.ToList();
//Assuming there is only one Shipment returned for a given ID, we can just take the first Shipment.
Shipment shipmentOut = results.First().Shipment.Data;
shipmentOut.Consignments = new List<Consignment>();
foreach (var result in results)
{
result.Consignment.ShippedTo = result.Consignee;
result.Consignment.LoadItems = new List<LoadItem>();
result.Consignment.LoadItems.AddRange(result.LoadItem.Select(l => l.Data));
shipmentOut.Consignments.Add(result.Consignment);
}
return shipmentOut;
}
For your info, the DataElements I was using were:
public class Shipment
{
public string Id { get; set; }
public List<Consignment> Consignments { get; set; }
public override string ToString() { return Id; }
}
public class Consignment
{
public string Id { get; set; }
public List<LoadItem> LoadItems { get; set; }
public Consignee ShippedTo { get; set; }
public override string ToString() { return Id; }
}
public class Consignee
{
public string Name { get; set; }
public override string ToString() { return Name; }
}
public class LoadItem
{
public string Item { get; set; }
public override string ToString() { return Item; }
}
With relationships all defined like:
public class HasConsignment : Relationship, IRelationshipAllowingSourceNode<Shipment>, IRelationshipAllowingTargetNode<Consignment>
{
public const string TypeKey = "HAS_CONSIGNMENT";
public HasConsignment() : base(-1) {}
public HasConsignment(NodeReference targetNode): base(targetNode) {}
public HasConsignment(NodeReference targetNode, object data) : base(targetNode, data) {}
public override string RelationshipTypeKey { get { return TypeKey; } }
}
(with obvious changes where needed)

uCommerce - add dynamic property to order line

I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim

Unique constraint with data annotation

I'm using the System.ComponentModel.DataAnnotations namespace to validate my domain classes. How can I create a custom attribute to validate the uniqueness of a property regardless of the database (through some interface, for example)?
This is the solution I came up with for this situation, it simply checks the table for a record with a different id that has the same value for the property being validated. It assumes that you will be using LinqToSQL, and that any table on which this kind of validation is required has a single ID column.
I'd also put a unique constraint on the underlying table in the database. This attribute allows me to put a nice error message on the form and associate it with the appropriate property.
public class UniqueAttribute : ValidationAttribute
{
public Func<DataContext> GetDataContext { get; private set; }
public string IDProperty { get; private set; }
public string Message { get; private set; }
public UniqueAttribute(Type dataContextType, string idProperty, string message)
{
IDProperty = idProperty;
Message = message;
GetDataContext = () => (DataContext)Activator.CreateInstance(dataContextType);
}
public UniqueAttribute(Type dataContextType, string idProperty, string message, string connectionString)
{
IDProperty = idProperty;
Message = message;
GetDataContext = () => (DataContext)Activator.CreateInstance(dataContextType, new object[] { connectionString });
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var idProperty = validationContext.ObjectType.GetProperty(IDProperty);
var idType = idProperty.PropertyType;
var id = idProperty.GetValue(validationContext.ObjectInstance, null);
// Unsightly hack due to validationContext.MemberName being null :(
var memberName = validationContext.ObjectType.GetProperties()
.Where(p => p.GetCustomAttributes(false).OfType<DisplayAttribute>().Any(a => a.Name == validationContext.DisplayName))
.Select(p => p.Name)
.FirstOrDefault();
if (string.IsNullOrEmpty(memberName))
{
memberName = validationContext.DisplayName;
}
// End of hack
var validateeProperty = validationContext.ObjectType.GetProperty(memberName);
var validateeType = validateeProperty.PropertyType;
var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null);
var idParameter = Expression.Constant(id, idType);
var validateeParameter = Expression.Constant(validatee, validateeType);
var objectParameter = Expression.Parameter(validationContext.ObjectType, "o");
var objectIDProperty = Expression.Property(objectParameter, idProperty);
var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty);
var idCheck = Expression.NotEqual(objectIDProperty, idParameter);
var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter);
var compositeCheck = Expression.And(idCheck, validateeCheck);
var lambda = Expression.Lambda(compositeCheck, objectParameter);
var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2);
var genericCountMethod = countMethod.MakeGenericMethod(validationContext.ObjectType);
using (var context = GetDataContext())
{
var table = context.GetTable(validationContext.ObjectType) as IQueryable<Models.Group>;
var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda });
if (count > 0)
{
return new ValidationResult(Message);
}
}
return null;
}
}
Example usage:
[MetadataType(typeof(UserMetadata))]
public partial class Group : IDatabaseRecord
{
public class UserMetadata
{
[Required(ErrorMessage = "Name is required")]
[StringLength(255, ErrorMessage = "Name must be under 255 characters")]
[Unique(typeof(MyDataContext), "GroupID", "Name must be unique")]
public string Name { get; set; }
}
}
just do something like this on your model
[StringLength(100)]
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)]
public string CodigoHabilitacion { get; set; }
If I am understanding you properly, you should be able to create a custom ValidationAttribute and get a context to your repository through a custom factory.
Validator:
using System.ComponentModel.DataAnnotations;
public class DBUniqueAttribute : ValidationAttribute
{
private IRepository Repository{ get; set;}
public DBUniqueAttribute()
{
this.Repository = MyRepositoryFactory.Create();
}
public override bool IsValid(object value)
{
string stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
return Repository.IsUnique(stringValue);
}
}
You would have an IRepository interface with an IsUnique() method. The MyRepositoryFactory would have a static method called Create() which would create the concrete Repository necessary for your database. If the database type changes, you only need to update the Factory to return a new Repository for your new database.
I love #daveb's solution. Unfortunately, three years later it required some pretty heavy modification for me. Here's his solution updated for EF6. Hopefully will save someone an hour or so of fiddling.
public class UniqueAttribute : ValidationAttribute
{
public UniqueAttribute(string idProperty, string message)
{
IdProperty = idProperty;
Message = message;
}
[Inject]
public DataContext DataContext { get; set; }
private string IdProperty { get; set; }
private string Message { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var objectType = validationContext.ObjectType;
if (objectType.Namespace == "System.Data.Entity.DynamicProxies")
{
objectType = objectType.BaseType;
}
var idProperty = objectType.GetProperty(IdProperty);
var idType = idProperty.PropertyType;
var id = idProperty.GetValue(validationContext.ObjectInstance, null);
var memberName = validationContext.MemberName;
var validateeProperty = objectType.GetProperty(memberName);
var validateeType = validateeProperty.PropertyType;
var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null);
var idParameter = Expression.Constant(id, idType);
var validateeParameter = Expression.Constant(validatee, validateeType);
var objectParameter = Expression.Parameter(objectType, "o");
var objectIdProperty = Expression.Property(objectParameter, idProperty);
var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty);
var idCheck = Expression.NotEqual(objectIdProperty, idParameter);
var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter);
var compositeCheck = Expression.And(idCheck, validateeCheck);
var lambda = Expression.Lambda(compositeCheck, objectParameter);
var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2);
var genericCountMethod = countMethod.MakeGenericMethod(objectType);
var table = DataContext.Set(objectType);
var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda });
if (count > 0)
{
return new ValidationResult(Message);
}
return null;
}
}

Resources