ef核心单元测试

时间:2020-07-24 16:13:12

标签: unit-testing entity-framework-core

我的存储库中有一个要测试的方法

public User UpdateUserManyToMany(User user, List<Guid> manyToManyds)
{
    var dbContext = _databaseContext as DbContext;

    dbContext?.TryUpdateManyToMany(user.ManyToMany, manyToManyds
        .Select(x => new ManyToMany{
            OtherEntityId = x,
            UserId = user.Id,
        }), x => x.OtherEntityId);

    return user;
}

我的ManyToMany实体:

public class ManyToMany 
{
        public Guid OtherEntityId { get; set; }

        public OtherEntity OtherEntityId { get; set; }

        public Guid UserId { get; set; }

        public User User { get; set; }
}

我的TryUpdateManyToMany:

public static class ManyToManyExtensions
{
    public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
    {
        db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
        db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
    }

    public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
    {
        return items
            .GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
            .SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
            .Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
            .Select(t => t.t.item);
    }
}

这是我的单元测试:

using (var context = new InMemoryDataBaseContext())
{
    // Arrange
    var repository = new UserRepository(context);
    await context.Users.AddRangeAsync(GetUser());
    await context.SaveChangesAsync();

    // Act
    var manyIds = new List<Guid>();
    manyIds.Add(Guid.Parse("855d1a64-a707-40d5-ab93-34591a923abf"));
    manyIds.Add(Guid.Parse("855d1a64-a787-40d9-ac93-34591a923abf"));
    manyIds.Add(Guid.Parse("855d1a64-a707-41d9-ab93-39591a923abf"));

    var user = new User();
    var expected = repository.UpdateUserManyToMany(GetUser(), manyIds);

    // Assert
}

但是我在测试中遇到以下错误:

 Message: 
    System.InvalidOperationException : The instance of entity type 'ManyToMany' cannot be tracked because another instance with the same key value for {'UserId', 'OtherEntityId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
  Arborescence des appels de procédure: 
    IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
    IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
    IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
    NullableKeyIdentityMap`1.Add(InternalEntityEntry entry)
    StateManager.StartTracking(InternalEntityEntry entry)
    InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
    InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
    EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
    EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
    EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
    DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
    DbContext.RemoveRange(IEnumerable`1 entities)
    InternalDbSet`1.RemoveRange(IEnumerable`1 entities)
    ManyToManyExtensions.TryUpdateManyToMany[T,TKey](DbContext db, IEnumerable`1 currentItems, IEnumerable`1 newItems, Func`2 getKey) ligne 24
    UserRepository.UpdateUserManyToMany(User user, List`1 manyToManyds) ligne 59
    MyRepoUnitTest.MyTestMethod() ligne 102
    --- End of stack trace from previous location where exception was thrown ```

1 个答案:

答案 0 :(得分:0)

以下基于您提供的代码的示例程序可以正常运行:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class User
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        
        public ICollection<ManyToMany> ManyToMany { get; set; } = new HashSet<ManyToMany>();
    }

    public class OtherEntity
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        
        public ICollection<ManyToMany> ManyToMany { get; set; } = new HashSet<ManyToMany>();
    }

    public class ManyToMany 
    {
        public Guid OtherEntityId { get; set; }
        public Guid UserId { get; set; }

        public OtherEntity OtherEntity { get; set; }
        public User User { get; set; }
    }

    public class Context : DbContext
    {
        public DbSet<OtherEntity> OtherEntities { get; set; }
        public DbSet<User> Users { get; set; }
        public DbSet<ManyToMany> ManyToMany { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63077461")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<OtherEntity>(
                entity =>
                {
                    entity.HasKey(e => e.Id);

                    entity.HasMany(e => e.ManyToMany)
                        .WithOne(e => e.OtherEntity)
                        .HasForeignKey(e => e.OtherEntityId);

                    entity.HasData(
                        new OtherEntity
                        {
                            Id = new Guid("855d1a64-a707-40d5-ab93-34591a923abf"),
                            Name = "Bicycle"
                        },
                        new OtherEntity
                        {
                            Id = new Guid("855d1a64-a787-40d9-ac93-34591a923abf"),
                            Name = "Bus"
                        },
                        new OtherEntity
                        {
                            Id = new Guid("855d1a64-a707-41d9-ab93-39591a923abf"),
                            Name = "Plane"
                        });
                });
            modelBuilder.Entity<User>(
                entity =>
                {
                    entity.HasKey(e => e.Id);

                    entity.HasMany(e => e.ManyToMany)
                        .WithOne(e => e.User)
                        .HasForeignKey(e => e.UserId);
                });
            modelBuilder.Entity<ManyToMany>(
                entity =>
                {
                    entity.HasKey(e => new {e.OtherEntityId, e.UserId});
                });
        }
    }

    public static class ManyToManyExtensions
    {
        public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
        {
            db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
            db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
        }

        public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
        {
            return items
                .GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
                .SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
                .Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
                .Select(t => t.t.item);
        }
    }

    internal class UserRepository
    {
        private readonly Context _databaseContext;

        public UserRepository(Context context)
        {
            _databaseContext = context;
        }
        
        public User UpdateUserManyToMany(User user, List<Guid> manyToManyds)
        {
            var dbContext = _databaseContext as DbContext;

            dbContext?.TryUpdateManyToMany(user.ManyToMany, manyToManyds
                .Select(x => new ManyToMany{
                    OtherEntityId = x,
                    UserId = user.Id,
                }), x => x.OtherEntityId);

            return user;
        }
    }

    internal static class Program
    {
        private static async Task Main()
        {
            //
            // Operations with referential integrity intact:
            //
            
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            // Arrange
            var repository = new UserRepository(context);
            await context.Users.AddRangeAsync(GetUser());
            await context.SaveChangesAsync();

            // Act
            var manyIds = new List<Guid>
            {
                new Guid("855d1a64-a707-40d5-ab93-34591a923abf"),
                new Guid("855d1a64-a787-40d9-ac93-34591a923abf"),
                new Guid("855d1a64-a707-41d9-ab93-39591a923abf")
            };

            var expected = repository.UpdateUserManyToMany(GetUser(), manyIds);
        }

        private static User GetUser()
            => User;

        private static readonly User User = new User
        {
            Id = new Guid("30c35d2e-77fd-480b-9974-6ebf037a8f86"),
            Name = "John"
        };
    }
}

System.InvalidOperationException:无法跟踪实体类型'ManyToMany'的实例,因为已经跟踪了另一个具有相同'{'UserId','OtherEntityId'}'键值的实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用'DbContextOptionsBuilder.EnableSensitiveDataLogging'查看冲突的键值。

错误消息指出,您的ManyToMany条目中至少有一个已经存在于数据库中(存在一个具有相同的UserIdOtherEntityId组合的条目)。

您可以通过在用3个ID填充manyIds变量后直接运行以下代码来验证这一点:

var user = GetUser();
var alreadyExistingManyToMany = context.ManyToMany
    .Where(m => m.UserId == user.Id &&
                manyIds.Contains(m.OtherEntityId))
    .ToList();

Debug.Assert(alreadyExistingManyToMany.Count == 0);