【问题标题】:Prevent a self-referencing table from becoming circular in c#防止自引用表在 C# 中变为循环
【发布时间】:2019-03-28 06:00:39
【问题描述】:

Here@mellamokb 解释了如何创建约束来防止循环依赖。

我需要在 C# 中实现相同的功能。我相信这需要一种递归函数,但我无法理解它。

我有一张如下表:

经理 用户名 |经理编号

一个例子:

UserId | ManagerId
1         2
2         1

这是允许的。用户可以互相管理

但是:

UserId | ManagerId
1         2
2         3
3         1

这是不允许的

我尝试了以下方法:

 private Manager CheckCircularDependency(int userId)
    {
        var managers = Managers.GetByManagersId(userId);
        if(managers==null || managers.Count == 0)
        {
            return null;
        }

        foreach (Manager manager in managers)
        {
             var man= CheckCircularDependency(manager.UserId);
            if (man== null)
            {
                return manager;
            }
        }

        return null;
    }

这里是检查:

public boid  AddManager(int userId, int managerId){

 var manager= CheckCircularDependency(userId);
  if (manager!= null)
            {
                if (manager.ManagerId == userId && manager.UserId == managerId)
                {
                    //this is allowed
                }else if(manager.ManagerId != userId){
                  throw new InvalidOperationException(" Not allowed");
                 }
            }
}

我在桌子上:

1   2
2   3

当我尝试将另一个经理插入为 3 => 1 时,我应该得到异常,但我没有。递归总是返回 null 而不是返回用户 1。

知道为什么吗?

【问题讨论】:

  • 您调用GetByManagerId 但传入userId 是否正确?如果不看到该方法的作用,很难判断。
  • @DarrenRuane 它通过提供 managerId 从 Managers 表中获取经理列表
  • 是的,但是您传入的是 userId 而不是 managerId。我错过了什么吗?
  • 基本上它告诉我去找这个用户管理员。然后它找到那些用户管理器管理器,然后进一步直到没有用户的管理器。因此,在我给用户 1 的示例中,他不是任何人的经理。所以函数应该返回那个用户。
  • if userId == mangerId then error message

标签: c#


【解决方案1】:

循环依赖可以出现在递归树中的任何级别或分支,而不仅仅是底层。目前,一旦您到达底部或没有任何管理器的用户,您的递归函数将返回,而无需通过 foreach 中的其余管理器。相反,我会更改您的递归函数以指示 UserIdManagerId 的组合是否会创建循环依赖项。然后,您可以在发现冲突后立即返回 true,如果在任何分支或级别中未发现冲突,则返回 false。见例子:

private bool CheckCircularDependency(int userId, int managerId, bool rootNode = false)
{
    //Optional: A user may not manage themselves
    if(userId == managerId && rootNode) return true;

    var managers = Managers.GetByManagersId(userId);
    if(managers == null || managers.Count == 0)
    {
        //User is not managing anyone therefore no conflict
        return false;
    }

    foreach (Manager manager in managers)
    {
        //Circular dependency, unless they are managers of each other
        if(manager.UserId == managerId && !rootNode) return true;

         var circularDependency = CheckCircularDependency(manager.UserId, managerId);
        if (circularDependency)
        {
            return true;
        }
    }

    //No conflicts found
    return false;
}

添加方法:

public void AddManager(int userId, int managerId)
{
    if(CheckCircularDependency(userId, managerId, true))
    {
        throw new InvalidOperationException(" Not allowed");
    }
    else
    {
        //this is allowed
    }
}

此示例假设Manager.GetByManagersId(userId) 返回userIdManagerId 列中的所有记录。

https://dotnetfiddle.net/Jc6tfY 的工作示例,带有输出:

添加经理 1 => 2:成功!
添加经理 2 => 3:成功!
添加经理 2 => 1:成功!
添加经理 3 => 1:不允许

【讨论】:

  • 如何让用户成为对方的管理者?所以 1 => 2 和 2=> 1 。这是必须允许的。
  • @akd 更新了解决方案以指定何时对 CheckCircularDependency 进行初始调用,即处理并允许用户相互管理的 rootNode。
  • @akd 添加 dotnetfiddle 示例和修复代码示例时检查“用户可能无法管理自己”
  • 不,@jason。在某些情况下,这会导致 Stackoverflow 异常。看这里:dotnetfiddle.net/hC5Ttn
【解决方案2】:

就我个人而言,我不喜欢递归,因为递归函数的核心是允许堆栈溢出的可能性,而且由于有许多更简单的方法可以执行相同的逻辑而没有可能的异常,我不会使用它们,除非我知道 Set of items 是有限的。

如果我们要预防问题,我们必须禁止消费者更改用户。只有公开IUser 接口才能提供这种类型的可访问性。 (用户可以通过反射来改变它,但这不是你问的问题)。

DotNetFiddle

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
   public static void Main()
   {
      var userManager = new UserManager();
      // self referencing manager?
      var user1 = new User { Id = 1, ManagerId = 1 };

      // two users managing each other
      var user2 = new User { Id = 2, ManagerId = 3 };
      var user3 = new User { Id = 3, ManagerId = 2 };

      // three users managing each other
      var user4 = new User { Id = 4, ManagerId = 5 };
      var user5 = new User { Id = 5, ManagerId = 6 };
      var user6 = new User { Id = 6, ManagerId = 4 };

      // no manager?
      var user7 = new User { Id = 7, };

      IUser outUser;

      Console.WriteLine($"added user?{userManager.TryAdd(user1, out outUser)} user added:{outUser.ToConsole()}");
      Console.WriteLine($"added user?{userManager.TryAdd(user2, out outUser)} user added:{outUser.ToConsole()}");
      Console.WriteLine($"added user?{userManager.TryAdd(user3, out outUser)} user added:{outUser.ToConsole()}");
      Console.WriteLine($"added user?{userManager.TryAdd(user4, out outUser)} user added:{outUser.ToConsole()}");
      Console.WriteLine($"added user?{userManager.TryAdd(user5, out outUser)} user added:{outUser.ToConsole()}");
      Console.WriteLine($"added user?{userManager.TryAdd(user6, out outUser)} user added:{outUser.ToConsole()}");

      Console.WriteLine($"added user?{userManager.TryAdd(user7, out outUser)} user added:{outUser.ToConsole()}");

      Console.WriteLine("done adding...");

      Console.WriteLine("Current users:");
      foreach(var kvp in userManager.Users)
      {
         Console.WriteLine($"{kvp.Value.ToConsole()}");
      }

   }


   public class UserManager
   {
      private Dictionary<int, User> _users = new Dictionary<int, User>();

      public IReadOnlyDictionary<int, IUser> Users
      {
         get
         {
            return _users
            .Select(kvp => new { Key = kvp.Key, Value = kvp.Value as IUser })
            .ToDictionary(a => a.Key, a => a.Value);
         }
      }

      public bool TryAdd(IUser user, out IUser userResult)
      {
         userResult = null;
         var result = !IsUserCircular(user);
         if (result)
         {
            var validUser = new User { Id = user.Id, ManagerId = user.ManagerId };
            _users.Add(validUser.Id, validUser);
            userResult = validUser;
         }

         return result;
      }

      private bool IsUserCircular(IUser user)
      {
         var currentUser = user;
         var currentManagers = new HashSet<int> { user.Id };
         var result = false;
         while (currentUser?.ManagerId != null)
         {
            // just because they have an Id doesn't mean that user exists...
            // or does it?
            if (currentManagers.Contains(currentUser.ManagerId.Value))
            {
               // we've come full circle to the same user through X users
               result = currentManagers.Count > 2;
               break;
            }
            else
            {
               if (_users.TryGetValue(currentUser.ManagerId.Value, out User nextUser))
               {
                  currentManagers.Add(currentUser.ManagerId.Value);
                  currentUser = nextUser;
               }
               else
               {
                  // user has Manager that doesn't exist in our system
                  currentUser = null;
               }
            }
         }

         return result;
      }
   }
}
public interface IUser
{
   int Id { get; }

   int? ManagerId { get; }
}

public class User : IUser
{
   public int Id { get; set; }
   public int? ManagerId { get; set; }
}


public static class IUserExtensions
{
   public static string ToConsole(this IUser user)
   {
      if (user == null)
         return $"null";
      return $"Id={user.Id} ManagerId={(user.ManagerId.HasValue ? user.ManagerId.ToString() : "null")}";
   }

}

输出:

添加的用户?添加的真实用户:Id=1 ManagerId=1

添加的用户?添加的真实用户:Id=2 ManagerId=3

添加的用户?添加的真实用户:Id=3 ManagerId=2

添加的用户?添加的真实用户:Id=4 ManagerId=5

添加的用户?添加的真实用户:Id=5 ManagerId=6

添加了用户?添加了假用户:null

添加的用户?添加的真实用户:Id=6 ManagerId=null

完成添加...

当前用户:

Id=1 ManagerId=1

Id=2 ManagerId=3

Id=3 ManagerId=2

Id=4 ManagerId=5

Id=5 ManagerId=6

Id=7 ManagerId=null

【讨论】:

  • 感谢您的详细解释。我会选择递归解决方案,因为不允许自我管理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-12
  • 2011-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多