I have the following class:
public class Foo : IFoo
{
public object Id { get; set; }
public string someProperty { get; set;}
}
If you notice, the Id property it's an object type. This is very important because I don't wanna to have any MongoDb dependence on my Models, thus the same models will be used in others databases repositories.
So I have my FooMongoDBRepository where my class map are defined:
public class FooMongoDBRepository : IFooRepository{
public MongoDbSubscriberRepository (MongoDatabase database){
BsonSerializer.LookupSerializer(typeof(Foo));
if (!BsonClassMap.IsClassMapRegistered (typeof(Foo))) {
BsonClassMap.RegisterClassMap<Foo> (cm => {
cm.AutoMap ();
cm.SetIdMember (cm.GetMemberMap (c => c.Id));
});
}
}
}
This work fine for inserts, but when try to Upsert, my _id key is always NULL. Can someone help me, how to force the Id field to be generated without using annotations ?
Thanks in advanced!
Edit: Now It's working!, here is the code
cm.AutoMap ();
cm.GetMemberMap(c => c.Id).SetIgnoreIfDefault(true);
cm.SetIdMember (cm.GetMemberMap (c => c.Id));
cm.IdMemberMap.SetIdGenerator(ObjectIdGenerator.Instance);
You need to add
[BsonIgnoreIfDefault] to your Id
BsonClassMap.RegisterClassMap<MyClass>(cm => {
cm.AutoMap();
cm.GetMemberMap(c => c.SomeProperty)
.SetDefaultValue("abc")
.SetIgnoreIfDefault(true);
});
The sample above can be found here
Related
Im using the Graphql .Net library to build a GraphQl API.
The following is a domain example of what we currently have, where, the area has a list of sampling point identifiers:
public class AreaRoot {
public String Id { get; set; }
public List<String > SamplingPointIds { get; set; }
}
public class SamplingPointRoot {
public String Id { get; set; }
public String Description { get; set; }
}
And the types are defined as follow:
public class AreaType : ObjectGraphType<AreaRoot>
{
public AreaType()
{
Name = "Area";
Field(x => x.Id, type: typeof(IdGraphType));
Field(x => x.SamplingPointIds, type: typeof(ListGraphType<StringGraphType>));
}
}
public class SamplingPointType : ObjectGraphType<SamplingPointRoot>
{
public SamplingPointType()
{
Name = "SamplingPoint";
Field(x => x.Id, type: typeof(IdGraphType));
Field(x => x.description, type: typeof(StringGraphType));
}
}
Is there any way to retrieving everything from the sampling point without changing the domain classes? there is an example in the conference GraphQL vs Traditional Rest API, in the 25:41 min, but this example is in java, and we could not make the same using the graphQl .net.
The next example illustrates the type of query we want to make:
query GetAreas(){
areas(){
Id
samplingPoints{
Id
description
}
}
}
So the question is: Is there a way to this as in the video above, as we pass the samplingPoints, and resolve it, retrieving the samplingPoints for that area (in some query)?
Question resolved on github. For those trying to do the same, its really easy actually, we just have to had resolver inside the AreaType like this:
public class AreaType : ObjectGraphType<AreaRoot>
{
public AreaType(IRepository repository)
{
Name = "Area";
Field(x => x.Id, type: typeof(IdGraphType));
Field<ListGraphType<SamplingPointType>>("areaSamplingPoints",
resolve: context =>
{
return repository.GetAllByAreaId(context.Source?.Id);
});
}
}
notice the context.Source?.Id used to access the Area Id...
And also, if you are trying to access the arguments of the top level context, well, you can't, as stated here, but instead, you can access the variables passed to the query, not the best, but not the worst, so use: context.Variables.ValueFor("variableName")
I have a class with a complex property:
public class A
{
public B Prop { get; set; }
}
public class B
{
public int Id { get; set; }
}
I've added a validator:
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.A.Id)
.NotEmpty()
.WithMessage("Please ensure you have selected the A object");
}
}
But during client-side validation for A.Id I still have a default validation message: 'Id' must not be empty. How can I change it to my string from the validator?
You can achieve this by using custom validator for nested object:
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.B).NotNull().SetValidator(new BValidator());
}
class BValidator : AbstractValidator<B>
{
public BValidator()
{
RuleFor(x => x.Id).NotEmpty().WithMessage("Please ensure you have selected the B object");
}
}
}
public class A
{
public B B { get; set; }
}
public class B
{
public int Id { get; set; }
}
There is an alternative option here. When configuring FluentValidation in your Startup class you can set the following configuration.ImplicitlyValidateChildProperties = true;
So the full code might look something like this
services
.AddMvc()
.AddFluentValidation(configuration =>
{
...
configuration.ImplicitlyValidateChildProperties = true;
...
})
So you would still have two validators one for class A and one for class B, then class B would be validated.
The documentation states:
Whether or not child properties should be implicitly validated if a matching validator can be found. By default this is false, and you should wire up child validators using SetValidator.
So setting this to true implies that child properties will be validated.
Pretty old question but for future generations - you can either use a child validator or define child rules inline as described in the official documentation: https://fluentvalidation.net/start#complex-properties
It depends on where you want to use your validators:
In the common scenario, where we have an application with N layers,
in the ASP.net layer, for the validation of View Models, DTOs or Commands (e.g. to achieve fail-fast validation), just enable ImplicitlyValidateChildProperties, as #StevenYates said:
services.AddMvc().AddFluentValidation(fv =>
{
fv.ImplicitlyValidateChildProperties = true;
});
When this is enabled, instead of having to specify child validators using SetValidator, MVC’s validation infrastructure will recursively attempt to automatically find validators for each property automatically.
IMHO, this is great, because besides being practical, it prevents us from forgetting some .SetValidator (...)!
Note that if you enable this behaviour you should not use SetValidator
for child properties, or the validator will be executed twice.
Doc: https://docs.fluentvalidation.net/en/latest/aspnet.html#implicit-vs-explicit-child-property-validation
But, in addition (or instead) of that, if you want to use FluentValidation in other layers (e.g. Domain), you need to use the SetValidator(...) method, as #t138 said, for example:
RuleFor(customer => customer.Address).SetValidator(new AddressValidator());
Doc: https://docs.fluentvalidation.net/en/latest/start.html#complex-properties
public class A
{
public B B { get; set; }
}
public class B
{
public int Id { get; set; }
}
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.B).NotNull().SetValidator(new BValidator());
}
class BValidator : AbstractValidator<B>
{
public BValidator()
{
RuleFor(x => x.Id).NotEmpty().WithMessage("message ....");
}
}
}
----------------------------------------------------------------------
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<Startup>();
fv.ImplicitlyValidateChildProperties = true;
});
}
You can use ChildRules method:
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.Prop)
.ChildRules(x => x.RuleFor(b => b.Id))
.NotEmpty()
.WithMessage("Please ensure you have selected the A object");
}
}
We have a enum Supplier
But now we need to also have some Domain data on that relation
So in 99.9% in the domain code we doe operations on the enum like product.Supplier == Suppliers.FedEx
But now we also have added product.SupplierInfo.CanAdjustPickupTime where SupplierInfo is a Entity and not just a simple enum type.
I have tried these configs
Property(p => p.Supplier)
.IsRequired()
.HasColumnName("SupplierId");
HasRequired(p => p.SupplierInfo)
.WithMany()
.HasForeignKey(p => p.Supplier); //I have also tried casting to int doing .HasForeignKey(p => (int)p.Supplier)
This will fail with
The ResultType of the specified expression is not compatible with the
required type. The expression ResultType is
'MyApp.Model.Suppliers' but the required type is
'Edm.Int32'. Parameter name: keyValues[0]
Also tried
Property(l => l.Supplier)
.IsRequired()
.HasColumnName("SupplierId");
HasRequired(p => p.SupplierInfo)
.WithMany()
.Map(m => m.MapKey("SupplierId"));
This will offcourse give the good old
One or more validation errors were detected during model generation:
SupplierId: Name: Each property name in a type must be unique.
Property name 'SupplierId' is already defined.
I could offcourse define SupplierId as a Property use that with HasForeignKey But then I need to change to .SuppliedId == (int)Suppliers.FedEx etc. Not really a solution.
I could also add a property enum that uses the SupplierId property as backing field, but this will not work with Expressions since it needs to use real mapped DB properties
Any ideas?
I have classes:
public class Agreement
{
public int Id { get; set; }
public AgreementStateTypeEnum AgreementStateId { get; set; }
}
public class AgreementState
{
public int Id { get; set; }
public string Title { get; set; }
}
context:
public class AgreementContext :DbContext
{
public AgreementContext() : base("SqlConnection") { }
public DbSet<Agreement> Agreements { get; set; }
}
In method OnModelCreating I wrote nothing.
My enum:
public enum AgreementStateTypeEnum : int
{
InReviewing = 1,
Confirmed = 2,
Rejected = 3
}
In database: in table Agreements I have foreign key AgreementStateId - it is link to table AgreementStates.
Everything is working. For example:
var temp = context.Agreements.First(x => x.AgreementStateId == AgreementStateTypeEnum.Confirmed);
I use enum how foreign key.
Finally I found the problem. (I'm using EF6, NET 4.5)
So, if you create a type Enum in your code, you couldn't create a relationship with other property virtual.
//This is wrong, when do you create a foreignkey using a type enum
//Do You should remove that's code on in your class Map.
HasRequired(p => p.SupplierInfo)
.WithMany()
.HasForeignKey(p => p.Supplier); //I have also tried casting to int doing
.HasForeignKey(p => (int)p.Supplier)
If did you created a type enum it means that you don't need for a table return data throught for a join in EF.
So, the correct code it is:
public class MyClass{
public enum myEnumType {
FedEx,
Olther
}
public int id {get;set;}
public myEnumType Supplier {get;set;}
}
//My class Map (using Fluent...)
public class MyClassMap {
HasKey(t => t.Id);
Property(t => t.Id).HasColumnName("Id");
//The type [supplier] should be [int] in database.
Property(t => t.Supplier).HasColumnName("supplier");
//That's all, you don't need write relationship, int this case
//Because, when the data returns, the EF will to do the conversion for you.
}
I hope that's useful
The best way I have found to deal with this scenario is to map Supplier as a regular domain object and create a separate class of known supplier IDs.
public class KnownSupplierIds
{
public const int FedEx = 1;
public const int UPS = 2;
// etc.
}
if (product.Supplier.SupplierId == KnownSupplierIds.Fedex) { ... };
When your code needs to check the supplier, it can compare the IDs; when you need additional info from the domain model you just load the Supplier. The reason I prefer using a class of constants instead of an enum is that the pattern works for string comparisons also and there's no need to cast.
I use Automapper to map from EF entities to view models.
I now have this entity
public class MenuGroup : IEntity
{
public int MenuGroupId { get; set; }
protected ICollection<MenuGroupItem> _menuGroupItems { get; set; }
public IEnumerable<MenuGroupItem> MenuGroupItems { get { return _menuGroupItems; } }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
_menuGroupItems.Add(menuGroupItem);
}
}
That is an encapsulated collection, I followed instructions here to make this work: http://lostechies.com/jimmybogard/2014/05/09/missing-ef-feature-workarounds-encapsulated-collections/
So I configure it like so this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
Now the problem I get is when I try to use automapper to map my MenuGroup into a viewmodel.
I run this code: menuGroup = _context.MenuGroups.Project().To<MenuGroupEditModel>().Single(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName);
and get this error: The specified type member 'MenuGroupItems' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Now I can work with the collection, it saves correctly to the database and all is well there it's only when i want to user automapper here that it fails.
If I replace the protected ICollection and public IEnumerable with simply: public ICollection<MenuGroupItem> MenuGroupItems { get; set; } it works right away so the problem lies in automapping with my encapsulated collection.
Update: I also tried this menuGroup = _context.MenuGroups.Include(x => x.MenuGroupItems).Where(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName).Project().ToSingleOrDefault<MenuGroupEditModel>(); with no difference other than that it errored in the ToSingleOrDefault instead.
Your problem is that Automapper can't modify MenuGroupItems because there is no public setter.
Your solution is changing it to this:
public IEnumerable<MenuGroupItem> MenuGroupItems { get; set; }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
MenuGroupItems.Add(menuGroupItem);
}
After some more debugging I figured out the Config file looking like this
public MenuGroupConfiguration()
{
this.HasMany(x => x.MenuGroupAssigments).WithRequired(x => x.BelongTo).WillCascadeOnDelete(true);
this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
}
had not been included leading to that error that now makes sense.
I can add as a general tip that if you don't use auto-mapper for a query but still use your encapsulated collection remember that you have to call decompile for it to work.
like so
var menuGroupsWithType =
_context.MenuGroups.Include(x => x.MenuGroupItems).Include(x => x.MenuGroupAssigments).Where(x => x.MenuGroupAssigments.Any(y => y.AssignToAll == selectedStructureType))
.OrderBy(x => x.Name).Decompile().ToList();
i defined an entity called Variable and derived classes by using Table Per Hierarchy (TPH). The Base class "Variable" contains a collection of PropertyValues:
private ICollection<PropertyValue> propertyValues;
public const string DiscriminatorColumn = "Discriminator";
public const string Table = "Variables";
public VariableType VariableType { get; set; }
public string Name { get; set; }
[NotMapped]
public string Discriminator { get; set; }
public virtual ICollection<PropertyValue> PropertyValues
{
get { return this.propertyValues ?? (this.propertyValues = new ObservableCollection<PropertyValue>()); }
set { SetProperty(ref this.propertyValues, value, () => PropertyValues); }
}
Now, i want to derive a SpecialVariable class (or more than one), which define some SpecialProperties (e.g. HighLimit) which should be mapped to an entry in the PropertyValues (table).
public class MySpecialVariabe : Variable
{
public double HighLimit { get; set; }
}
My OnModelCreating function looks currently like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Variable>().HasKey(x => new { x.Id });
modelBuilder.Entity<Variable>()
.Map<MySpecialVariabe>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe).Name))
.Map<MySpecialVariabe2>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe2).Name)).ToTable(Variable.Table);
}
Can someone give me some tips how to realize this, without writing tons of bad looking code in the derived class. (Performance is not that important.)
best regards,
Chris
You can't map properties to records. That is how I understand your question. You have some PropertyValues table which is most probably some Key/Value pair and you want to map entity properties as records (data) to this table. This is not something which EF do for you. You must provide not mapped properties which will work with correct record in propertyValues collection.
Something like:
[NotMapped]
public double HighLimit
{
get
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
return current != null ? current.Value : 0.0;
}
set
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
if (current != null)
{
current.Value = value;
}
else
{
propertyValues.Add(new PropertyValue { Key = "HighLimit", Value = value });
}
}
}
The problem with this approach is that you can't use HighLimit in Linq-to-entities queries - you must always use PropertyValues.
Moreover TPH in EF requires that properties of derived entity (MySpecialVariable) are mapped to the same table as parent entity (Variable). You can't map properties of derived entity into data stored in other table (PropertyValues).