0

I'm having a lot of trouble with creating my business entities from my data entities.

Github

My Data.Entities.User looks as follows:

public class User
{
    public User()
    {
        Messages = new List<Message>();
        Followers = new List<User>();
        Favorites = new List<Message>();
        Notifications = new List<Notification>();
        SubscribedTopics = new List<Topic>();
    }

    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Tag { get; set; }
    public string Picture { get; set; }

    public ICollection<Message> Messages { get; set; }
    public ICollection<User> Followers { get; set; }
    public ICollection<Message> Favorites { get; set; }
    public ICollection<Notification> Notifications { get; set; }
    public ICollection<Topic> SubscribedTopics { get; set; }

}

My Data.Mappers.UserMapper looks like this:

class UserMapper : EntityTypeConfiguration<User>
{
    public UserMapper()
    {
        // Table Mapping
        ToTable("Users");

        // Primary Key
        HasKey(u => u.Id);
        Property(u => u.Id)
            .IsRequired();

        // Properties
        Property(u => u.Name)
            .IsRequired()
            .HasMaxLength(140);

        Property(u => u.Email)
            .IsRequired()
            .HasMaxLength(255)
            .IsUnicode(false);

        Property(u => u.Tag)
            .IsRequired()
            .IsUnicode(false)
            .HasMaxLength(255)
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

        Property(u => u.Picture)
            .IsOptional();

        // Relationships
        HasMany(u => u.Followers)
            .WithMany()
            .Map(u => u.MapLeftKey("FollowerID"));

        HasMany(u => u.Favorites)
            .WithMany()
            .Map(u => u.MapLeftKey("MessageID"));

        HasMany(u => u.SubscribedTopics)
            .WithMany(t => t.Subscribers)
            .Map(u =>
            {
                u.ToTable("TopicSubscribers");
                u.MapLeftKey("UserID");
                u.MapRightKey("TopicID");
            });
    }
}

Finally, my Domain.Entities.User like this:

public class User : EntityBase<string>
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Tag { get; set; }
    public string Picture { get; set; }
    public IEnumerable<Message> Messages { get; set; }
    public IEnumerable<User> Followers { get; set; }
    public IEnumerable<Message> Favorites { get; set; }
    public IEnumerable<Notification> Notifications { get; set; }
    public IEnumerable<Topic> SubscribedTopics { get; set; }

    protected override void Validate()
    {
        if (string.IsNullOrWhiteSpace(Name))
        {
            AddBrokenRule(new ValidationRule("Name", "Name_Missing"));
        }
        if (string.IsNullOrWhiteSpace(Email))
        {
            AddBrokenRule(new ValidationRule("Email", "Email_Missing"));
        }
        if (string.IsNullOrWhiteSpace(Tag))
        {
            AddBrokenRule(new ValidationRule("Tag", "Tag_Missing"));
        }
        System.Uri uriResult;
        if (!string.IsNullOrWhiteSpace(Picture) &&
            Uri.TryCreate(Picture, UriKind.Absolute, out uriResult) &&
            (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
        {
            AddBrokenRule(new ValidationRule("Picture", "Picture_InvalidURI"));
        }
    }
}

EntityBase adds the Id parameter, so as far as parameters are concerned, these two classes should be identical.

The part where I run into trouble is mapping the Data Entity to the Domain Entity.

    public override IEnumerable<User> GetAll()
    {
        IEnumerable<User> user = _context.Users.Project()
                       .To<User>("Followers");
        return user;
    }

I think what's causing trouble is the circular navigational properties. User1 might have a follower named User2, while at the same time following User2.

So far I have tried both AutoMapper and ValueInjecter, but I have not had any success with either.

  • I tried adding "Virtual" to all navigational properties, enabling lazy and proxy loading, but this causes both AutoMapper and ValueInjecter to fail. ValueInjecter due to a already opened datareader and AutoMapper because of a type mismatch.
  • I tried explicitly loading navigational properties, but as soon as I Include("Followers") on User, I get a stackoverflow.
  • Trying to create a AutoMapperConfiguration where I specify a maxDepth of 1 yields a stackoverflow unless I add opt.ExplicitExpansion to every navigational property.
  • If i then try to explicitly expand a navigational property, I get

The type 'ShortStuff.Domain.Entities.User' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.

Ideally I would want a solution that lets me explicitly control which navigational properties to expand without recursing.

For example, I'd like to do something like:

_context.Users.Include("Followers").NoNavigation().AsEnumerable();

And then I would be able to access User.Followers and have a list of other users, with their navigational properties set to null.

Many thanks!

Full source code of my Repository / Service learning project can be found on Github at https://github.com/Bio2hazard/ShortStuff/tree/master/ShortStuffApi

Edit: I made some progress.

I got things to work by turning off proxy generation & lazy loading, and then using ValueInjector like so:

        IEnumerable<Data.Entities.User> userList = _context.Users.Include("Followers").Include("Favorites").Include("Messages").Include("Notifications").Include("SubscribedTopics");

        IEnumerable<User> users = userList.Select(u => new User
        {
            Id = u.Id,
            Email = u.Email,
            Picture = u.Picture,
            Tag = u.Tag,
            Name = u.Name,
            Followers = u.Followers.Select(uu => new User().InjectFrom<SmartConventionInjection>(uu)).Cast<User>(),
            Favorites = u.Favorites.Select(uf => new Message().InjectFrom<SmartConventionInjection>(uf)).Cast<Message>(),
            Messages = u.Messages.Select(um => new Message().InjectFrom<SmartConventionInjection>(um)).Cast<Message>(),
            Notifications = u.Notifications.Select(un => new Notification().InjectFrom<SmartConventionInjection>(un)).Cast<Notification>(),
            SubscribedTopics = u.SubscribedTopics.Select(ut => new Topic().InjectFrom<SmartConventionInjection>(ut)).Cast<Topic>()
        });

But that's a ton of code. I could probably create a factory for this, but there has got to be a easier way, right?

Bio2hazard
  • 150
  • 3
  • 10

1 Answers1

0

with ValueInjecter you can use the SmartConventionInjection which will only access the properties if it needs to get the value: http://valueinjecter.codeplex.com/wikipage?title=SmartConventionInjection&referringTitle=Home

other injections usually get the value too so that you could use it in the matching algorithm

for an example of using valueinjecter with Entity Framework (code first, latest) have a look at this project: http://prodinner.codeplex.com

Omu
  • 69,856
  • 92
  • 277
  • 407