防止自引用表在C#中变成圆形

时间:2019-03-27 17:33:03

标签: c#

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

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

我有一张桌子,如下:

经理 用户名| ManagerId

一个例子:

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。

知道为什么吗?

2 个答案:

答案 0 :(得分:1)

循环依赖可以在递归树内的任何级别或分支上发生,而不仅仅是最低级别。目前,只要您到达最低点或没有任何管理员的用户,您的递归函数就会返回,而不会遍历您// let inputString = "Foo<Bar, Baz<Qux<Some, Thing<Else<But, Not>, So<Is>>, Other>>>" let inputString = "Foo<Bar, Baz<Qux>>" const replacements = {} let replacementIndex = 0 while (true) { const replacement = (inputString.match(/[A-Z][a-z0-9]+<(([A-Z][a-z0-9]+)[,\s]*)+>/) || [])[0] if (replacement) { const key = `Key${replacementIndex}` replacementIndex++ replacements[key] = replacement inputString = inputString.replace(replacement, key) } else { break } } const resultJson = {} const parseSentence = (text) => { const [key, valuesStr] = text.replace(/>$/, '').split(/</) const values = valuesStr.split(',').map((x) => x.trim()) return { [key]: values, } } Object.keys(replacements).forEach((key) => { resultJson[key] = parseSentence(replacements[key]) }) while (true) { let replacementsFound = false Object.keys(resultJson).forEach((key) => { Object.keys(resultJson[key]).forEach((name) => { resultJson[key][name] = resultJson[key][name].map((value) => { if (/^Key[\d+]$/.test(value)) { replacementsFound = true return resultJson[value] } return value }) }) }) if (!replacementsFound) { break } } const resultKey = `Key${replacementIndex - 1}` const unpreparedResult = resultJson[resultKey] const prepareResultJson = (json) => { const name = Object.keys(json)[0] const generics = [] json[name].forEach((generic) => { if (typeof generic === 'string') { generics.push({ name: generic, generics: [] }) } else { generics.push(prepareResultJson(generic)) } }) const result = { name, generics, } return result } const finalResult = prepareResultJson(unpreparedResult) console.log(finalResult) 中的其他管理员。相反,我将更改您的递归函数以指示foreachUserId的组合是否会创建循环依赖项。然后,一旦发现冲突,您就可以返回true;如果在任何分支或级别中都没有找到冲突,则可以返回false。参见示例:

ManagerId

添加方法:

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)userId列中的所有记录。

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

  

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

答案 1 :(得分:0)

我个人不喜欢递归,仅仅是因为它的核心是递归函数,它允许堆栈溢出,并且因为有许多更简单的方法可以执行相同的逻辑而没有可能的例外,除非我我不使用它们知道一组商品受限制的事实。

如果我们要预防问题,我们必须禁止消费者更改用户。仅公开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

     

添加了用户?添加了错误的用户:空

     

添加了用户?添加了真正的用户: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